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;
|