docpensieve 0.5.0-beta.2 → 0.5.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/docpensieve.js +2 -0
  2. package/package.json +5 -5
  3. package/src/commands/build.js +2 -0
  4. package/src/commands/dev.js +14 -3
  5. package/src/commands/init.js +14 -0
  6. package/src/commands/serve.js +5 -3
  7. package/src/server.js +48 -14
  8. package/starter/01-guide/02-first-site.md +4 -0
  9. package/starter/01-guide/06-deployment.md +30 -0
  10. package/starter/01-guide/07-migrate-to-beta.md +18 -2
  11. package/starter/02-components/01-card.mdx +25 -0
  12. package/starter/02-components/02-columns.mdx +12 -0
  13. package/starter/02-components/03-time-timer.mdx +19 -0
  14. package/starter/02-components/04-tooltip.mdx +9 -0
  15. package/starter/02-components/05-tree.mdx +13 -0
  16. package/starter/02-components/06-scroll-to-top.mdx +10 -0
  17. package/starter/02-components/07-skill.mdx +16 -0
  18. package/starter/02-components/08-logo-icon.mdx +10 -0
  19. package/starter/02-components/09-for-theme.mdx +6 -0
  20. package/starter/02-components/10-cards.mdx +9 -0
  21. package/starter/02-components/11-menu.mdx +22 -0
  22. package/starter/02-components/12-admonition.mdx +9 -0
  23. package/starter/02-components/13-snippet.mdx +230 -0
  24. package/starter/02-components/14-hero.mdx +230 -0
  25. package/starter/02-components/15-calendar.mdx +130 -0
  26. package/starter/02-components/index.mdx +9 -3
  27. package/starter/02-components/snippet-example.js +32 -0
  28. package/starter/02-components/snippet-example.py +19 -0
  29. package/starter/03-reference/01-cli.md +13 -7
  30. package/starter/03-reference/02-configuration.md +52 -0
  31. package/starter/03-reference/05-api.md +315 -3
  32. package/starter/05-whats-new.md +57 -0
  33. package/types/commands/dev.d.ts +2 -1
  34. package/types/commands/serve.d.ts +2 -1
  35. package/types/server.d.ts +24 -1
