thibaud frere commited on
Commit
0390c56
·
1 Parent(s): f9074bc

update doc

Browse files
app/astro.config.mjs CHANGED
@@ -8,7 +8,7 @@ import remarkFootnotes from 'remark-footnotes';
8
  import rehypeSlug from 'rehype-slug';
9
  import rehypeAutolinkHeadings from 'rehype-autolink-headings';
10
  import rehypeCitation from 'rehype-citation';
11
- import rehypeCodeCopyAndLabel from './plugins/rehype/code-copy-and-label.mjs';
12
  import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
13
  import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
14
  import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
@@ -56,7 +56,7 @@ export default defineConfig({
56
  }],
57
  rehypeReferencesAndFootnotes,
58
  rehypeRestoreAtInCode,
59
- rehypeCodeCopyAndLabel,
60
  rehypeWrapTables
61
  ]
62
  }
 
8
  import rehypeSlug from 'rehype-slug';
9
  import rehypeAutolinkHeadings from 'rehype-autolink-headings';
10
  import rehypeCitation from 'rehype-citation';
11
+ import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
12
  import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
13
  import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
14
  import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
 
56
  }],
57
  rehypeReferencesAndFootnotes,
58
  rehypeRestoreAtInCode,
59
+ rehypeCodeCopy,
60
  rehypeWrapTables
61
  ]
62
  }
app/plugins/rehype/{code-copy-and-label.mjs → code-copy.mjs} RENAMED
@@ -1,46 +1,14 @@
1
- // Minimal rehype plugin to wrap code blocks with a copy button and a language label
2
  // Exported as a standalone module to keep astro.config.mjs lean
