Spaces:
Running
Running
thibaud frere
commited on
Commit
·
542395c
1
Parent(s):
a4f9298
add brotli compression
Browse files- Dockerfile +3 -0
- app/astro.config.mjs +7 -1
- app/package.json +0 -0
- app/src/components/Accordion.astro +143 -0
- app/src/content/article.mdx +2 -5
- app/src/content/chapters/available-blocks.mdx +39 -10
- nginx.conf +37 -0
Dockerfile
CHANGED
|
@@ -23,6 +23,9 @@ RUN npm run export:pdf -- --theme=light --wait=full
|
|
| 23 |
# Use an official Nginx runtime as the base image for serving the application
|
| 24 |
FROM nginx:alpine
|
| 25 |
|
|
|
|
|
|
|
|
|
|
| 26 |
# Copy the built application from the build stage
|
| 27 |
COPY --from=build /app/dist /usr/share/nginx/html
|
| 28 |
|
|
|
|
| 23 |
# Use an official Nginx runtime as the base image for serving the application
|
| 24 |
FROM nginx:alpine
|
| 25 |
|
| 26 |
+
# Install Brotli dynamic module for Nginx
|
| 27 |
+
RUN apk add --no-cache nginx-mod-http-brotli
|
| 28 |
+
|
| 29 |
# Copy the built application from the build stage
|
| 30 |
COPY --from=build /app/dist /usr/share/nginx/html
|
| 31 |
|
app/astro.config.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { defineConfig } from 'astro/config';
|
| 2 |
import mdx from '@astrojs/mdx';
|
| 3 |
import mermaid from 'astro-mermaid';
|
|
|
|
| 4 |
import remarkMath from 'remark-math';
|
| 5 |
import rehypeKatex from 'rehype-katex';
|
| 6 |
import remarkFootnotes from 'remark-footnotes';
|
|
@@ -11,7 +12,12 @@ import rehypeCitation from 'rehype-citation';
|
|
| 11 |
|
| 12 |
export default defineConfig({
|
| 13 |
output: 'static',
|
| 14 |
-
integrations: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
devToolbar: {
|
| 16 |
enabled: false
|
| 17 |
},
|
|
|
|
| 1 |
import { defineConfig } from 'astro/config';
|
| 2 |
import mdx from '@astrojs/mdx';
|
| 3 |
import mermaid from 'astro-mermaid';
|
| 4 |
+
import compressor from 'astro-compressor';
|
| 5 |
import remarkMath from 'remark-math';
|
| 6 |
import rehypeKatex from 'rehype-katex';
|
| 7 |
import remarkFootnotes from 'remark-footnotes';
|
|
|
|
| 12 |
|
| 13 |
export default defineConfig({
|
| 14 |
output: 'static',
|
| 15 |
+
integrations: [
|
| 16 |
+
mermaid({ theme: 'forest', autoTheme: true }),
|
| 17 |
+
mdx(),
|
| 18 |
+
// Precompress output with Brotli (preferred) and Gzip as fallback
|
| 19 |
+
compressor({ brotli: true, gzip: true })
|
| 20 |
+
],
|
| 21 |
devToolbar: {
|
| 22 |
enabled: false
|
| 23 |
},
|
app/package.json
CHANGED
|
Binary files a/app/package.json and b/app/package.json differ
|
|
|
app/src/components/Accordion.astro
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
const { title, open = false, class: className, ...props } = Astro.props;
|
| 3 |
+
const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
|
| 4 |
+
---
|
| 5 |
+
<details class={wrapperClass} open={open} {...props}>
|
| 6 |
+
<summary class="accordion__summary">
|
| 7 |
+
<span class="accordion__title">{title}</span>
|
| 8 |
+
<svg class="accordion__chevron" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
| 9 |
+
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
| 10 |
+
</svg>
|
| 11 |
+
</summary>
|
| 12 |
+
<div class="accordion__content-wrapper">
|
| 13 |
+
<div class="accordion__content">
|
| 14 |
+
<slot />
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
</details>
|
| 18 |
+
|
| 19 |
+
<script>
|
| 20 |
+
// Animate open/close by transitioning explicit height
|
| 21 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 22 |
+
const accordions = document.querySelectorAll('.accordion') as NodeListOf<HTMLDetailsElement>;
|
| 23 |
+
accordions.forEach((acc) => {
|
| 24 |
+
const summary = acc.querySelector('summary.accordion__summary') as HTMLElement | null;
|
| 25 |
+
const wrapper = acc.querySelector('.accordion__content-wrapper') as HTMLElement | null;
|
| 26 |
+
const content = acc.querySelector('.accordion__content') as HTMLElement | null;
|
| 27 |
+
if (!summary || !wrapper || !content) return;
|
| 28 |
+
|
| 29 |
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
| 30 |
+
const duration = reduceMotion ? 0 : 240;
|
| 31 |
+
|
| 32 |
+
// Initial state
|
| 33 |
+
wrapper.style.overflow = 'hidden';
|
| 34 |
+
wrapper.style.height = acc.open ? 'auto' : '0px';
|
| 35 |
+
|
| 36 |
+
const open = () => {
|
| 37 |
+
wrapper.style.height = '0px';
|
| 38 |
+
void wrapper.offsetHeight; // reflow
|
| 39 |
+
const target = content.scrollHeight;
|
| 40 |
+
wrapper.style.transition = `height ${duration}ms ease`;
|
| 41 |
+
wrapper.style.height = `${target}px`;
|
| 42 |
+
const onEnd = () => {
|
| 43 |
+
wrapper.style.transition = '';
|
| 44 |
+
wrapper.style.height = 'auto';
|
| 45 |
+
wrapper.removeEventListener('transitionend', onEnd);
|
| 46 |
+
};
|
| 47 |
+
wrapper.addEventListener('transitionend', onEnd);
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
const close = () => {
|
| 51 |
+
const start = content.scrollHeight;
|
| 52 |
+
wrapper.style.height = `${start}px`;
|
| 53 |
+
void wrapper.offsetHeight; // reflow
|
| 54 |
+
wrapper.style.transition = `height ${duration}ms ease`;
|
| 55 |
+
wrapper.style.height = '0px';
|
| 56 |
+
const onEnd = () => {
|
| 57 |
+
wrapper.style.transition = '';
|
| 58 |
+
wrapper.removeEventListener('transitionend', onEnd);
|
| 59 |
+
acc.removeAttribute('open');
|
| 60 |
+
};
|
| 61 |
+
wrapper.addEventListener('transitionend', onEnd);
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
summary.addEventListener('click', (e) => {
|
| 65 |
+
e.preventDefault();
|
| 66 |
+
if (acc.open) {
|
| 67 |
+
close();
|
| 68 |
+
} else {
|
| 69 |
+
acc.setAttribute('open', '');
|
| 70 |
+
open();
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
});
|
| 74 |
+
});
|
| 75 |
+
</script>
|
| 76 |
+
|
| 77 |
+
<style>
|
| 78 |
+
.accordion {
|
| 79 |
+
border: 1px solid var(--border-color);
|
| 80 |
+
border-radius: 10px;
|
| 81 |
+
background: var(--surface-bg);
|
| 82 |
+
transition: box-shadow 180ms ease, border-color 180ms ease;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.accordion[open] {
|
| 86 |
+
box-shadow: 0 6px 22px rgba(0,0,0,0.08);
|
| 87 |
+
border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.accordion__summary {
|
| 91 |
+
list-style: none;
|
| 92 |
+
display: flex;
|
| 93 |
+
align-items: center;
|
| 94 |
+
justify-content: space-between;
|
| 95 |
+
gap: var(--spacing-1);
|
| 96 |
+
padding: var(--spacing-1);
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
color: var(--text-color);
|
| 99 |
+
user-select: none;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/* Remove native marker */
|
| 103 |
+
.accordion__summary::-webkit-details-marker {
|
| 104 |
+
display: none;
|
| 105 |
+
}
|
| 106 |
+
.accordion__summary::marker {
|
| 107 |
+
content: "";
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.accordion__title {
|
| 111 |
+
font-weight: 600;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.accordion__chevron {
|
| 115 |
+
flex: 0 0 auto;
|
| 116 |
+
transition: transform 220ms ease;
|
| 117 |
+
opacity: .85;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.accordion[open] .accordion__chevron {
|
| 121 |
+
transform: rotate(180deg);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/* Animated expand/collapse using height (controlled in JS) */
|
| 125 |
+
.accordion__content-wrapper {
|
| 126 |
+
overflow: hidden;
|
| 127 |
+
height: 0px;
|
| 128 |
+
will-change: height;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.accordion__content {
|
| 132 |
+
padding: var(--spacing-1);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/* Focus styles for accessibility */
|
| 136 |
+
.accordion__summary:focus-visible {
|
| 137 |
+
outline: 2px solid var(--primary-color);
|
| 138 |
+
outline-offset: 3px;
|
| 139 |
+
border-radius: 8px;
|
| 140 |
+
}
|
| 141 |
+
</style>
|
| 142 |
+
|
| 143 |
+
|
app/src/content/article.mdx
CHANGED
|
@@ -60,13 +60,10 @@ import GettingStarted from "./chapters/getting-started.mdx";
|
|
| 60 |
<span className="tag">SEO Friendly</span>
|
| 61 |
<span className="tag">Mermaid diagrams</span>
|
| 62 |
<span className="tag">Lightweight bundle</span>
|
| 63 |
-
<span className="tag">Aside notes</span>
|
| 64 |
<span className="tag">Mobile friendly</span>
|
| 65 |
<span className="tag">Optimized images</span>
|
| 66 |
-
<span className="tag">
|
| 67 |
-
<span className="tag">
|
| 68 |
-
<span className="tag">To do: Accordion</span>
|
| 69 |
-
<span className="tag">To do: dataviz color palette generator</span>
|
| 70 |
</div>
|
| 71 |
<Fragment slot="aside">
|
| 72 |
If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/research-blog-template/discussions?status=open&type=discussion">Community tab</a>!
|
|
|
|
| 60 |
<span className="tag">SEO Friendly</span>
|
| 61 |
<span className="tag">Mermaid diagrams</span>
|
| 62 |
<span className="tag">Lightweight bundle</span>
|
|
|
|
| 63 |
<span className="tag">Mobile friendly</span>
|
| 64 |
<span className="tag">Optimized images</span>
|
| 65 |
+
<span className="tag">Automatic PDF export</span>
|
| 66 |
+
<span className="tag">Dataviz color palettes</span>
|
|
|
|
|
|
|
| 67 |
</div>
|
| 68 |
<Fragment slot="aside">
|
| 69 |
If you have questions or remarks open a discussion on the <a href="https://huggingface.co/spaces/tfrere/research-blog-template/discussions?status=open&type=discussion">Community tab</a>!
|
app/src/content/chapters/available-blocks.mdx
CHANGED
|
@@ -5,6 +5,7 @@ import HtmlFragment from '../../components/HtmlFragment.astro';
|
|
| 5 |
import Aside from '../../components/Aside.astro';
|
| 6 |
import Wide from '../../components/Wide.astro';
|
| 7 |
import FullBleed from '../../components/FullBleed.astro';
|
|
|
|
| 8 |
|
| 9 |
## Available blocks
|
| 10 |
|
|
@@ -18,6 +19,7 @@ All the following blocks are available in the article.mdx file. You can also cre
|
|
| 18 |
<a className="button" href="#mermaid-diagrams">Mermaid</a>
|
| 19 |
<a className="button" href="#citations-and-notes">Citations & notes</a>
|
| 20 |
<a className="button" href="#placement">Placement</a>
|
|
|
|
| 21 |
<a className="button" href="#minimal-table">Minimal table</a>
|
| 22 |
<a className="button" href="#audio">Audio</a>
|
| 23 |
<a className="button" href="#embeds">Embeds</a>
|
|
@@ -39,9 +41,7 @@ $x^2 + y^2 = z^2$
|
|
| 39 |
|
| 40 |
**Block**
|
| 41 |
|
| 42 |
-
|
| 43 |
-
\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V
|
| 44 |
-
$$
|
| 45 |
|
| 46 |
<small className="muted">Example</small>
|
| 47 |
```mdx
|
|
@@ -213,6 +213,39 @@ import FullBleed from '../components/FullBleed.astro'
|
|
| 213 |
```
|
| 214 |
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
### Minimal table
|
| 217 |
|
| 218 |
| Method | Score |
|
|
@@ -230,17 +263,13 @@ import FullBleed from '../components/FullBleed.astro'
|
|
| 230 |
|
| 231 |
### Audio
|
| 232 |
|
| 233 |
-
<audio controls src={audioDemo}
|
| 234 |
-
|
| 235 |
-
</audio>
|
| 236 |
-
|
| 237 |
<small className="muted">Example</small>
|
| 238 |
```mdx
|
| 239 |
import audioDemo from '../assets/audio/audio-example.wav'
|
| 240 |
|
| 241 |
-
<audio controls src={audioDemo}
|
| 242 |
-
Your browser does not support the audio element.
|
| 243 |
-
</audio>
|
| 244 |
```
|
| 245 |
|
| 246 |
|
|
|
|
| 5 |
import Aside from '../../components/Aside.astro';
|
| 6 |
import Wide from '../../components/Wide.astro';
|
| 7 |
import FullBleed from '../../components/FullBleed.astro';
|
| 8 |
+
import Accordion from '../../components/Accordion.astro';
|
| 9 |
|
| 10 |
## Available blocks
|
| 11 |
|
|
|
|
| 19 |
<a className="button" href="#mermaid-diagrams">Mermaid</a>
|
| 20 |
<a className="button" href="#citations-and-notes">Citations & notes</a>
|
| 21 |
<a className="button" href="#placement">Placement</a>
|
| 22 |
+
<a className="button" href="#accordion">Accordion</a>
|
| 23 |
<a className="button" href="#minimal-table">Minimal table</a>
|
| 24 |
<a className="button" href="#audio">Audio</a>
|
| 25 |
<a className="button" href="#embeds">Embeds</a>
|
|
|
|
| 41 |
|
| 42 |
**Block**
|
| 43 |
|
| 44 |
+
Affichage en bloc, voir l'exemple ci-dessous.
|
|
|
|
|
|
|
| 45 |
|
| 46 |
<small className="muted">Example</small>
|
| 47 |
```mdx
|
|
|
|
| 213 |
```
|
| 214 |
|
| 215 |
|
| 216 |
+
### Accordion
|
| 217 |
+
|
| 218 |
+
Accessible accordion based on `details/summary`. You can pass any children content.
|
| 219 |
+
|
| 220 |
+
<Accordion title="What can this accordion hold?" open>
|
| 221 |
+
<p>Text, lists, images, code blocks, etc.</p>
|
| 222 |
+
<ul>
|
| 223 |
+
<li>Item one</li>
|
| 224 |
+
<li>Item two</li>
|
| 225 |
+
</ul>
|
| 226 |
+
</Accordion>
|
| 227 |
+
|
| 228 |
+
<Accordion title="Closed by default">
|
| 229 |
+
<p>This one stays collapsed until the user clicks the summary.</p>
|
| 230 |
+
</Accordion>
|
| 231 |
+
|
| 232 |
+
<small className="muted">Example</small>
|
| 233 |
+
```mdx
|
| 234 |
+
import Accordion from '../components/Accordion.astro'
|
| 235 |
+
|
| 236 |
+
<Accordion title="Accordion title" open>
|
| 237 |
+
<p>Free content with <strong>markdown</strong> and MDX components.</p>
|
| 238 |
+
</Accordion>
|
| 239 |
+
|
| 240 |
+
<Accordion title="Another accordion">
|
| 241 |
+
<ul>
|
| 242 |
+
<li>Item A</li>
|
| 243 |
+
<li>Item B</li>
|
| 244 |
+
</ul>
|
| 245 |
+
</Accordion>
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
|
| 249 |
### Minimal table
|
| 250 |
|
| 251 |
| Method | Score |
|
|
|
|
| 263 |
|
| 264 |
### Audio
|
| 265 |
|
| 266 |
+
<audio controls src={audioDemo}/>
|
| 267 |
+
<br/>
|
|
|
|
|
|
|
| 268 |
<small className="muted">Example</small>
|
| 269 |
```mdx
|
| 270 |
import audioDemo from '../assets/audio/audio-example.wav'
|
| 271 |
|
| 272 |
+
<audio controls src={audioDemo}/>
|
|
|
|
|
|
|
| 273 |
```
|
| 274 |
|
| 275 |
|
nginx.conf
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
worker_processes auto;
|
| 2 |
pid /tmp/nginx.pid;
|
| 3 |
|
|
@@ -17,6 +20,40 @@ http {
|
|
| 17 |
sendfile on;
|
| 18 |
keepalive_timeout 65;
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
server {
|
| 21 |
listen 8080;
|
| 22 |
server_name localhost;
|
|
|
|
| 1 |
+
load_module modules/ngx_http_brotli_filter_module.so;
|
| 2 |
+
load_module modules/ngx_http_brotli_static_module.so;
|
| 3 |
+
|
| 4 |
worker_processes auto;
|
| 5 |
pid /tmp/nginx.pid;
|
| 6 |
|
|
|
|
| 20 |
sendfile on;
|
| 21 |
keepalive_timeout 65;
|
| 22 |
|
| 23 |
+
# Brotli (preferred)
|
| 24 |
+
brotli on;
|
| 25 |
+
brotli_comp_level 5;
|
| 26 |
+
brotli_static always;
|
| 27 |
+
brotli_min_length 256;
|
| 28 |
+
brotli_types
|
| 29 |
+
text/plain
|
| 30 |
+
text/css
|
| 31 |
+
text/javascript
|
| 32 |
+
application/javascript
|
| 33 |
+
application/json
|
| 34 |
+
application/xml
|
| 35 |
+
application/rss+xml
|
| 36 |
+
image/svg+xml
|
| 37 |
+
font/ttf
|
| 38 |
+
font/otf
|
| 39 |
+
font/woff
|
| 40 |
+
font/woff2;
|
| 41 |
+
|
| 42 |
+
# Gzip fallback
|
| 43 |
+
gzip on;
|
| 44 |
+
gzip_vary on;
|
| 45 |
+
gzip_proxied any;
|
| 46 |
+
gzip_comp_level 5;
|
| 47 |
+
gzip_min_length 256;
|
| 48 |
+
gzip_types
|
| 49 |
+
text/plain
|
| 50 |
+
text/css
|
| 51 |
+
application/javascript
|
| 52 |
+
application/json
|
| 53 |
+
application/xml
|
| 54 |
+
application/rss+xml
|
| 55 |
+
image/svg+xml;
|
| 56 |
+
|
| 57 |
server {
|
| 58 |
listen 8080;
|
| 59 |
server_name localhost;
|