@@ -0,0 +1,230 @@
1
+ ---
2
+ title: Snippet
3
+ description: A code block that names a real file of your project, read when the page is generated, so that the example cannot go stale.
4
+ ---
5
+
6
+ # Snippet
7
+
8
+ An example written by hand is a copy. It stops being true the day the code
9
+ changes, and nothing reports it: the page goes on showing something that
10
+ worked once. `Snippet` names the file instead, and the file is read when the
11
+ page is generated — there is no copy left to drift.
12
+
13
+ The idea comes from **Christophe Avonture**, who built it for his own site.
14
+
15
+ ## Naming a file
16
+
17
+ <Snippet source="./snippet-example.js" />
18
+
19
+ ```mdx
20
+ <Snippet source="./snippet-example.js" />
21
+ ```
22
+
23
+ The file name labels the block, and the highlighting is the one every code
24
+ block gets. A path beginning with `./` or `../` is read **from the page**, as
25
+ a link is; any other path is read **from the root of your project**, so that
26
+ `src/index.js` means the same file from every page.
27
+
28
+ ## Part of a file
29
+
30
+ Two ways, and they do not age the same.
31
+
32
+ A **range of lines** is quick to write and means line 12 of the file as it is
33
+ today:
34
+
35
+ <Snippet source="./snippet-example.js" lines="9-10" title="The words it drops" />
36
+
37
+ ```mdx
38
+ <Snippet source="./snippet-example.js" lines="9-10" title="The words it drops" />
39
+ ```
40
+
41
+ Write `lines="9"` for one line, `"9-10"` for a range, `"9-"` from there to the
42
+ end, `"-10"` from the start. It says what the file holds today, and only
43
+ today.
44
+
45
+ A **region** is a marker left in the source file itself, in a comment. It
46
+ follows the code when the code moves, which is the whole point of naming a
47
+ file rather than copying it:
48
+
49
+ <Snippet source="./snippet-example.js" region="guard" title="The guard" />
50
+
51
+ ```mdx
52
+ <Snippet source="./snippet-example.js" region="guard" title="The guard" />
53
+ ```
54
+
55
+ In the file, the marker is an ordinary comment — the one editors already fold
56
+ on:
57
+
58
+ ```js
59
+ // #region guard
60
+ if (!name) {
61
+ throw new Error('greet needs a name');
62
+ }
63
+ // #endregion
64
+ ```
65
+
66
+ The indentation the kept lines share is removed, so a region marked inside a
67
+ function does not come out shifted.
68
+
69
+ ## Folding a long file
70
+
71
+ `collapsed` puts the block inside a `details`. No script: a long file can sit
72
+ in a page without burying what follows it.
73
+
74
+ <Snippet source="./snippet-example.js" collapsed title="The whole file" />
75
+
76
+ ```mdx
77
+ <Snippet source="./snippet-example.js" collapsed title="The whole file" />
78
+ ```
79
+
80
+ ## Code you write yourself
81
+
82
+ Without `source`, the component frames the code written in the page. A fenced
83
+ block inside the tag is the dependable way to write it: it carries anything,
84
+ braces and `<` included, and no formatter touches what is inside it.
85
+
86
+ <Snippet title="greet.py">
87
+
88
+ ```python
89
+ def greet(name, greetings={'en': 'Hello'}):
90
+ return f"{greetings['en']}, {name}!"
91
+ ```
92
+
93
+ </Snippet>
94
+
95
+ ````mdx
96
+ <Snippet title="greet.py">
97
+
98
+ ```python
99
+ def greet(name, greetings={'en': 'Hello'}):
100
+ return f"{greetings['en']}, {name}!"
101
+ ```
102
+
103
+ </Snippet>
104
+ ````
105
+
106
+ The extension of the title names the language; `lang` says it where the title
107
+ does not, as for a shell:
108
+
109
+ <Snippet title="Build the site" lang="bash">
110
+
111
+ ```bash
112
+ npm install
113
+ npx docpensieve build
114
+ ```
115
+
116
+ </Snippet>
117
+
118
+ ````mdx
119
+ <Snippet title="Build the site" lang="bash">
120
+
121
+ ```bash
122
+ npm install
123
+ npx docpensieve build
124
+ ```
125
+
126
+ </Snippet>
127
+ ````
128
+
129
+ ### Code typed straight into the tag
130
+
131
+ Code written bare inside the tag is read too, from the page as you typed it —
132
+ indentation kept, nothing taken for Markdown:
133
+
134
+ ```mdx
135
+ <Snippet title="who_are_you.py">name = input("What's your name? ")</Snippet>
136
+ ```
137
+
138
+ Two things it cannot carry, both of which happen before any component sees the
139
+ code. A page is MDX, so `{`, `}` and `<` are read as MDX and stop the build on
140
+ a syntax error. And a formatter run over your pages reflows what it takes for
141
+ prose: two lines of shell come back as one. Where either applies — which is
142
+ most real code — use the fenced form above.
143
+
144
+ ## The mark of the language
145
+
146
+ A block carries the colour of its language — a rule above it, and a short mark
147
+ beside the file name. The colour is set as a variable on the frame, so the
148
+ theme decides what it paints and a project can set another.
149
+
150
+ The same function, in the other file this page reads. Nothing was said about
151
+ the language: the extension carried it, and with it the mark and the colour.
152
+
153
+ <Snippet source="./snippet-example.py" region="guard" title="The same guard, in Python" />
154
+
155
+ ```mdx
156
+ <Snippet source="./snippet-example.py" region="guard" title="The same guard, in Python" />
157
+ ```
158
+
159
+ A block the page writes says its language in the fence, and is marked the same
160
+ way:
161
+
162
+ <Snippet title="deploy.yml">
163
+
164
+ ```yaml
165
+ steps:
166
+ - run: npx docpensieve build
167
+ - run: npx docpensieve check
168
+ ```
169
+
170
+ </Snippet>
171
+
172
+ <Snippet title="A first build" lang="bash">
173
+
174
+ ```bash
175
+ npx docpensieve init my-site
176
+ npx docpensieve dev
177
+ ```
178
+
179
+ </Snippet>
180
+
181
+ Where a project installs an icon collection and names it, the drawing replaces
182
+ the mark:
183
+
184
+ ```js
185
+ snippetIcons: 'simple-icons',
186
+ ```
187
+
188
+ It is never a reason to fail. The icon is the tool's idea, not something your
189
+ page asked for: a collection you do not have, or a language it does not draw,
190
+ brings back the mark. `icon="simple-icons:docker"` on a single block wins over
191
+ both.
192
+
193
+ ### A language nobody marked
194
+
195
+ Twenty-one languages have a mark and a colour. Any other is shown without
196
+ one — the frame, the name, and the code.
197
+
198
+ Highlighting is a separate matter, and a wider one: the highlighter knows
199
+ several hundred languages, and a block in one it does not know comes out as
200
+ plain text rather than stopping the build. You can write a snippet in anything
201
+ your project is written in.
202
+
203
+ ## What it refuses
204
+
205
+ Nothing fails quietly, because a snippet that resolved to nothing would leave
206
+ an empty frame — and an empty frame looks deliberate.
207
+
208
+ | Written | What happens |
209
+ | ----------------------------------- | --------------------------------------------------------------- |
210
+ | a file that is not there | The build stops, saying which path was looked for. |
211
+ | a path leading outside the project | Refused: a page is content, and content does not read the disk. |
212
+ | a region no marker opens, or closes | The build stops, naming the region and the file. |
213
+ | a range that reads backwards | The build stops. |
214
+
215
+ ## Properties
216
+
217
+ | Property | Type | Effect |
218
+ | ----------- | --------- | --------------------------------------------------------------------------- |
219
+ | `source` | `string` | File to read. From the page with `./` or `../`, from the project otherwise. |
220
+ | `title` | `string` | Label above the block. Defaults to the name of the file. |
221
+ | `lines` | `string` | `8`, `8-14`, `8-` or `-14`. |
222
+ | `region` | `string` | Name marked by `#region` and `#endregion` in the file. |
223
+ | `lang` | `string` | Language for the highlighting, when the extension says the wrong thing. |
224
+ | `collapsed` | `boolean` | Folds the block into a `details`. |
225
+ | `icon` | `string` | An icon for this block, instead of the mark of its language. |
226
+ | `className` | `string` | Classes added to the frame. |
227
+ | `style` | `object` | Inline styles on the frame. |
228
+
229
+ The file this page reads is installed along with it, so every example above
230
+ works in your project exactly as it does here.
@@ -0,0 +1,230 @@
1
+ ---
2
+ title: Hero
3
+ description: The banner at the head of a page — a name, a sentence, and the way in.
4
+ ---
5
+
6
+ # Hero
7
+
8
+ A landing page opens on three things: what this is, what it does, and where to
9
+ start. `Hero` is the block that holds them, so that the head of a page is a
10
+ component rather than a `div` and a pile of classes.
11
+
12
+ It is **not a card**: no border, no panel. What a banner is made of is space, a
13
+ title large enough to be the first thing read, and one way in that stands out
14
+ from the rest. A frame exists for the page that asks for one, and it is not
15
+ what you get by default.
16
+
17
+ ## A banner
18
+
19
+ <Hero>
20
+
21
+ ## DocPensieve
22
+
23
+ Your Markdown and MDX files become a static site. No runtime to load, one
24
+ version per branch.
25
+
26
+ <HeroActions>[Get started](../guide/installation/) [See the components](./)</HeroActions>
27
+
28
+ </Hero>
29
+
30
+ ```mdx
31
+ <Hero>
32
+
33
+ # My project
34
+
35
+ What it does, in one sentence.
36
+
37
+ <HeroActions>[Get started](../guide/installation/) [Components](./)</HeroActions>
38
+
39
+ </Hero>
40
+ ```
41
+
42
+ Everything inside is ordinary Markdown: a heading, a paragraph, a row of
43
+ links. The component sets the width of the sentence, the spacing, and turns
44
+ the links into something to press — **the first one carries the accent**,
45
+ because a reader should see one way in rather than three.
46
+
47
+ ## Aligned to the start
48
+
49
+ `align="start"` leaves the banner where the text of the page is, which suits a
50
+ section that opens a chapter rather than a site.
51
+
52
+ <Hero align="start">
53
+
54
+ ### A section, not a site
55
+
56
+ The same block, reading from the left.
57
+
58
+ <HeroActions>[Read on](../guide/writing-pages/)</HeroActions>
59
+
60
+ </Hero>
61
+
62
+ ```mdx
63
+ <Hero align="start">
64
+
65
+ ## A section, not a site
66
+
67
+ The same block, reading from the left.
68
+
69
+ </Hero>
70
+ ```
71
+
72
+ Accepted values are `center`, the default, and `start`. Anything else stops the
73
+ build and lists the two — a banner silently ignoring its alignment would look
74
+ like a stylesheet that failed.
75
+
76
+ ## With something to show
77
+
78
+ Put a `HeroVisual` among the children and the banner becomes two columns — the
79
+ words on one side, the picture on the other. Nothing to declare: having a
80
+ visual is what makes it a split, which is the commonest shape of a landing
81
+ page and the reason a project puts a screenshot there.
82
+
83
+ <Hero>
84
+ <HeroText>
85
+
86
+ ### My project
87
+
88
+ What it does, in one sentence, with room to breathe.
89
+
90
+ <HeroActions>[Get started](../guide/installation/) [Components](./)</HeroActions>
91
+
92
+ </HeroText>
93
+ <HeroVisual>
94
+
95
+ ![A page of the site](./icons/banner.svg)
96
+
97
+ </HeroVisual>
98
+ </Hero>
99
+
100
+ ```mdx
101
+ <Hero>
102
+ <HeroText>
103
+
104
+ # My project
105
+
106
+ What it does, in one sentence, with room to breathe.
107
+
108
+ <HeroActions>[Get started](/guide/installation/) [Components](/components/)</HeroActions>
109
+
110
+ </HeroText>
111
+ <HeroVisual>
112
+
113
+ ![A page of the site](./screenshot.png)
114
+
115
+ </HeroVisual>
116
+ </Hero>
117
+ ```
118
+
119
+ `HeroText` holds the words, `HeroVisual` the picture. Below 48rem the two
120
+ columns become one, the picture following the words it illustrates.
121
+
122
+ ## A frame, if you want one
123
+
124
+ `frame` draws the panel a banner does not have by default — a border, and a
125
+ wash of its tone.
126
+
127
+ <Hero frame size="small">
128
+
129
+ ### Framed
130
+
131
+ For a banner that has to be told apart from the page around it.
132
+
133
+ </Hero>
134
+
135
+ ```mdx
136
+ <Hero frame>...</Hero>
137
+ ```
138
+
139
+ ## How much room it takes
140
+
141
+ `size` decides the air around the words, not the look: `small`, `medium` —
142
+ the default — `large`, and `full`, which takes the height of the screen and
143
+ centres its content in it.
144
+
145
+ <Hero size="small">
146
+
147
+ ### A small one
148
+
149
+ For a section that only needs announcing.
150
+
151
+ </Hero>
152
+
153
+ ```mdx
154
+ <Hero size="small">...</Hero>
155
+ <Hero size="large">...</Hero>
156
+ <Hero size="full">...</Hero>
157
+ ```
158
+
159
+ `full` measures the screen as the browser really shows it, so a phone whose
160
+ address bar comes and goes does not push the banner under it.
161
+
162
+ ## The tone it is lit with
163
+
164
+ `tone` names a colour the theme already carries — the same tones an admonition
165
+ takes, because a theme defines a colour per tone, not a colour per component.
166
+ A project that gives `attention` its own colour gives it to both at once.
167
+
168
+ <Hero tone="tip" align="start" size="small">
169
+
170
+ ### Lit by a tone
171
+
172
+ The wash behind the words, and the first way in, follow it.
173
+
174
+ <HeroActions>[Read on](../guide/writing-pages/)</HeroActions>
175
+
176
+ </Hero>
177
+
178
+ ```mdx
179
+ <Hero tone="tip">...</Hero>
180
+ ```
181
+
182
+ Accepted: `note` — the default, which is the accent of the theme — `info`,
183
+ `tip`, `attention` and `danger`. Anything else stops the build and lists them,
184
+ as an unknown size or alignment does: a banner silently ignoring what it was
185
+ asked would look like a stylesheet that failed.
186
+
187
+ ## On a page of your own
188
+
189
+ A banner belongs on a page that opens something: the home page of a version, a
190
+ landing page for a section. The `home` layout of the frontmatter is its usual
191
+ companion, since it drops the menu and the table of contents:
192
+
193
+ ```mdx
194
+ ---
195
+ title: My project
196
+ layout: home
197
+ ---
198
+
199
+ <Hero>
200
+
201
+ # My project
202
+
203
+ ...
204
+ ```
205
+
206
+ ## Properties
207
+
208
+ | Property | Type | Effect |
209
+ | ----------- | -------- | ---------------------------------- |
210
+ | `align` | `string` | `center` (the default) or `start`. |
211
+ | `className` | `string` | Classes added to the banner. |
212
+ | `style` | `object` | Inline styles on the banner. |
213
+
214
+ Its companions, each taking `className` and `style` as well:
215
+
216
+ | Component | What it holds |
217
+ | ------------- | -------------------------------------------------------------------- |
218
+ | `HeroActions` | The row of ways in. Its children are links, as Markdown writes them. |
219
+ | `HeroText` | The words, in a split. Not needed in a single column. |
220
+ | `HeroVisual` | The picture. Its presence is what makes the banner a split. |
221
+
222
+ **One primary action and one alternative** is the shape that reads — a row of
223
+ five buttons is a row of none.
224
+
225
+ The classes are `dp-banner*`, after what the element is rather than after the
226
+ component: a page that already writes a class of its own around a title keeps
227
+ whatever that class did, and adopting the component stays a decision.
228
+
229
+ A row of ways in is a row of choices — [Columns](./columns/) shows how to keep
230
+ several blocks the same height when the banner is followed by cards.
@@ -0,0 +1,130 @@
1
+ ---
2
+ title: Calendar
3
+ description: A month of dates and what happens on them — built at generation, navigable once the page has loaded its script.
4
+ ---
5
+
6
+ # Calendar
7
+
8
+ Dates read badly as a list. A release plan, a course schedule, the weeks of a
9
+ migration: what matters is where they fall, and how far apart. `Calendar`
10
+ draws the months, and puts each event on its day.
11
+
12
+ ## Days and what is on them
13
+
14
+ <Calendar>
15
+ <Event date="2026-10-14" label="Release 0.5" href="../whats-new/" />
16
+ <Event date="2026-10-14" label="Blog post" />
17
+ <Event date="2026-10-28" label="Workshop" />
18
+ </Calendar>
19
+
20
+ ```mdx
21
+ <Calendar>
22
+ <Event date="2026-10-14" label="Release 0.5" href="/whats-new/" />
23
+ <Event date="2026-10-14" label="Blog post" />
24
+ <Event date="2026-10-28" label="Workshop" />
25
+ </Calendar>
26
+ ```
27
+
28
+ A day is written `2026-10-14` — four digits, two, two. Several events may fall
29
+ on the same day, and they are drawn in the order they are written. `href` is
30
+ resolved like any other link of a page: relative to the page, or from the
31
+ version root.
32
+
33
+ The events are written in the page. Nothing is read from the frontmatter of
34
+ other pages: a calendar says what its author put in it, and says it where it
35
+ is read.
36
+
37
+ ## Several months
38
+
39
+ Every month between the first event and the last is drawn — including the ones
40
+ with nothing in them, because a gap is something a reader should see.
41
+
42
+ <Calendar>
43
+ <Event date="2026-11-18" label="Beta" />
44
+ <Event date="2027-01-06" label="Release" />
45
+ </Calendar>
46
+
47
+ ```mdx
48
+ <Calendar>
49
+ <Event date="2026-11-18" label="Beta" />
50
+ <Event date="2027-01-06" label="Release" />
51
+ </Calendar>
52
+ ```
53
+
54
+ Without JavaScript, the months are stacked one after the other and the whole
55
+ calendar is readable. With it, they fold into one, two arrows move between
56
+ them, and today is outlined. The arrows are **written hidden** and revealed by
57
+ the script, so they never appear without the code that makes them work.
58
+
59
+ That script is the client file of the version: a couple of kilobytes, loaded
60
+ only by the pages that hold a calendar, and shared with any other behaviour
61
+ the version uses. See [copyCode](../reference/configuration/) for what that
62
+ file is.
63
+
64
+ ## The small model
65
+
66
+ `compact` draws the grid small and writes the events out under it, at every
67
+ width — beside a paragraph, or in a column of its own.
68
+
69
+ **A narrow screen takes this model whatever the page asked for.** Seven columns
70
+ of full cells on a phone give each day a few millimetres, and a label written
71
+ in one is a word per line. Both forms are in the page and the stylesheet shows
72
+ one, as the menu of the header is written twice: no script decides it, and
73
+ nothing is read twice by a screen reader.
74
+
75
+ <Calendar compact>
76
+ <Event date="2026-10-14" label="Release 0.5" href="../whats-new/" />
77
+ <Event date="2026-10-14" label="Blog post" />
78
+ <Event date="2026-10-28" label="Workshop" />
79
+ </Calendar>
80
+
81
+ ```mdx
82
+ <Calendar compact>
83
+ <Event date="2026-10-14" label="Release 0.5" />
84
+ <Event date="2026-10-28" label="Workshop" />
85
+ </Calendar>
86
+ ```
87
+
88
+ A day that holds something carries a dot, or the number of things it holds —
89
+ a day with three should say three rather than look like a day with one. The
90
+ list underneath is what says what they are: **nothing is only a colour**,
91
+ which no screen reader reads and no colour-blind reader sees.
92
+
93
+ ## The language of the page
94
+
95
+ Month and weekday names come from the language of the page, and so does the
96
+ name a screen reader announces. A French page says `octobre`, an English one
97
+ `October` — nothing to declare.
98
+
99
+ ## What it refuses
100
+
101
+ | Written | What happens |
102
+ | ----------------------------- | ------------------------------------------------------------------ |
103
+ | a calendar with no event | The build stops: an empty calendar shows nothing. |
104
+ | `2026-10` or `14/10/2026` | The build stops, saying how a day is written. |
105
+ | `2026-09-31` | Refused: September has thirty days, so this is a typo, not a date. |
106
+ | an event without a label | The build stops, naming its day. |
107
+ | an `Event` outside a calendar | The build stops: nothing would know which month to draw it in. |
108
+ | anything else inside | The build stops: a calendar holds events. |
109
+
110
+ ## Properties
111
+
112
+ `Calendar`:
113
+
114
+ | Property | Type | Effect |
115
+ | ----------- | --------- | ---------------------------------------------------------------- |
116
+ | `label` | `string` | Name announced to a screen reader. Default: `Calendar`. |
117
+ | `compact` | `boolean` | The small model at every width. A narrow screen takes it anyway. |
118
+ | `className` | `string` | Classes added to the calendar. |
119
+ | `style` | `object` | Inline styles on the calendar. |
120
+
121
+ `Event`:
122
+
123
+ | Property | Type | Effect |
124
+ | -------- | -------- | ---------------------------------------------- |
125
+ | `date` | `string` | The day, `2026-10-14`. Required. |
126
+ | `label` | `string` | What happens that day. Required. |
127
+ | `href` | `string` | Where the event leads. Resolved like any link. |
128
+
129
+ Writing a block twice and showing one is how this site handles a narrow screen
130
+ throughout — [Columns](./columns/) makes the same choice.
@@ -67,9 +67,15 @@ changes the examples too.
67
67
 
