forked from phc/website
Ulteriori aggiunte al prototipo della pagina Appunti Condivisi
This commit is contained in:
@@ -49,7 +49,7 @@ export default defineConfig([
|
||||
plugins: [esbuild({ minify: true })],
|
||||
},
|
||||
{
|
||||
input: 'src/appunti-condivisi.jsx',
|
||||
input: 'src/appunti-condivisi/main.jsx',
|
||||
output: {
|
||||
file: 'out/appunti-condivisi.min.js',
|
||||
format: 'iife',
|
||||
@@ -68,7 +68,8 @@ export default defineConfig([
|
||||
],
|
||||
],
|
||||
}),
|
||||
terser(),
|
||||
// https://rollupjs.org/guide/en/#-w--watch
|
||||
!process.env.ROLLUP_WATCH && terser(),
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import { render } from 'preact'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
|
||||
function randomHex(length = 16) {
|
||||
return Array.from({ length }, () => Math.floor(Math.random() * 16).toString(16)).join('')
|
||||
}
|
||||
|
||||
const useAutosizeTextarea = () => {
|
||||
const [rows, setRows] = useState(1)
|
||||
|
||||
const onInput = e => {
|
||||
const lines = e.target.value.split('\n')
|
||||
console.log(lines)
|
||||
|
||||
setRows(lines.length)
|
||||
}
|
||||
|
||||
return { rows, onInput }
|
||||
}
|
||||
|
||||
const InputTags = ({ tags, setTags, availableTags }) => {
|
||||
availableTags ??= []
|
||||
|
||||
const [id] = useState('tags-' + randomHex())
|
||||
|
||||
const nextRef = useRef()
|
||||
const [nextText, setNextText] = useState('')
|
||||
|
||||
const onFocus = e => {
|
||||
if (!e.target.closest('.tags .tag .remove')) {
|
||||
nextRef.current?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const removeTag = tag => {
|
||||
setTags(tags => tags.filter(t => t !== tag))
|
||||
}
|
||||
|
||||
const addTag = tag => {
|
||||
setTags(tags => [...tags, tag])
|
||||
}
|
||||
|
||||
const onKeyDown = e => {
|
||||
if (e.key === 'Backspace' && nextText.length === 0) {
|
||||
removeTag(tags.at(-1))
|
||||
}
|
||||
|
||||
const trimmed = nextText.trim()
|
||||
if (e.key === 'Enter' && trimmed.length > 0) {
|
||||
addTag(trimmed)
|
||||
setNextText('')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="input-tags" onClick={onFocus}>
|
||||
{tags.map(tag => (
|
||||
<div class="tag">
|
||||
<span>{tag}</span>
|
||||
<span class="remove" onClick={() => removeTag(tag)}>
|
||||
<i class="fa-solid fa-remove"></i>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<input
|
||||
type="text"
|
||||
ref={nextRef}
|
||||
list={id}
|
||||
value={nextText}
|
||||
onInput={e => setNextText(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<datalist id={id}>
|
||||
{availableTags.map(({ id, label }) => (
|
||||
<option value={id} label={label} />
|
||||
))}
|
||||
</datalist>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const App = ({}) => {
|
||||
const descriptionTextareaProps = useAutosizeTextarea()
|
||||
|
||||
const [tags, setTags] = useState(['Geometria 1', 'Fortuna', 'Frigerio', '2013/2014'])
|
||||
|
||||
const availableTags = [
|
||||
...Array.from({ length: 10 }, (_, i) => {
|
||||
const year = new Date().getFullYear() - i
|
||||
|
||||
return { id: `${year}/${year + 1}`, label: `Anno Accademico ${year}/${year + 1}` }
|
||||
}),
|
||||
{ id: 'G1', label: 'Geometria 1' },
|
||||
{ id: 'G2', label: 'Geometria 2' },
|
||||
{ id: 'ETI', label: 'Elementi di Teoria degli Insiemi' },
|
||||
{ id: 'ETA', label: 'Elementi di Topologia Algebrica' },
|
||||
{ id: 'Analisi 1', label: 'Analisi 1' },
|
||||
{ id: 'Aritmetica', label: 'Aritmetica' },
|
||||
{ id: 'Programmazione', label: 'Programmazione' },
|
||||
{ id: 'Fisica 1', label: 'Fisica 1' },
|
||||
{ id: 'Steffe 1', label: 'Laboratorio di Comunicazione Mediante Calcolatore' },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="upload-region">
|
||||
<span>Trascina qui un PDF oppure usa il tasto sottostante</span>
|
||||
<button>Carica PDF</button>
|
||||
</div>
|
||||
<div class="dispense-table">
|
||||
<div class="edit header"></div>
|
||||
<div class="name header">Nome</div>
|
||||
<div class="tags header">Tags</div>
|
||||
<div class="status header">Stato</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name">Appunti di Geometria 1</div>
|
||||
<div class="tags">
|
||||
<div class="tag">Geometria 1</div>
|
||||
<div class="tag">Prof. 1</div>
|
||||
<div class="tag">2016/2017</div>
|
||||
</div>
|
||||
<div class="status approved">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="expanded">
|
||||
<div class="edit-close">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="edit-container">
|
||||
<p>
|
||||
Qui puoi modificare le varie proprietà della dispensa, ricorda che se
|
||||
carichi un nuovo PDF diverso dal precedente dovrà essere nuovamente
|
||||
sottoposto a approvazione quindi inizialmente scomparirà dall'elenco
|
||||
principale.
|
||||
</p>
|
||||
<div class="edit-form">
|
||||
<div class="label">Nome</div>
|
||||
<input type="text" placeholder="Nome" value="Mezzedimi" />
|
||||
<div class="label">Descrizione</div>
|
||||
<textarea
|
||||
placeholder="Descrizione..."
|
||||
value="Best dispensa di Geometria 1 ever written anche se non in LaTeX"
|
||||
{...descriptionTextareaProps}
|
||||
></textarea>
|
||||
<div class="label">Tags</div>
|
||||
<InputTags {...{ tags, setTags, availableTags }} />
|
||||
<div class="label">PDF</div>
|
||||
<input type="file" value="/mezzedimi.pdf" accept="application/pdf" />
|
||||
|
||||
<div class="right">
|
||||
<button class="primary">Salva</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name pending">Appunti di Geometria 2</div>
|
||||
<div class="tags">
|
||||
<div class="tag">Geometria 2</div>
|
||||
<div class="tag">Prof. 2</div>
|
||||
<div class="tag">2017/2018</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
<div class="tag">Tanti Tag</div>
|
||||
</div>
|
||||
<div class="status pending" title="In attesa di approvazione...">
|
||||
<i class="fas fa-hourglass"></i>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name rejected">F1Le SuP3R LeGaLe</div>
|
||||
<div class="tags">
|
||||
<div class="tag">Foo</div>
|
||||
<div class="tag">Bar</div>
|
||||
</div>
|
||||
<div class="status rejected">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render(<App />, document.querySelector('#app'))
|
||||
@@ -0,0 +1,526 @@
|
||||
import { render } from 'preact'
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { formatFileSize, intersperse } from './util.js'
|
||||
|
||||
function randomHex(length = 16) {
|
||||
return Array.from({ length }, () => Math.floor(Math.random() * 16).toString(16)).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista di tag "standard" disponibili nel completamento per i tags
|
||||
*/
|
||||
const availableTags = [
|
||||
// Genera una ventina di coppie come "2022/2023"
|
||||
...Array.from({ length: 20 }, (_, i) => {
|
||||
const year = new Date().getFullYear() - i
|
||||
|
||||
return { id: `${year}/${year + 1}`, label: `Anno Accademico ${year}/${year + 1}` }
|
||||
}),
|
||||
// List di tag "standard", l'id è la vera stringa del tag, mentre la label
|
||||
{ id: 'geometria-1', label: 'Geometria 1' },
|
||||
{ id: 'geometria-2', label: 'Geometria 2' },
|
||||
{ id: 'eti', label: 'Elementi di Teoria degli Insiemi' },
|
||||
{ id: 'eta', label: 'Elementi di Topologia Algebrica' },
|
||||
{ id: 'ega', label: 'Elementi di Geometria Algebrica' },
|
||||
{ id: 'gtd', label: 'Geometria e Topologia Differenziale' },
|
||||
{ id: 'ist-anal', label: 'Istituzioni di Analisi' },
|
||||
{ id: 'ist-geom', label: 'Istituzioni di Geometria' },
|
||||
{ id: 'ist-fis', label: 'Istituzioni di Fisica' },
|
||||
{ id: 'ist-prob', label: 'Istituzioni di Probabilità' },
|
||||
{ id: 'ist-alg', label: 'Istituzioni di Algebra' },
|
||||
{ id: 'ist-num', label: 'Istituzioni di Analisi Numerica' },
|
||||
{ id: 'analisi-1', label: 'Analisi 1' },
|
||||
{ id: 'analisi-2', label: 'Analisi 2' },
|
||||
{ id: 'aritmetica', label: 'Aritmetica' },
|
||||
{ id: 'programmazione', label: 'Programmazione' },
|
||||
{ id: 'fisica-1', label: 'Fisica 1' },
|
||||
{ id: 'steffe-1', label: 'Laboratorio di Comunicazione Mediante Calcolatore' },
|
||||
]
|
||||
|
||||
const useAutosizeTextarea = ({ minRows } = {}) => {
|
||||
minRows ??= 1
|
||||
|
||||
const textareaRef = useRef(null)
|
||||
|
||||
const updateTextareaHeight = useCallback(() => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = 'auto'
|
||||
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
updateTextareaHeight()
|
||||
})
|
||||
|
||||
return {
|
||||
rows: minRows,
|
||||
ref: textareaRef,
|
||||
onInput: updateTextareaHeight,
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeTag = tag => {
|
||||
return tag
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/ /g, '-')
|
||||
.replace(/[^\p{L}0-9\/\-]/gu, '')
|
||||
}
|
||||
|
||||
const InputTags = ({ tags, setTags, availableTags }) => {
|
||||
availableTags ??= []
|
||||
|
||||
const [id] = useState('tags-' + randomHex())
|
||||
|
||||
const nextRef = useRef()
|
||||
const [nextText, setNextText] = useState('')
|
||||
|
||||
const onFocus = e => {
|
||||
if (!e.target.closest('.tags .tag .remove')) {
|
||||
nextRef.current?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const removeTag = tag => {
|
||||
setTags(tags => tags.filter(t => t !== tag))
|
||||
}
|
||||
|
||||
const addTag = tag => {
|
||||
setTags(tags => [...tags, tag])
|
||||
}
|
||||
|
||||
const onKeyDown = e => {
|
||||
if (e.key === 'Backspace' && nextText.length === 0) {
|
||||
removeTag(tags.at(-1))
|
||||
}
|
||||
|
||||
const trimmed = nextText.trim()
|
||||
if (e.key === 'Enter' && trimmed.length > 0) {
|
||||
addTag(normalizeTag(trimmed))
|
||||
setNextText('')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="input-tags" onClick={onFocus}>
|
||||
{tags.map(tag => (
|
||||
<div class="tag">
|
||||
<span>{tag}</span>
|
||||
<span class="remove" onClick={() => removeTag(tag)}>
|
||||
<i class="fa-solid fa-remove"></i>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<input
|
||||
type="text"
|
||||
ref={nextRef}
|
||||
list={id}
|
||||
value={nextText}
|
||||
onInput={e => setNextText(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<datalist id={id}>
|
||||
{availableTags.map(({ id, label }) => (
|
||||
<option value={id} label={label} />
|
||||
))}
|
||||
</datalist>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const withClasses = a => {
|
||||
if (Array.isArray(a)) {
|
||||
return { class: a.filter(className => !!className).join(' ') }
|
||||
} else if (typeof a === 'object') {
|
||||
return {
|
||||
class: Object.entries(a)
|
||||
.flatMap(([className, active]) => (active ? [className] : []))
|
||||
.join(' '),
|
||||
}
|
||||
} else if (typeof a === 'string') {
|
||||
return { class: a }
|
||||
} else {
|
||||
throw new Error(`Invalid class format`)
|
||||
}
|
||||
}
|
||||
|
||||
const UploadRegion = ({}) => {
|
||||
const [draggingOver, setDraggingOver] = useState(false)
|
||||
|
||||
const onDragOver = e => {
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
setDraggingOver(true)
|
||||
}
|
||||
|
||||
const onDragLeave = () => {
|
||||
setDraggingOver(false)
|
||||
}
|
||||
|
||||
const onDropFiles = files => {
|
||||
if (files.length !== 1) {
|
||||
throw new Error('Must drop one file')
|
||||
}
|
||||
|
||||
const [file] = files
|
||||
console.dir(file)
|
||||
|
||||
if (file.type !== 'application/pdf') {
|
||||
console.error('The file must be a PDF')
|
||||
}
|
||||
|
||||
console.log(formatFileSize(file.size))
|
||||
}
|
||||
|
||||
const onDrop = e => {
|
||||
e.preventDefault()
|
||||
|
||||
if (e.dataTransfer.items) {
|
||||
onDropFiles(
|
||||
[...e.dataTransfer.items].flatMap(item =>
|
||||
item.kind === 'file' ? [item.getAsFile()] : []
|
||||
)
|
||||
)
|
||||
} else {
|
||||
onDropFiles([...e.dataTransfer.files])
|
||||
}
|
||||
|
||||
setDraggingOver(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
{...withClasses(['upload-region', draggingOver && 'dragging-over'])}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
{!draggingOver ? (
|
||||
<>
|
||||
<span>Trascina qui un PDF oppure usa il tasto sottostante</span>
|
||||
<InputFile accept="application/pdf" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div class="release-text">
|
||||
<i class="fa-solid fa-upload"></i>
|
||||
<span>Rilascia per iniziare il caricamento</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Progress = ({ value, max }) => {
|
||||
return (
|
||||
<div class="progress-bar">
|
||||
<div class="indicator" style={{ width: Math.floor((value / max) * 100) + '%' }}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CancellableUpload = ({ uploadedSize, totalSize, onCancel }) => {
|
||||
return (
|
||||
<>
|
||||
<div class="progress-bytes">
|
||||
{formatFileSize(uploadedSize)} / {formatFileSize(totalSize)}
|
||||
</div>
|
||||
<div class="progress-and-action">
|
||||
<Progress value={uploadedSize} max={totalSize} />
|
||||
<button onClick={onCancel}>Annulla</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const UploadPopup = ({}) => {
|
||||
const [shown, setShown] = useState(false)
|
||||
|
||||
const descriptionTextareaProps = useAutosizeTextarea({ minRows: 2 })
|
||||
const [tags, setTags] = useState(['geometria-1', 'fortuna', 'frigerio', '2013/2014'])
|
||||
|
||||
const [doneUploading, setDoneUploading] = useState(false)
|
||||
|
||||
const hash = '59e514dd50c63051'
|
||||
|
||||
return (
|
||||
<>
|
||||
{shown && (
|
||||
<div class="upload-popup">
|
||||
<div class="popup">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
Carica la dispensa "<code>file.pdf</code>"
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<p>
|
||||
{/* TODO: */}
|
||||
Inserisci titolo, descrizione e tag per questa dispensa, una volta
|
||||
premuto <strong>Salva</strong> la dispensa verrà aggiunta
|
||||
inizialmente non sarà visibile nell'elenco degli appunti finché non
|
||||
verrà approvata da un moderatore
|
||||
</p>
|
||||
</div>
|
||||
<div class="form">
|
||||
<div class="label">Nome</div>
|
||||
<input type="text" placeholder="Nome" value="Mezzedimi" />
|
||||
<div class="label">Descrizione</div>
|
||||
<textarea placeholder="Descrizione..." {...descriptionTextareaProps}>
|
||||
Best dispensa di Geometria 1 ever written anche se non in LaTeX
|
||||
</textarea>
|
||||
<div class="label">Tags</div>
|
||||
<InputTags {...{ tags, setTags, availableTags }} />
|
||||
|
||||
{!doneUploading ? (
|
||||
<CancellableUpload
|
||||
uploadedSize={34.2 * 1024 ** 2}
|
||||
totalSize={45.6 * 1024 ** 2}
|
||||
/>
|
||||
) : (
|
||||
<div class="right">
|
||||
<div class="upload-message">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
<span title={hash}>File caricato</span>
|
||||
</div>
|
||||
<button>Annulla</button>
|
||||
<button class="primary">Salva</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const InputFile = ({ accept, onFile }) => {
|
||||
const inputFileRef = useRef()
|
||||
|
||||
const [file, setFile] = useState(null)
|
||||
|
||||
const onButtonClick = e => {
|
||||
inputFileRef.current.click()
|
||||
}
|
||||
|
||||
const onInputFile = e => {
|
||||
if (e.target.files.length === 1) {
|
||||
const f = e.target.files[0]
|
||||
|
||||
setFile(f)
|
||||
onFile?.(f)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="input-file">
|
||||
<input type="file" ref={inputFileRef} accept={accept} onInput={onInputFile} />
|
||||
<button onClick={onButtonClick}>Carica File</button>
|
||||
{file && <div class="file-name">{file.name}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TabellaApprovazioni = ({ pendingApprovazioni }) => {
|
||||
return (
|
||||
<div class="table approvazioni">
|
||||
<div class="download header"></div>
|
||||
<div class="hash header">PDF (Hash)</div>
|
||||
<div class="title header">Dispensa</div>
|
||||
<div class="owner header">Proprietario</div>
|
||||
<div class="actions header">Azioni</div>
|
||||
|
||||
{intersperse(
|
||||
pendingApprovazioni.map(({ id, title, owner, hash }) => (
|
||||
<>
|
||||
<div class="download">
|
||||
<a class="button icon" href={`/appunti/files/${hash}`}>
|
||||
<i class="fa-solid fa-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hash">
|
||||
<code>{hash}</code>
|
||||
</div>
|
||||
<div class="title">
|
||||
<a href={`/appunti/${id}`} title={id}>
|
||||
<i class="fa-solid fa-book"></i> {title}
|
||||
</a>
|
||||
</div>
|
||||
<div class="owner">
|
||||
<a href={`/u/${owner}`}>@{owner}</a>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="icon">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</button>
|
||||
<button class="icon">
|
||||
<i class="fa-solid fa-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)),
|
||||
<div class="separator"></div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const App = ({}) => {
|
||||
const descriptionTextareaProps = useAutosizeTextarea({ minRows: 2 })
|
||||
|
||||
const [tags, setTags] = useState(['geometria-1', 'fortuna', 'frigerio', '2013/2014'])
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadRegion />
|
||||
<UploadPopup />
|
||||
|
||||
<div class="flex col gap-1 fill-h">
|
||||
<h1>Le tue dispense</h1>
|
||||
<div class="table dispense">
|
||||
<div class="edit header"></div>
|
||||
<div class="name header">Nome</div>
|
||||
<div class="tags header">Tags</div>
|
||||
<div class="status header">Stato</div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name">Appunti di Geometria 1</div>
|
||||
<div class="tags">
|
||||
<div class="tag">geometria-1</div>
|
||||
<div class="tag">prof-1</div>
|
||||
<div class="tag">2016/2017</div>
|
||||
</div>
|
||||
<div class="status approved">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="expanded">
|
||||
<div class="edit-close">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="edit-container">
|
||||
<div class="header">
|
||||
<a href="/appunti/6f82dca3d83b475c">
|
||||
<span class="title">
|
||||
<i class="fa-solid fa-book"></i> Mezzedimi
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form">
|
||||
<div class="label">Nome</div>
|
||||
<input type="text" placeholder="Nome" value="Mezzedimi" />
|
||||
<div class="label">Descrizione</div>
|
||||
<textarea
|
||||
placeholder="Descrizione..."
|
||||
{...descriptionTextareaProps}
|
||||
>
|
||||
Best dispensa di Geometria 1 ever written anche se non in LaTeX
|
||||
</textarea>
|
||||
<div class="label">Tags</div>
|
||||
<InputTags {...{ tags, setTags, availableTags }} />
|
||||
</div>
|
||||
<p>
|
||||
Puoi anche caricare una nuova versione del PDF ma ricorda che se
|
||||
carichi un file non precedentemente approvato inizialmente
|
||||
scomparirà dall'elenco principale in attesa di apporvazione da parte
|
||||
di un moderatore.
|
||||
</p>
|
||||
<div class="form">
|
||||
<div class="label">Stato</div>
|
||||
<div class="stato-approvazione approved">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
Approvata
|
||||
</div>
|
||||
|
||||
<div class="label">Cambia PDF</div>
|
||||
<InputFile accept="application/pdf" />
|
||||
|
||||
<div class="right">
|
||||
<button class="primary">Salva</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name pending">Appunti di Geometria 2</div>
|
||||
<div class="tags">
|
||||
<div class="tag">geometria-2</div>
|
||||
<div class="tag">prof-2</div>
|
||||
<div class="tag">2017/2018</div>
|
||||
<div class="tag">tanti-tag-1</div>
|
||||
<div class="tag">tanti-tag-2</div>
|
||||
<div class="tag">tanti-tag-3</div>
|
||||
<div class="tag">tanti-tag-4</div>
|
||||
<div class="tag">tanti-tag-5</div>
|
||||
<div class="tag">tanti-tag-6</div>
|
||||
</div>
|
||||
<div class="status pending" title="In attesa di approvazione...">
|
||||
<i class="fas fa-hourglass"></i>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="edit">
|
||||
<button class="icon flat">
|
||||
<i class="fa-solid fa-angle-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="name rejected">F1Le SuP3R LeGaLe</div>
|
||||
<div class="tags">
|
||||
<div class="tag">foo</div>
|
||||
<div class="tag">bar</div>
|
||||
</div>
|
||||
<div class="status rejected">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex col gap-1 fill-h">
|
||||
<h1>PDF da Approvare</h1>
|
||||
<TabellaApprovazioni
|
||||
pendingApprovazioni={[
|
||||
{
|
||||
id: '59e514dd50c63051',
|
||||
title: 'GAAL',
|
||||
owner: 'mezzedimi',
|
||||
hash: '2c8d593c6be289ab',
|
||||
},
|
||||
{
|
||||
id: '4c52d9726c438f3d',
|
||||
title: 'Dispensa 1',
|
||||
owner: 'persona-1',
|
||||
hash: '346ba392a3a1eb86',
|
||||
},
|
||||
{
|
||||
id: 'eecb96f04e319c4c',
|
||||
title: 'Dispensa 2',
|
||||
owner: 'persona-1',
|
||||
hash: '74f9652c28f82e7f',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render(<App />, document.querySelector('#app'))
|
||||
@@ -0,0 +1,17 @@
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} bytes`
|
||||
}
|
||||
if (bytes < 1024 ** 2) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`
|
||||
}
|
||||
if (bytes < 1024 ** 3) {
|
||||
return `${(bytes / 1024 ** 2).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
return `${(bytes / 1024 ** 3).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
export function intersperse(list, separator) {
|
||||
return list.flatMap((el, i) => (i === 0 ? [el] : [separator, el]))
|
||||
}
|
||||
+363
-39
@@ -5,10 +5,12 @@
|
||||
--bg: #eaeaea;
|
||||
--fg: #333;
|
||||
|
||||
--fg-lighter: #444;
|
||||
|
||||
--bg-lighter: #f0f0f0;
|
||||
|
||||
--bg-dark: hsl(220, 5%, 93%);
|
||||
--bg-darker: hsl(220, 5%, 90%);
|
||||
--bg-dark: #ecedee;
|
||||
--bg-darker: #e4e5e7;
|
||||
--bg-darker-2: #d5d5d5;
|
||||
--bg-darker-2-1: #c8c8c8;
|
||||
--bg-darker-3: #c0c0c0;
|
||||
@@ -36,6 +38,8 @@
|
||||
--accent-2: #4eaa59;
|
||||
--accent-2-darker: #2e974c;
|
||||
--accent-2-darkest: #002d0d;
|
||||
|
||||
--accent-mix-bg: #dae4db;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -420,6 +424,11 @@ hr {
|
||||
background-color: var(--bg-darker-2);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0.5rem 0;
|
||||
|
||||
@@ -445,6 +454,74 @@ p.center {
|
||||
|
||||
/* Controls */
|
||||
|
||||
.progress-bar {
|
||||
background: var(--bg-darker-2-1);
|
||||
color: var(--fg);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-darker-2);
|
||||
box-shadow: 0 0 8px 0 #00000020, inset 0 0 8px 0 #00000030;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.progress-bar .indicator {
|
||||
border-radius: 4px;
|
||||
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, var(--accent-1), var(--accent-2));
|
||||
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
animation: test-progressbar 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes test-progressbar {
|
||||
0% {
|
||||
width: 0%;
|
||||
}
|
||||
100% {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar .indicator::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
/* background-size: 50px 50px;
|
||||
|
||||
--highlight: rgba(255, 255, 255, 0.1);
|
||||
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
transparent 0%,
|
||||
var(--highlight) 5%,
|
||||
var(--highlight) 25%,
|
||||
transparent 30%,
|
||||
transparent 50%,
|
||||
var(--highlight) 55%,
|
||||
var(--highlight) 75%,
|
||||
transparent 80%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
animation: move 4s linear infinite; */
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 50px 50px;
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.button) {
|
||||
color: var(--accent-1-fg);
|
||||
font-weight: var(--font-weight-medium);
|
||||
@@ -464,7 +541,7 @@ a.button {
|
||||
|
||||
button,
|
||||
.button {
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
|
||||
font-family: var(--font-sf);
|
||||
font-weight: var(--font-weight-medium);
|
||||
@@ -488,6 +565,9 @@ button,
|
||||
box-shadow: 0 4px 8px 0 #00000022;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
@@ -496,17 +576,20 @@ button:hover,
|
||||
box-shadow: 0 4px 8px 0 #00000033;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
button.primary,
|
||||
.button.primary {
|
||||
border: 1px solid var(--accent-2-darker);
|
||||
background: var(--accent-2);
|
||||
color: var(--accent-2-darkest);
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
button.primary:hover,
|
||||
.button.primary:hover {
|
||||
background: var(--accent-2-lighter);
|
||||
}
|
||||
|
||||
button.icon {
|
||||
button.icon,
|
||||
.button.icon {
|
||||
padding: 0;
|
||||
width: 2rem;
|
||||
}
|
||||
@@ -556,8 +639,8 @@ textarea {
|
||||
border: none;
|
||||
background: none;
|
||||
|
||||
height: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
height: 2.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
|
||||
font-size: 17px;
|
||||
|
||||
@@ -574,15 +657,14 @@ textarea {
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: unset;
|
||||
height: auto;
|
||||
min-height: 2rem;
|
||||
|
||||
padding: 0.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
padding: 0.25rem 0.5rem;
|
||||
height: unset;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
input[type='password'] {
|
||||
@@ -602,6 +684,21 @@ input[type='password'].error {
|
||||
color: #311;
|
||||
}
|
||||
|
||||
/* Better File Input */
|
||||
|
||||
.input-file {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-file > input[type='file'] {
|
||||
position: fixed;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
/* Compound Controls */
|
||||
|
||||
.compound {
|
||||
@@ -873,16 +970,17 @@ form .field-set input {
|
||||
/* Appunti Condivisi */
|
||||
|
||||
.page-appunti-condivisi .main {
|
||||
padding: 0 1rem 6rem;
|
||||
max-width: 100ch;
|
||||
padding: 0 1rem inherit;
|
||||
max-width: 95ch;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-region {
|
||||
padding: 2rem;
|
||||
|
||||
width: 30rem;
|
||||
height: 30vh;
|
||||
|
||||
border: 2px dashed var(--bg-darker-4);
|
||||
border: 3px dashed var(--bg-darker-4);
|
||||
border-radius: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
@@ -892,6 +990,128 @@ form .field-set input {
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
transition: border-color 150ms ease-in-out, background-color 150ms ease-in-out;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-region.dragging-over {
|
||||
border-color: var(--accent-1);
|
||||
color: var(--accent-1-fg);
|
||||
|
||||
background: var(--accent-mix-bg);
|
||||
}
|
||||
|
||||
/* Serve altrimenti l'evento dropleave viene emesso anche quando si passa da ".upload-region" ad uno dei figli, così invece rendiamo tutti i figli trasparenti agli eventi del mouse */
|
||||
.page-appunti-condivisi .upload-region.dragging-over * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-region.dragging-over .release-text {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
font-size: 22px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup {
|
||||
z-index: 100;
|
||||
background-color: #00000066;
|
||||
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
/* backdrop-filter: blur(0.5rem); */
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
background: var(--bg-dark);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-darker-2);
|
||||
box-shadow: 0 0 32px #00000033;
|
||||
|
||||
min-width: 40ch;
|
||||
max-width: 60ch;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup .block {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup .form {
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .popup .form .label {
|
||||
min-width: 10ch;
|
||||
}
|
||||
|
||||
.form .progress-bytes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-message {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form .progress-and-action {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup > .header {
|
||||
background: var(--bg-darker);
|
||||
border-bottom: 1px solid var(--bg-darker-2);
|
||||
|
||||
color: var(--fg-lighter);
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup .title {
|
||||
font-size: 20px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .upload-popup .popup .title code {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi #app {
|
||||
@@ -900,64 +1120,80 @@ form .field-set input {
|
||||
align-items: center;
|
||||
|
||||
gap: 3rem;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table {
|
||||
.page-appunti-condivisi .table {
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr minmax(auto, 50ch) auto;
|
||||
|
||||
background: var(--bg-dark);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-darker-2);
|
||||
box-shadow: var(--shadow-1);
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .pending {
|
||||
.page-appunti-condivisi .table.dispense {
|
||||
grid-template-columns: auto 1fr minmax(auto, 50ch) auto;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .pending {
|
||||
color: royalblue;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .rejected {
|
||||
.page-appunti-condivisi .table.dispense .rejected {
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .header {
|
||||
.page-appunti-condivisi .table > .header {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
background: var(--bg-darker);
|
||||
|
||||
.page-appunti-condivisi .dispense-table .separator {
|
||||
grid-column: span 4;
|
||||
border-bottom: 1px solid var(--bg-darker-2);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit,
|
||||
.page-appunti-condivisi .dispense-table .name,
|
||||
.page-appunti-condivisi .dispense-table .tags,
|
||||
.page-appunti-condivisi .dispense-table .status {
|
||||
.page-appunti-condivisi .table .separator {
|
||||
grid-column: 1 / -1;
|
||||
border-bottom: 1px solid var(--bg-darker-2);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit,
|
||||
.page-appunti-condivisi .table.dispense .name,
|
||||
.page-appunti-condivisi .table.dispense .tags,
|
||||
.page-appunti-condivisi .table.dispense .status {
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .tags {
|
||||
.page-appunti-condivisi .table.dispense .tags {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .status {
|
||||
.page-appunti-condivisi .table.dispense .status {
|
||||
place-self: center;
|
||||
|
||||
padding: 1rem 1.25rem 1rem 2rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .expanded {
|
||||
.page-appunti-condivisi .table.dispense .expanded {
|
||||
grid-column: span 4;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-container {
|
||||
.page-appunti-condivisi .table.dispense .expanded p {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
@@ -965,20 +1201,85 @@ form .field-set input {
|
||||
padding: 1rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-close {
|
||||
.page-appunti-condivisi .table.dispense .edit-container > .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-container > .header code {
|
||||
padding: 0 0.125rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-container > .header .title {
|
||||
font-size: 24px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-container .stato-approvazione {
|
||||
font-weight: var(--font-weight-medium);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-container .stato-approvazione.approved {
|
||||
color: var(--accent-1);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.dispense .edit-close {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-form {
|
||||
.page-appunti-condivisi .table.approvazioni {
|
||||
grid-template-columns: auto auto 1fr auto auto;
|
||||
|
||||
width: 80ch;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.approvazioni .download,
|
||||
.page-appunti-condivisi .table.approvazioni .hash,
|
||||
.page-appunti-condivisi .table.approvazioni .title,
|
||||
.page-appunti-condivisi .table.approvazioni .owner,
|
||||
.page-appunti-condivisi .table.approvazioni .actions {
|
||||
padding: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.approvazioni .hash {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .table.approvazioni .actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .form {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5rem 1rem;
|
||||
gap: 1rem 2rem;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-form > .label {
|
||||
.page-appunti-condivisi .form button:not(.icon) {
|
||||
min-width: 6rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .form > .label {
|
||||
height: 2rem;
|
||||
|
||||
align-self: flex-start;
|
||||
@@ -989,18 +1290,22 @@ form .field-set input {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-form > textarea {
|
||||
.page-appunti-condivisi .form > textarea {
|
||||
resize: vertical;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .dispense-table .edit-form > .right {
|
||||
.page-appunti-condivisi .form > .fill {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .form > .right {
|
||||
grid-column: span 2;
|
||||
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
||||
padding-top: 0.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-appunti-condivisi .input-tags {
|
||||
@@ -1212,3 +1517,22 @@ table td {
|
||||
table tbody tr:hover {
|
||||
background: var(--bg-darker);
|
||||
}
|
||||
|
||||
/* Utils */
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex.col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.fill-h {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.phc.dm.unipi.it/phc/website/config"
|
||||
@@ -61,14 +62,15 @@ func routes(h handler.Service, r fiber.Router) {
|
||||
|
||||
staticConfig := fiber.Static{}
|
||||
if config.Mode == "development" {
|
||||
log.Printf("Disabling Cache-Control in development mode")
|
||||
|
||||
// if no "Cache-Control" is present the browser will cache heuristically (and we don't want that)
|
||||
r.Use(func(c *fiber.Ctx) error {
|
||||
c.Set("Cache-Control", "no-cache")
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
staticConfig.CacheDuration = -1
|
||||
staticConfig.MaxAge = -1
|
||||
staticConfig.CacheDuration = 1 * time.Millisecond
|
||||
}
|
||||
|
||||
r.Static("/", "./_frontend/out", staticConfig)
|
||||
|
||||
Reference in New Issue
Block a user