@astrojs/starlight 0.4.1 → 0.5.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#313](https://github.com/withastro/starlight/pull/313) [`dc42569`](https://github.com/withastro/starlight/commit/dc42569bddfae2c48ea60c0dd5cc70643a129a68) Thanks [@delucis](https://github.com/delucis)! - Add a `not-content` CSS class that allows users to opt out of Starlight’s default content styling
8
+
9
+ - [#297](https://github.com/withastro/starlight/pull/297) [`fb15a9b`](https://github.com/withastro/starlight/commit/fb15a9b65252ac5fa32304096fbdb49ecdd6009b) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Improve `<Tabs>` component keyboard interactions
10
+
11
+ - [#303](https://github.com/withastro/starlight/pull/303) [`69b7d4c`](https://github.com/withastro/starlight/commit/69b7d4c23761a45dc2b9ea75c6c9c904a885ba5d) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Add new global `pagination` option defaulting to `true` to define whether or not the previous and next page links are shown in the footer. A page can override this setting or the link text and/or URL using the new `prev` and `next` frontmatter fields.
12
+
13
+ ### Patch Changes
14
+
15
+ - [#318](https://github.com/withastro/starlight/pull/318) [`5db3e6e`](https://github.com/withastro/starlight/commit/5db3e6ea2e5cb7d9552fc54567811358851fb533) Thanks [@delucis](https://github.com/delucis)! - Support relative paths in Starlight config for `customCSS` and `logo` paths
16
+
17
+ ## 0.4.2
18
+
19
+ ### Patch Changes
20
+
21
+ - [#308](https://github.com/withastro/starlight/pull/308) [`c3aa4c6`](https://github.com/withastro/starlight/commit/c3aa4c6aa18f7f6859ad1c0acc28f0da59a84760) Thanks [@delucis](https://github.com/delucis)! - Fix use of default monospace font stack
22
+
23
+ - [#286](https://github.com/withastro/starlight/pull/286) [`a2aedfc`](https://github.com/withastro/starlight/commit/a2aedfc7f9555b44f5f33aad7f4a98b207a11b47) Thanks [@mzaien](https://github.com/mzaien)! - Add Arabic UI translations
24
+
3
25
  ## 0.4.1
4
26
 
5
27
  ### Patch Changes
@@ -14,7 +14,10 @@ interface Props extends LocaleData {
14
14
  }
15
15
 
16
16
  const { entry, dir, lang, locale, sidebar } = Astro.props;
17
- const prevNextLinks = getPrevNextLinks(sidebar);
17
+ const prevNextLinks = getPrevNextLinks(sidebar, config.pagination, {
18
+ prev: entry.data.prev,
19
+ next: entry.data.next
20
+ });
18
21
  ---
19
22
 
20
23
  <footer>
@@ -2,112 +2,133 @@
2
2
 
3
3
  <style>
4
4
  .content
5
- :global(
5
+ > :global(
6
6
  :not(a, strong, em, del, span, input)
7
- + :not(a, strong, em, del, span, input)
7
+ + :not(a, strong, em, del, span, input, :where(.not-content *))
8
8
  ) {
9
9
  margin-top: 1.5rem;
10
10
  }
11
11
 
12
12
  /* Headings after non-headings have more spacing. */
13
- .content :global(:not(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6)) {
13
+ .content
14
+ :global(
15
+ :not(h1, h2, h3, h4, h5, h6)
16
+ + :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *))
17
+ ) {
14
18
  margin-top: 2.5rem;
15
19
  }
16
20
 
17
- .content :global(li + li),
18
- .content :global(dt + dt),
19
- .content :global(dt + dd),
20
- .content :global(dd + dd) {
21
+ .content :global(li + li:not(:where(.not-content *))),
22
+ .content :global(dt + dt:not(:where(.not-content *))),
23
+ .content :global(dt + dd:not(:where(.not-content *))),
24
+ .content :global(dd + dd:not(:where(.not-content *))) {
21
25
  margin-top: 0.25rem;
22
26
  }
23
27
 
24
28
  .content
25
29
  :global(
26
- li > :last-child:not(li, ul, ol):not(a, strong, em, del, span, input)
30
+ li
31
+ > :last-child:not(li, ul, ol):not(
32
+ a,
33
+ strong,
34
+ em,
35
+ del,
36
+ span,
37
+ input,
38
+ :where(.not-content *)
39
+ )
27
40
  ) {
28
41
  margin-bottom: 1.25rem;
29
42
  }
30
43
 
31
- .content :global(dt) {
44
+ .content :global(dt:not(:where(.not-content *))) {
32
45
  font-weight: 700;
33
46
  }
34
- .content :global(dd) {
47
+ .content :global(dd:not(:where(.not-content *))) {
35
48
  padding-inline-start: 1rem;
36
49
  }
37
50
 
38
- .content :global(:is(h1, h2, h3, h4, h5, h6)) {
51
+ .content :global(:is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *))) {
39
52
  color: var(--sl-color-white);
40
53
  line-height: var(--sl-line-height-headings);
41
54
  font-weight: 600;
42
55
  }
43
56
 
44
- .content :global(:is(img, picture, video, canvas, svg, iframe)) {
57
+ .content
58
+ :global(
59
+ :is(img, picture, video, canvas, svg, iframe):not(:where(.not-content *))
60
+ ) {
45
61
  display: block;
46
62
  max-width: 100%;
47
63
  height: auto;
48
64
  }
49
65
 
50
- .content :global(h1) {
66
+ .content :global(h1:not(:where(.not-content *))) {
51
67
  font-size: var(--sl-text-h1);
52
68
  }
53
- .content :global(h2) {
69
+ .content :global(h2:not(:where(.not-content *))) {
54
70
  font-size: var(--sl-text-h2);
55
71
  }
56
- .content :global(h3) {
72
+ .content :global(h3:not(:where(.not-content *))) {
57
73
  font-size: var(--sl-text-h3);
58
74
  }
59
- .content :global(h4) {
75
+ .content :global(h4:not(:where(.not-content *))) {
60
76
  font-size: var(--sl-text-h4);
61
77
  }
62
- .content :global(h5) {
78
+ .content :global(h5:not(:where(.not-content *))) {
63
79
  font-size: var(--sl-text-h5);
64
80
  }
65
- .content :global(h6) {
81
+ .content :global(h6:not(:where(.not-content *))) {
66
82
  font-size: var(--sl-text-h6);
67
83
  }
68
84
 
69
- .content :global(a) {
85
+ .content :global(a:not(:where(.not-content *))) {
70
86
  color: var(--sl-color-text-accent);
71
87
  }
72
- .content :global(a:hover) {
88
+ .content :global(a:hover:not(:where(.not-content *))) {
73
89
  color: var(--sl-color-white);
74
90
  }
75
91
 
76
- .content :global(code) {
92
+ .content :global(code:not(:where(.not-content *))) {
77
93
  background-color: var(--sl-color-bg-inline-code);
78
94
  margin-block: -0.125rem;
79
95
  padding: 0.125rem 0.375rem;
96
+ font-size: var(--sl-text-code-sm);
97
+ }
98
+ .content :global(:is(h1, h2, h3, h4, h5, h6) code) {
99
+ font-size: inherit;
80
100
  }
81
101
 
82
- .content :global(pre) {
102
+ .content :global(pre:not(:where(.not-content *))) {
83
103
  border: 1px solid var(--sl-color-gray-5);
84
104
  padding: 0.75rem 1rem;
85
105
  font-size: var(--sl-text-code);
86
106
  }
87
107
 
88
- .content :global(pre code) {
108
+ .content :global(pre code:not(:where(.not-content *))) {
89
109
  all: unset;
110
+ font-family: var(--__sl-font-mono);
90
111
  }
91
112
 
92
- .content :global(blockquote) {
113
+ .content :global(blockquote:not(:where(.not-content *))) {
93
114
  border-inline-start: 1px solid var(--sl-color-gray-5);
94
115
  padding-inline-start: 1rem;
95
116
  }
96
117
 
97
- .content :global(table) {
118
+ .content :global(table:not(:where(.not-content *))) {
98
119
  display: block;
99
120
  overflow: auto;
100
121
  border-collapse: collapse;
101
122
  }
102
- .content :global(tr:nth-child(2n)) {
123
+ .content :global(tr:nth-child(2n):not(:where(.not-content *))) {
103
124
  background-color: var(--sl-color-gray-7, var(--sl-color-gray-6));
104
125
  }
105
- .content :global(:is(th, td)) {
126
+ .content :global(:is(th, td):not(:where(.not-content *))) {
106
127
  border: 1px solid var(--sl-color-hairline);
107
128
  padding: 0.375rem 0.8125rem;
108
129
  }
109
130
 
110
- .content :global(hr) {
131
+ .content :global(hr:not(:where(.not-content *))) {
111
132
  border: 0;
112
133
  border-bottom: 1px solid var(--sl-color-hairline);
113
134
  }
package/index.ts CHANGED
@@ -6,7 +6,7 @@ import type {
6
6
  ViteUserConfig,
7
7
  } from 'astro';
8
8
  import { spawn } from 'node:child_process';
9
- import { dirname, relative } from 'node:path';
9
+ import { dirname, relative, resolve } from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { starlightAsides } from './integrations/asides';
12
12
  import { starlightSitemap } from './integrations/sitemap';
@@ -88,18 +88,20 @@ function vitePluginStarlightUserConfig(
88
88
  opts: StarlightConfig,
89
89
  { root }: AstroConfig
90
90
  ): NonNullable<ViteUserConfig['plugins']>[number] {
91
+ const resolveRelativeId = (id: string) =>
92
+ id.startsWith('.') ? resolve(fileURLToPath(root), id) : id;
91
93
  const modules = {
92
94
  'virtual:starlight/user-config': `export default ${JSON.stringify(opts)}`,
93
95
  'virtual:starlight/project-context': `export default ${JSON.stringify({
94
96
  root,
95
97
  })}`,
96
98
  'virtual:starlight/user-css': opts.customCss
97
- .map((id) => `import "${id}";`)
99
+ .map((id) => `import "${resolveRelativeId(id)}";`)
98
100
  .join(''),
99
101
  'virtual:starlight/user-images': opts.logo
100
102
  ? 'src' in opts.logo
101
- ? `import src from "${opts.logo.src}"; export const logos = { dark: src, light: src };`
102
- : `import dark from "${opts.logo.dark}"; import light from "${opts.logo.light}"; export const logos = { dark, light };`
103
+ ? `import src from "${resolveRelativeId(opts.logo.src)}"; export const logos = { dark: src, light: src };`
104
+ : `import dark from "${resolveRelativeId(opts.logo.dark)}"; import light from "${resolveRelativeId(opts.logo.light)}"; export const logos = { dark, light };`
103
105
  : 'export const logos = {};',
104
106
  };
105
107
  const resolutionMap = Object.fromEntries(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^18.15.11",
34
- "astro": "^2.7.3"
34
+ "astro": "2.7.0"
35
35
  },
36
36
  "dependencies": {
37
37
  "@astrojs/mdx": "^0.19.7",
@@ -40,6 +40,7 @@
40
40
  "@types/mdast": "^3.0.11",
41
41
  "bcp-47": "^2.1.0",
42
42
  "execa": "^7.1.1",
43
+ "hast-util-select": "^5.0.5",
43
44
  "hastscript": "^7.2.0",
44
45
  "pagefind": "^1.0.0-alpha.5",
45
46
  "rehype": "^12.0.1",
package/schema.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from 'astro/zod';
2
2
  import { HeadConfigSchema } from './schemas/head';
3
+ import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
3
4
  import { TableOfContentsSchema } from './schemas/tableOfContents';
4
5
  import { Icons } from './components/Icons';
5
6
  export { i18nSchema } from './schemas/i18n';
@@ -119,5 +120,16 @@ export function docsSchema() {
119
120
  * Overrides the `lastUpdated` global config or the date generated from the Git history.
120
121
  */
121
122
  lastUpdated: z.union([z.date(), z.boolean()]).optional(),
123
+
124
+ /**
125
+ * The previous navigation link configuration.
126
+ * Overrides the `pagination` global config or the link text and/or URL.
127
+ */
128
+ prev: PrevNextLinkConfigSchema(),
129
+ /**
130
+ * The next navigation link configuration.
131
+ * Overrides the `pagination` global config or the link text and/or URL.
132
+ */
133
+ next: PrevNextLinkConfigSchema(),
122
134
  });
123
135
  }
@@ -0,0 +1,20 @@
1
+ import { z } from 'astro/zod';
2
+
3
+ export const PrevNextLinkConfigSchema = () =>
4
+ z
5
+ .union([
6
+ z.boolean(),
7
+ z.string(),
8
+ z
9
+ .object({
10
+ /** The navigation link URL. */
11
+ link: z.string().optional(),
12
+ /** The navigation link text. */
13
+ label: z.string().optional(),
14
+ })
15
+ .strict(),
16
+ ])
17
+ .optional();
18
+
19
+ export type PrevNextLinkUserConfig = z.input<ReturnType<typeof PrevNextLinkConfigSchema>>;
20
+ export type PrevNextLinkConfig = z.output<ReturnType<typeof PrevNextLinkConfigSchema>>;
package/style/props.css CHANGED
@@ -89,8 +89,8 @@
89
89
  'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
90
90
  --sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
91
91
  'Liberation Mono', 'Courier New', monospace;
92
- --__sb-font: var(--sl-font, ''), var(--sl-font-system);
93
- --__sb-font-mono: var(--sl-font-mono, ''), var(--sl-font-system-mono);
92
+ --__sl-font: var(--sl-font, ''), var(--sl-font-system);
93
+ --__sl-font-mono: var(--sl-font-mono, ''), var(--sl-font-system-mono);
94
94
 
95
95
  /** Key layout values */
96
96
  --sl-nav-height: 3.5rem;
package/style/reset.css CHANGED
@@ -18,7 +18,7 @@ html[data-theme='light'] {
18
18
  }
19
19
 
20
20
  body {
21
- font-family: var(--__sb-font);
21
+ font-family: var(--__sl-font);
22
22
  line-height: var(--sl-line-height);
23
23
  -webkit-font-smoothing: antialiased;
24
24
  color: var(--sl-color-text);
@@ -41,3 +41,7 @@ h5,
41
41
  h6 {
42
42
  overflow-wrap: break-word;
43
43
  }
44
+
45
+ code {
46
+ font-family: var(--__sl-font-mono);
47
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "skipLink.label": "الذهاب للمحتوى",
3
+ "search.label": "البحث",
4
+ "search.shortcutLabel": "(إضغط / للبحث)",
5
+ "search.cancelLabel": "إلغاء",
6
+ "search.devWarning": "البحث متوفر فقط في نسخ الاطلاق. \n جرب بناء المشروع ومعاينته على جهازك",
7
+ "themeSelect.accessibleLabel": "إختر ثيم",
8
+ "themeSelect.dark": "ليلي",
9
+ "themeSelect.light": "نهاري",
10
+ "themeSelect.auto": "تلقائي",
11
+ "languageSelect.accessibleLabel": "إختر لغة",
12
+ "menuButton.accessibleLabel": "القائمة",
13
+ "sidebarNav.accessibleLabel": "الرئيسية",
14
+ "tableOfContents.onThisPage": "على هذه الصفحة",
15
+ "tableOfContents.overview": "ملخص",
16
+ "i18n.untranslatedContent": "هذا المحتوى لا يتوفر بلغتك بعد.",
17
+ "page.editLink": "عدل الصفحة",
18
+ "page.lastUpdated": "اخر تحديث:",
19
+ "page.previousLink": "السابق",
20
+ "page.nextLink": "التالي",
21
+ "404.text": "الصفحة غير موجودة. تأكد من الرابط أو ابحث بإستعمال شريط البحث."
22
+ }
@@ -10,11 +10,12 @@ import it from './it.json';
10
10
  import nl from './nl.json';
11
11
  import da from './da.json';
12
12
  import tr from './tr.json';
13
+ import ar from './ar.json';
13
14
 
14
15
  const { parse } = builtinI18nSchema();
15
16
 
16
17
  export default Object.fromEntries(
17
- Object.entries({ cs, en, es, de, ja, pt, fr, it, nl, da, tr }).map(([key, dict]) => [
18
+ Object.entries({ cs, en, es, de, ja, pt, fr, it, nl, da, tr, ar }).map(([key, dict]) => [
18
19
  key,
19
20
  parse(dict),
20
21
  ])
@@ -8,7 +8,7 @@ const { html, panels } = processPanels(panelHtml);
8
8
  <starlight-tabs>
9
9
  {
10
10
  panels && (
11
- <div class="tablist-wrapper">
11
+ <div class="tablist-wrapper not-content">
12
12
  <ul role="tablist">
13
13
  {panels.map(({ label, panelId, tabId }, idx) => (
14
14
  <li role="presentation" class="tab">
@@ -46,9 +46,6 @@ const { html, panels } = processPanels(panelHtml);
46
46
  padding: 0;
47
47
  }
48
48
 
49
- [role='tablist'] .tab + .tab {
50
- margin-top: 0;
51
- }
52
49
  .tab {
53
50
  margin-bottom: -2px;
54
51
  }
@@ -101,23 +98,20 @@ const { html, panels } = processPanels(panelHtml);
101
98
  const index = this.tabs.indexOf(e.currentTarget as any);
102
99
  // Work out which key the user is pressing and
103
100
  // Calculate the new tab's index where appropriate
104
- const dir =
101
+ const nextIndex =
105
102
  e.key === 'ArrowLeft'
106
103
  ? index - 1
107
104
  : e.key === 'ArrowRight'
108
105
  ? index + 1
109
- : e.key === 'ArrowDown'
110
- ? 'down'
106
+ : e.key === 'Home'
107
+ ? 0
108
+ : e.key === 'End'
109
+ ? this.tabs.length - 1
111
110
  : null;
112
- if (dir === null) return;
113
- // If the down key is pressed, move focus to the open panel,
114
- // otherwise switch to the adjacent tab
115
- if (dir === 'down') {
116
- e.preventDefault();
117
- this.panels[i]?.focus();
118
- } else if (this.tabs[dir]) {
111
+ if (nextIndex === null) return;
112
+ if (this.tabs[nextIndex]) {
119
113
  e.preventDefault();
120
- this.switchTab(this.tabs[dir], dir);
114
+ this.switchTab(this.tabs[nextIndex], nextIndex);
121
115
  }
122
116
  });
123
117
  });
@@ -1,3 +1,4 @@
1
+ import { select } from 'hast-util-select';
1
2
  import { rehype } from 'rehype';
2
3
  import { CONTINUE, SKIP, visit } from 'unist-util-visit';
3
4
 
@@ -15,6 +16,26 @@ declare module 'vfile' {
15
16
 
16
17
  export const TabItemTagname = 'starlight-tab-item';
17
18
 
19
+ // https://github.com/adobe/react-spectrum/blob/99ca82e87ba2d7fdd54f5b49326fd242320b4b51/packages/%40react-aria/focus/src/FocusScope.tsx#L256-L275
20
+ const focusableElementSelectors = [
21
+ 'input:not([disabled]):not([type=hidden])',
22
+ 'select:not([disabled])',
23
+ 'textarea:not([disabled])',
24
+ 'button:not([disabled])',
25
+ 'a[href]',
26
+ 'area[href]',
27
+ 'summary',
28
+ 'iframe',
29
+ 'object',
30
+ 'embed',
31
+ 'audio[controls]',
32
+ 'video[controls]',
33
+ '[contenteditable]',
34
+ '[tabindex]:not([disabled])',
35
+ ]
36
+ .map((selector) => `${selector}:not([hidden]):not([tabindex="-1"])`)
37
+ .join(',');
38
+
18
39
  let count = 0;
19
40
  const getIDs = () => {
20
41
  const id = count++;
@@ -51,7 +72,14 @@ const tabsProcessor = rehype()
51
72
  node.properties.id = ids.panelId;
52
73
  node.properties['aria-labelledby'] = ids.tabId;
53
74
  node.properties.role = 'tabpanel';
54
- node.properties.tabindex = -1;
75
+
76
+ const focusableChild = select(focusableElementSelectors, node);
77
+ // If the panel does not contain any focusable elements, include it in
78
+ // the tab sequence of the page.
79
+ if (!focusableChild) {
80
+ node.properties.tabindex = 0;
81
+ }
82
+
55
83
  // Hide all panels except the first
56
84
  // TODO: make initially visible tab configurable
57
85
  if (isFirst) {
@@ -9,6 +9,7 @@ import type {
9
9
  SidebarItem,
10
10
  SidebarLinkItem,
11
11
  } from './user-config';
12
+ import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
12
13
 
13
14
  export interface Link {
14
15
  type: 'link';
@@ -234,17 +235,63 @@ export function flattenSidebar(sidebar: SidebarEntry[]): Link[] {
234
235
  );
235
236
  }
236
237
 
237
- /** Get previous/next pages in the sidebar if there are any. */
238
- export function getPrevNextLinks(sidebar: SidebarEntry[]): {
238
+ /** Get previous/next pages in the sidebar or the ones from the frontmatter if any. */
239
+ export function getPrevNextLinks(
240
+ sidebar: SidebarEntry[],
241
+ paginationEnabled: boolean,
242
+ config: {
243
+ prev?: PrevNextLinkConfig;
244
+ next?: PrevNextLinkConfig;
245
+ }
246
+ ): {
239
247
  prev: Link | undefined;
240
248
  next: Link | undefined;
241
249
  } {
242
250
  const entries = flattenSidebar(sidebar);
243
251
  const currentIndex = entries.findIndex((entry) => entry.isCurrent);
244
- const prev = entries[currentIndex - 1];
245
- const next = currentIndex > -1 ? entries[currentIndex + 1] : undefined;
252
+ const prev = applyPrevNextLinkConfig(
253
+ entries[currentIndex - 1],
254
+ paginationEnabled,
255
+ config.prev
256
+ );
257
+ const next = applyPrevNextLinkConfig(
258
+ currentIndex > -1 ? entries[currentIndex + 1] : undefined,
259
+ paginationEnabled,
260
+ config.next
261
+ );
246
262
  return { prev, next };
247
263
  }
248
264
 
265
+ /** Apply a prev/next link config to a navigation link. */
266
+ function applyPrevNextLinkConfig(
267
+ link: Link | undefined,
268
+ paginationEnabled: boolean,
269
+ config: PrevNextLinkConfig | undefined
270
+ ): Link | undefined {
271
+ // Explicitly remove the link.
272
+ if (config === false) return undefined;
273
+ // Use the generated link if any.
274
+ else if (config === true) return link;
275
+ // If a link exists, update its label if needed.
276
+ else if (typeof config === 'string' && link) {
277
+ return { ...link, label: config };
278
+ } else if (typeof config === 'object') {
279
+ if (link) {
280
+ // If a link exists, update both its label and href if needed.
281
+ return {
282
+ ...link,
283
+ label: config.label ?? link.label,
284
+ href: config.link ?? link.href,
285
+ }
286
+ } else if (config.link && config.label) {
287
+ // If there is no link and the frontmatter contains both a URL and a label,
288
+ // create a new link.
289
+ return makeLink(config.link, config.label, config.link);
290
+ }
291
+ }
292
+ // Otherwise, if the global config is enabled, return the generated link if any.
293
+ return paginationEnabled ? link : undefined;
294
+ }
295
+
249
296
  /** Remove the extension from a path. */
250
297
  const stripExtension = (path: string) => path.replace(/\.\w+$/, '');
@@ -252,6 +252,14 @@ const UserConfigSchema = z.object({
252
252
  .default(false)
253
253
  .describe(
254
254
  'Define if the last update date should be visible in the page footer.'
255
+ ),
256
+
257
+ /** Define if the previous and next page links should be visible in the page footer. */
258
+ pagination: z
259
+ .boolean()
260
+ .default(true)
261
+ .describe(
262
+ 'Define if the previous and next page links should be visible in the page footer.'
255
263
  ),
256
264
  });
257
265