68
68
  ## Without JavaScript
69
69
 
70
- No component loads a script. Whatever needs interaction therefore goes
71
- through native elements or through CSS: `details` for expanding, a link for
72
- moving, `animation-timeline` for appearing on scroll.
70
+ No component loads a script but one: `Calendar`, whose arrows between months
71
+ come from the client file of the version — and only on the pages that hold a
72
+ calendar. Whatever else needs interaction goes through native elements or
73
+ through CSS: `details` for expanding, a link for moving, `animation-timeline`
74
+ for appearing on scroll.
75
+
76
+ The site itself can add a behaviour to a page — `copyCode` puts a copy button
77
+ on each block of code — but that is a field you turn on, it lands in one file
78
+ shared by the version, and only pages that use it load it.
73
79
 
74
80
  When the browser does not know one of these properties yet, the component stays
75
81
  usable in its simplest state — visible, full, expanded. None of them waits for
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A file the Snippet page reads, rather than quoting.
3
+ *
4
+ * It is installed along with the documentation, so the examples on that page
5
+ * work in your project exactly as they do on this site. Change a line here
6
+ * and the page changes with it — which is the whole point of the component.
7
+ */
8
+
9
+ /** Words a slug drops, so that titles do not all begin alike. */
10
+ const SKIPPED = new Set(['a', 'an', 'the', 'of', 'to']);
11
+
12
+ /**
13
+ * Turns the title of a page into the last segment of its address.
14
+ *
15
+ * @param {string} title Title, as the frontmatter gives it.
16
+ * @returns {string} A slug: lower case, accents dropped, words joined by a dash.
17
+ */
18
+ export function slugOf(title) {
19
+ // #region guard
20
+ if (typeof title !== 'string' || title.trim() === '') {
21
+ throw new TypeError('slugOf needs a title');
22
+ }
23
+ // #endregion
24
+
25
+ return title
26
+ .normalize('NFD')
27
+ .replace(/\p{M}/gu, '')
28
+ .toLowerCase()
29
+ .split(/[^a-z0-9]+/)
30
+ .filter((word) => word && !SKIPPED.has(word))
31
+ .join('-');
32
+ }
@@ -0,0 +1,19 @@
1
+ """A second file, in another language, so the page can show what changes.
2
+
3
+ Nothing here is read by the tool: it is only the source of an example. The
4
+ extension is what tells the block which language it holds, and therefore
5
+ which mark and which colour it carries.
6
+ """
7
+
8
+ SKIPPED = {"a", "an", "the", "of", "to"}
9
+
10
+
11
+ def slug_of(title: str) -> str:
12
+ """Turn the title of a page into the last segment of its address."""
13
+ # #region guard
14
+ if not isinstance(title, str) or not title.strip():
15
+ raise TypeError("slug_of needs a title")
16
+ # #endregion
17
+
18
+ words = "".join(c if c.isalnum() else " " for c in title.lower()).split()
19
+ return "-".join(w for w in words if w not in SKIPPED)