3
- export default function rehypeCodeCopyAndLabel() {
4
  return (tree) => {
5
  // Walk the tree; lightweight visitor to find <pre><code>
6
  const visit = (node, parent) => {
7
  if (!node || typeof node !== 'object') return;
8
  const children = Array.isArray(node.children) ? node.children : [];
9
  if (node.tagName === 'pre' && children.some(c => c.tagName === 'code')) {
10
- // Find code child and guess language
11
  const code = children.find(c => c.tagName === 'code');
12
- const collectClasses = (val) => Array.isArray(val) ? val.map(String) : (typeof val === 'string' ? String(val).split(/\s+/) : []);
13
- const fromClass = (names) => {
14
- const hit = names.find((n) => /^language-/.test(String(n)));
15
- return hit ? String(hit).replace(/^language-/, '') : '';
16
- };
17
- const codeClasses = collectClasses(code?.properties?.className);
18
- const preClasses = collectClasses(node?.properties?.className);
19
- const candidates = [
20
- code?.properties?.['data-language'],
21
- fromClass(codeClasses),
22
- node?.properties?.['data-language'],
23
- fromClass(preClasses),
24
- ];
25
- let lang = candidates.find(Boolean) || '';
26
- const lower = String(lang).toLowerCase();
27
- const toExt = (s) => {
28
- switch (String(s).toLowerCase()) {
29
- case 'typescript': case 'ts': return 'ts';
30
- case 'tsx': return 'tsx';
31
- case 'javascript': case 'js': case 'node': return 'js';
32
- case 'jsx': return 'jsx';
33
- case 'python': case 'py': return 'py';
34
- case 'bash': case 'shell': case 'sh': return 'sh';
35
- case 'markdown': case 'md': return 'md';
36
- case 'yaml': case 'yml': return 'yml';
37
- case 'html': return 'html';
38
- case 'css': return 'css';
39
- case 'json': return 'json';
40
- default: return lower || '';
41
- }
42
- };
43
- const ext = toExt(lower);
44
  // Determine if single-line block: prefer Shiki lines, then text content
45
  const countLinesFromShiki = () => {
46
  const isLineEl = (el) => el && el.type === 'element' && el.tagName === 'span' && Array.isArray(el.properties?.className) && el.properties.className.includes('line');
@@ -85,16 +53,11 @@ export default function rehypeCodeCopyAndLabel() {
85
  node.__forceSingle = true;
86
  }
87
  }
88
- // Ensure CSS-only label works: set data-language on <code> and <pre>, and wrapper
89
- code.properties = code.properties || {};
90
- if (ext) code.properties['data-language'] = ext;
91
- node.properties = node.properties || {};
92
- if (ext) node.properties['data-language'] = ext;
93
  // Replace <pre> with wrapper div.code-card containing button + pre
94
  const wrapper = {
95
  type: 'element',
96
  tagName: 'div',
97
- properties: { className: ['code-card'].concat((isSingleLine || node.__forceSingle) ? ['no-copy'] : []), 'data-language': ext },
98
  children: (isSingleLine || node.__forceSingle) ? [ node ] : [
99
  {
100
  type: 'element',
@@ -127,3 +90,5 @@ export default function rehypeCodeCopyAndLabel() {
127
  }
128
 
129
 
 
 
 
1
+ // Minimal rehype plugin to wrap code blocks with a copy button
2
  // Exported as a standalone module to keep astro.config.mjs lean
3
+ export default function rehypeCodeCopy() {
4
  return (tree) => {
5
  // Walk the tree; lightweight visitor to find <pre><code>
6
  const visit = (node, parent) => {
7
  if (!node || typeof node !== 'object') return;
8
  const children = Array.isArray(node.children) ? node.children : [];
9
  if (node.tagName === 'pre' && children.some(c => c.tagName === 'code')) {
10
+ // Find code child
11
  const code = children.find(c => c.tagName === 'code');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // Determine if single-line block: prefer Shiki lines, then text content
13
  const countLinesFromShiki = () => {
14
  const isLineEl = (el) => el && el.type === 'element' && el.tagName === 'span' && Array.isArray(el.properties?.className) && el.properties.className.includes('line');
 
53
  node.__forceSingle = true;
54
  }
55
  }
 
 
 
 
 
56
  // Replace <pre> with wrapper div.code-card containing button + pre
57
  const wrapper = {
58
  type: 'element',
59
  tagName: 'div',
60
+ properties: { className: ['code-card'].concat((isSingleLine || node.__forceSingle) ? ['no-copy'] : []) },
61
  children: (isSingleLine || node.__forceSingle) ? [ node ] : [
62
  {
63
  type: 'element',
 
90
  }
91
 
92
 
93
+
94
+
app/src/content/article.mdx CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: "Bringing paper to life:\n A modern template for\n scientific writing
3
  "
4
- subtitle: "A modern, MDX-first research article template with math, citations and interactive figures."
5
- description: "A modern, MDX-first research article template with math, citations and interactive figures."
6
  authors:
7
  - name: "Thibaud Frere"
8
  url: "https://huggingface.co/tfrere"
 
1
  ---
2
  title: "Bringing paper to life:\n A modern template for\n scientific writing
3
  "
4
+ subtitle: "Ready‑to‑publish workflow so you can focus on ideas rather than infrastructure."
5
+ description: "Ready‑to‑publish workflow so you can focus on ideas rather than infrastructure."
6
  authors:
7
  - name: "Thibaud Frere"
8
  url: "https://huggingface.co/tfrere"
app/src/content/chapters/introduction.mdx CHANGED
@@ -1,9 +1,10 @@
1
  import Sidenote from "../../components/Sidenote.astro";
2
 
3
  <Sidenote>
4
- Welcome to this **single‑page** research article template. It helps you publish **clear**, **modern**, and **interactive** technical writing with **minimal setup**. Grounded in up to date good practices in web dev, it favors interactive explanations, clear notation, and inspectable examples over static snapshots.
 
 
5
 
6
- It offers a **ready‑to‑publish**, **all‑in‑one** workflow so you can focus on **ideas** rather than **infrastructure**.
7
  <Fragment slot="aside">
8
  Reading time: 20–25 minutes.
9
  </Fragment>
@@ -26,7 +27,7 @@ import Sidenote from "../../components/Sidenote.astro";
26
  <span className="tag">Dataviz color palettes</span>
27
  <span className="tag">Optimized images</span>
28
  <span className="tag">Lightweight bundle</span>
29
- <span className="tag">SEO-friendly</span>
30
  <span className="tag">Automatic build</span>
31
  <span className="tag">Automatic PDF export</span>
32
  <span className="tag">Dark theme</span>
@@ -38,26 +39,23 @@ import Sidenote from "../../components/Sidenote.astro";
38
  </Sidenote>
39
 
40
  ## Introduction
41
- The web enables what static PDFs can’t: **interactive diagrams**, **progressive notation**, and **exploratory views** that reveal how ideas behave. This template makes **interactive artifacts**—figures, math, code and **inspectable experiments**—first‑class alongside prose, so readers **build intuition** rather than skim results.
42
-
43
- If you write technical blogs or research notes, you’ll benefit from a **ready‑to‑publish workflow** with clear notation, optimized media, and **automatic builds**.
44
 
45
  ### Who is this for
46
 
47
- For people who publish web‑native, interactive writing with minimal setup:
48
-
49
- - Researchers/engineers with live figures.
50
- - Educators crafting explorable explanations.
51
 
52
- Not a CMS or a multipage blog—this is a focused, single‑page, MDX‑first workflow.
 
53
 
 
54
 
55
  ### Inspired by Distill
56
 
57
  <Sidenote>
58
- This project draws strong inspiration from [**Distill**](https://distill.pub) (2016–2021), which championed clear, web‑native scholarship and set a high standard for interactive scientific communication.
59
 
60
- To give you a sense of what inspired this template, here is a short, curated list of well‑designed and often interactive works from Distill:
61
 
62
  - [Growing Neural Cellular Automata](https://distill.pub/2020/growing-ca/)
63
  - [Activation Atlas](https://distill.pub/2019/activation-atlas/)
 
1
  import Sidenote from "../../components/Sidenote.astro";
2
 
3
  <Sidenote>
4
+ Welcome to this single‑page **research article template**. It helps you publish **clear**, **modern**, and **interactive technical writing** with **minimal setup**.
5
+
6
+ Grounded in up to date good practices in web dev, it favors **interactive explanations**, **clear notation**, and **inspectable examples** over static snapshots.
7
 
 
8
  <Fragment slot="aside">
9
  Reading time: 20–25 minutes.
10
  </Fragment>
 
27
  <span className="tag">Dataviz color palettes</span>
28
  <span className="tag">Optimized images</span>
29
  <span className="tag">Lightweight bundle</span>
30
+ <span className="tag">SEO friendly</span>
31
  <span className="tag">Automatic build</span>
32
  <span className="tag">Automatic PDF export</span>
33
  <span className="tag">Dark theme</span>
 
39
  </Sidenote>
40
 
41
  ## Introduction
42
+ The web offers what static PDFs can’t: **interactive diagrams**, **progressive notation**, and **exploratory views** that show how ideas behave. This template treats **interactive artifacts**—figures, math, code, and inspectable experiments—as **first‑class** alongside prose, helping readers **build intuition** instead of skimming results.
 
 
43
 
44
  ### Who is this for
45
 
46
+ Ideal for anyone creating **web‑native** and **interactive** content with **minimal setup**:
 
 
 
47
 
48
+ - For **scientists** writing modern webnative papers
49
+ - For **educators** building explorable lessons.
50
 
51
+ This is not a CMS or a multi‑page blog—it's a **focused**, **single‑page**, **MDX‑first** workflow.
52
 
53
  ### Inspired by Distill
54
 
55
  <Sidenote>
56
+ This project draws strong inspiration from [**Distill**](https://distill.pub) (2016–2021), which championed **clear**, **web‑native** scholarship and set a **high standard** for **interactive scientific communication**.
57
 
58
+ To give you a sense of what inspired this template, here is a short, curated list of **well‑designed** and often **interactive** works from Distill:
59
 
60
  - [Growing Neural Cellular Automata](https://distill.pub/2020/growing-ca/)
61
  - [Activation Atlas](https://distill.pub/2019/activation-atlas/)
app/src/pages/index.astro CHANGED
@@ -186,7 +186,7 @@ const licence = (articleFM as any)?.licence ?? (articleFM as any)?.license ?? (a
186
  </script>
187
 
188
  <script>
189
- // Delegate copy clicks for code blocks injected by rehypeCodeCopyAndLabel
190
  document.addEventListener('click', async (e) => {
191
  const target = e.target instanceof Element ? e.target : null;
192
  const btn = target ? target.closest('.code-copy') : null;
 
186
  </script>
187
 
188
  <script>
189
+ // Delegate copy clicks for code blocks injected by rehypeCodeCopy
190
  document.addEventListener('click', async (e) => {
191
  const target = e.target instanceof Element ? e.target : null;
192
  const btn = target ? target.closest('.code-copy') : null;
app/src/styles/components/_code.css CHANGED
@@ -174,9 +174,7 @@ section.content-grid pre code {
174
  } */
175
 
176
  /* Fallback if Shiki uses data-lang instead of data-language */
177
- .astro-code[data-lang]::after { content: attr(data-lang); }
178
-
179
- /* Normalize to extensions for common languages */
180
  .astro-code[data-language="typescript"]::after { content: "ts"; }
181
  .astro-code[data-language="tsx"]::after { content: "tsx"; }
182
  .astro-code[data-language="javascript"]::after,
@@ -191,7 +189,7 @@ section.content-grid pre code {
191
  .astro-code[data-language="yml"]::after { content: "yml"; }
192
  .astro-code[data-language="html"]::after { content: "html"; }
193
  .astro-code[data-language="css"]::after { content: "css"; }
194
- .astro-code[data-language="json"]::after { content: "json"; }
195
 
196
  /* In Accordions, keep same bottom-right placement */
197
  .accordion .astro-code::after { right: 0; bottom: 0; }
 
174
  } */
175
 
176
  /* Fallback if Shiki uses data-lang instead of data-language */
177
+ /* .astro-code[data-lang]::after { content: attr(data-lang); }
 
 
178
  .astro-code[data-language="typescript"]::after { content: "ts"; }
179
  .astro-code[data-language="tsx"]::after { content: "tsx"; }
180
  .astro-code[data-language="javascript"]::after,
 
189
  .astro-code[data-language="yml"]::after { content: "yml"; }
190
  .astro-code[data-language="html"]::after { content: "html"; }
191
  .astro-code[data-language="css"]::after { content: "css"; }
192
+ .astro-code[data-language="json"]::after { content: "json"; } */
193
 
194
  /* In Accordions, keep same bottom-right placement */
195
  .accordion .astro-code::after { right: 0; bottom: 0; }
app/src/styles/components/_tag.css CHANGED
@@ -4,10 +4,10 @@
4
  display: inline-flex;
5
  align-items: center;
6
  gap: 6px;
7
- padding: 10px 16px;
8
  font-size: 12px;
9
  line-height: 1;
10
- border-radius: 100px;
11
  background: var(--surface-bg);
12
  border: 1px solid var(--border-color);
13
  color: var(--text-color);
 
4
  display: inline-flex;
5
  align-items: center;
6
  gap: 6px;
7
+ padding: 8px 12px;
8
  font-size: 12px;
9
  line-height: 1;
10
+ border-radius: var(--button-radius);
11
  background: var(--surface-bg);
12
  border: 1px solid var(--border-color);
13
  color: var(--text-color);