diff --git a/_frontend/rollup.config.js b/_frontend/rollup.config.js
index 2291d86..d8ca62e 100644
--- a/_frontend/rollup.config.js
+++ b/_frontend/rollup.config.js
@@ -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(),
],
},
])
diff --git a/_frontend/src/appunti-condivisi.jsx b/_frontend/src/appunti-condivisi.jsx
deleted file mode 100644
index 0e5142c..0000000
--- a/_frontend/src/appunti-condivisi.jsx
+++ /dev/null
@@ -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 (
-
- )
-}
-
-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 (
- <>
-
- Trascina qui un PDF oppure usa il tasto sottostante
- Carica PDF
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Appunti di Geometria 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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.
-
-
-
-
-
-
-
-
-
-
-
-
-
Appunti di Geometria 2
-
-
-
-
-
-
-
-
-
-
-
-
-
F1Le SuP3R LeGaLe
-
-
-
-
-
- >
- )
-}
-
-render( , document.querySelector('#app'))
diff --git a/_frontend/src/appunti-condivisi/main.jsx b/_frontend/src/appunti-condivisi/main.jsx
new file mode 100644
index 0000000..e5fcb13
--- /dev/null
+++ b/_frontend/src/appunti-condivisi/main.jsx
@@ -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 (
+
+ )
+}
+
+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 (
+
+ {!draggingOver ? (
+ <>
+
Trascina qui un PDF oppure usa il tasto sottostante
+
+ >
+ ) : (
+ <>
+
+
+ Rilascia per iniziare il caricamento
+
+ >
+ )}
+
+ )
+}
+
+const Progress = ({ value, max }) => {
+ return (
+
+ )
+}
+
+const CancellableUpload = ({ uploadedSize, totalSize, onCancel }) => {
+ return (
+ <>
+
+ {formatFileSize(uploadedSize)} / {formatFileSize(totalSize)}
+
+
+ >
+ )
+}
+
+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 && (
+
+ )}
+ >
+ )
+}
+
+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 (
+
+ )
+}
+
+const TabellaApprovazioni = ({ pendingApprovazioni }) => {
+ return (
+
+
+
+
+
+
+
+ {intersperse(
+ pendingApprovazioni.map(({ id, title, owner, hash }) => (
+ <>
+
+
+ {hash}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )),
+
+ )}
+
+ )
+}
+
+const App = ({}) => {
+ const descriptionTextareaProps = useAutosizeTextarea({ minRows: 2 })
+
+ const [tags, setTags] = useState(['geometria-1', 'fortuna', 'frigerio', '2013/2014'])
+
+ return (
+ <>
+
+
+
+
+
Le tue dispense
+
+
+
+
+
+
+
+
+
+
+
+
Appunti di Geometria 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
Appunti di Geometria 2
+
+
+
+
+
+
+
+
+
+
+
+
+
F1Le SuP3R LeGaLe
+
+
+
+
+
+
+
+
PDF da Approvare
+
+
+ >
+ )
+}
+
+render( , document.querySelector('#app'))
diff --git a/_frontend/src/appunti-condivisi/util.js b/_frontend/src/appunti-condivisi/util.js
new file mode 100644
index 0000000..60a0c80
--- /dev/null
+++ b/_frontend/src/appunti-condivisi/util.js
@@ -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]))
+}
diff --git a/_public/style.css b/_public/style.css
index 0652002..de8a436 100644
--- a/_public/style.css
+++ b/_public/style.css
@@ -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);
+
+ border-bottom: 1px solid var(--bg-darker-2);
}
-.page-appunti-condivisi .dispense-table .separator {
- grid-column: span 4;
+.page-appunti-condivisi .table .separator {
+ grid-column: 1 / -1;
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.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 .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 .dispense-table .edit-form {
+.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%;
+}
diff --git a/server/fiber.go b/server/fiber.go
index 66af044..9526920 100644
--- a/server/fiber.go
+++ b/server/fiber.go
@@ -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)