@astrojs/starlight 0.5.6 → 0.6.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.
Files changed (76) hide show
  1. package/404.astro +17 -17
  2. package/CHANGELOG.md +40 -2
  3. package/components/CallToAction.astro +32 -35
  4. package/components/ContentPanel.astro +18 -18
  5. package/components/EditLink.astro +23 -23
  6. package/components/FallbackContentNotice.astro +16 -18
  7. package/components/Footer.astro +28 -34
  8. package/components/HeadSEO.astro +69 -75
  9. package/components/Header.astro +47 -51
  10. package/components/Hero.astro +108 -121
  11. package/components/Icons.ts +73 -71
  12. package/components/LanguageSelect.astro +31 -31
  13. package/components/LastUpdated.astro +16 -18
  14. package/components/MarkdownContent.astro +105 -117
  15. package/components/MobileMenuToggle.astro +80 -80
  16. package/components/PrevNextLinks.astro +60 -60
  17. package/components/RightSidebar.astro +17 -17
  18. package/components/RightSidebarPanel.astro +41 -41
  19. package/components/Search.astro +293 -306
  20. package/components/Select.astro +64 -65
  21. package/components/Sidebar.astro +23 -23
  22. package/components/SidebarSublist.astro +96 -95
  23. package/components/SiteTitle.astro +65 -63
  24. package/components/SkipLink.astro +17 -17
  25. package/components/SocialIcons.astro +36 -34
  26. package/components/TableOfContents/MobileTableOfContents.astro +124 -124
  27. package/components/TableOfContents/TableOfContentsList.astro +64 -69
  28. package/components/TableOfContents/generateToC.ts +41 -43
  29. package/components/TableOfContents/starlight-toc.ts +84 -90
  30. package/components/TableOfContents.astro +8 -8
  31. package/components/ThemeProvider.astro +28 -32
  32. package/components/ThemeSelect.astro +66 -71
  33. package/global.d.ts +3 -3
  34. package/index.astro +1 -1
  35. package/index.ts +52 -59
  36. package/integrations/asides.ts +109 -111
  37. package/integrations/sitemap.ts +10 -13
  38. package/integrations/virtual-user-config.ts +37 -37
  39. package/layout/Page.astro +84 -72
  40. package/layout/PageFrame.astro +67 -69
  41. package/layout/TwoColumnContent.astro +40 -42
  42. package/package.json +2 -2
  43. package/schema.ts +126 -113
  44. package/schemas/favicon.ts +40 -0
  45. package/schemas/head.ts +12 -23
  46. package/schemas/i18n.ts +146 -160
  47. package/schemas/logo.ts +22 -22
  48. package/schemas/prevNextLink.ts +14 -14
  49. package/schemas/tableOfContents.ts +14 -18
  50. package/style/asides.css +27 -27
  51. package/style/props.css +168 -172
  52. package/style/reset.css +13 -13
  53. package/style/shiki.css +11 -11
  54. package/style/util.css +30 -30
  55. package/translations/fr.json +21 -21
  56. package/translations/index.ts +4 -4
  57. package/translations/it.json +20 -20
  58. package/translations/tr.json +1 -1
  59. package/translations/zh.json +0 -1
  60. package/user-components/Card.astro +50 -54
  61. package/user-components/CardGrid.astro +21 -21
  62. package/user-components/Icon.astro +19 -21
  63. package/user-components/TabItem.astro +3 -3
  64. package/user-components/Tabs.astro +113 -117
  65. package/user-components/rehype-tabs.ts +72 -72
  66. package/utils/base.ts +6 -6
  67. package/utils/git.ts +55 -55
  68. package/utils/head.ts +56 -54
  69. package/utils/i18n.ts +3 -3
  70. package/utils/localizedUrl.ts +25 -25
  71. package/utils/navigation.ts +225 -203
  72. package/utils/routing.ts +74 -85
  73. package/utils/slugs.ts +41 -51
  74. package/utils/translations.ts +26 -32
  75. package/utils/user-config.ts +278 -278
  76. package/virtual.d.ts +8 -8
@@ -1,28 +1,26 @@
1
1
  import { basename, dirname } from 'node:path';
2
2
  import config from 'virtual:starlight/user-config';
3
+ import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
3
4
  import { pathWithBase } from './base';
4
5
  import { pickLang } from './i18n';
5
- import { type Route, getLocaleRoutes, routes } from './routing';
6
+ import { getLocaleRoutes, type Route } from './routing';
6
7
  import { localeToLang, slugToPathname } from './slugs';
7
- import type {
8
- AutoSidebarGroup,
9
- SidebarItem,
10
- SidebarLinkItem,
11
- } from './user-config';
12
- import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
8
+ import type { AutoSidebarGroup, SidebarItem, SidebarLinkItem } from './user-config';
9
+
10
+ const DirKey = Symbol('DirKey');
13
11
 
14
12
  export interface Link {
15
- type: 'link';
16
- label: string;
17
- href: string;
18
- isCurrent: boolean;
13
+ type: 'link';
14
+ label: string;
15
+ href: string;
16
+ isCurrent: boolean;
19
17
  }
20
18
 
21
19
  interface Group {
22
- type: 'group';
23
- label: string;
24
- entries: (Link | Group)[];
25
- collapsed: boolean;
20
+ type: 'group';
21
+ label: string;
22
+ entries: (Link | Group)[];
23
+ collapsed: boolean;
26
24
  }
27
25
 
28
26
  export type SidebarEntry = Link | Group;
@@ -31,58 +29,70 @@ export type SidebarEntry = Link | Group;
31
29
  * A representation of the route structure. For each object entry:
32
30
  * if it’s a folder, the key is the directory name, and value is the directory
33
31
  * content; if it’s a route entry, the key is the last segment of the route, and value
34
- * is the entry’s full slug.
32
+ * is the full entry.
35
33
  */
36
34
  interface Dir {
37
- [item: string]: Dir | string;
35
+ [DirKey]: undefined;
36
+ [item: string]: Dir | Route;
37
+ }
38
+
39
+ /** Create a new directory object. */
40
+ function makeDir(): Dir {
41
+ const dir = {} as Dir;
42
+ // Add DirKey as a non-enumerable property so that `Object.entries(dir)` ignores it.
43
+ Object.defineProperty(dir, DirKey, { enumerable: false });
44
+ return dir;
45
+ }
46
+
47
+ /** Test if the passed object is a directory record. */
48
+ function isDir(data: Record<string, unknown>): data is Dir {
49
+ return DirKey in data;
38
50
  }
39
51
 
40
52
  /** Convert an item in a user’s sidebar config to a sidebar entry. */
41
53
  function configItemToEntry(
42
- item: SidebarItem,
43
- currentPathname: string,
44
- locale: string | undefined,
45
- routes: Route[]
54
+ item: SidebarItem,
55
+ currentPathname: string,
56
+ locale: string | undefined,
57
+ routes: Route[]
46
58
  ): SidebarEntry {
47
- if ('link' in item) {
48
- return linkFromConfig(item, locale, currentPathname);
49
- } else if ('autogenerate' in item) {
50
- return groupFromAutogenerateConfig(item, locale, routes, currentPathname);
51
- } else {
52
- return {
53
- type: 'group',
54
- label: pickLang(item.translations, localeToLang(locale)) || item.label,
55
- entries: item.items.map((i) =>
56
- configItemToEntry(i, currentPathname, locale, routes)
57
- ),
58
- collapsed: item.collapsed,
59
- };
60
- }
59
+ if ('link' in item) {
60
+ return linkFromConfig(item, locale, currentPathname);
61
+ } else if ('autogenerate' in item) {
62
+ return groupFromAutogenerateConfig(item, locale, routes, currentPathname);
63
+ } else {
64
+ return {
65
+ type: 'group',
66
+ label: pickLang(item.translations, localeToLang(locale)) || item.label,
67
+ entries: item.items.map((i) => configItemToEntry(i, currentPathname, locale, routes)),
68
+ collapsed: item.collapsed,
69
+ };
70
+ }
61
71
  }
62
72
 
63
73
  /** Autogenerate a group of links from a user’s sidebar config. */
64
74
  function groupFromAutogenerateConfig(
65
- item: AutoSidebarGroup,
66
- locale: string | undefined,
67
- routes: Route[],
68
- currentPathname: string
75
+ item: AutoSidebarGroup,
76
+ locale: string | undefined,
77
+ routes: Route[],
78
+ currentPathname: string
69
79
  ): Group {
70
- const { collapsed: subgroupCollapsed, directory } = item.autogenerate;
71
- const localeDir = locale ? locale + '/' + directory : directory;
72
- const dirDocs = routes.filter(
73
- (doc) =>
74
- // Match against `foo.md` or `foo/index.md`.
75
- stripExtension(doc.id) === localeDir ||
76
- // Match against `foo/anything/else.md`.
77
- doc.id.startsWith(localeDir + '/')
78
- );
79
- const tree = treeify(dirDocs, localeDir);
80
- return {
81
- type: 'group',
82
- label: pickLang(item.translations, localeToLang(locale)) || item.label,
83
- entries: sidebarFromDir(tree, currentPathname, locale, subgroupCollapsed ?? item.collapsed),
84
- collapsed: item.collapsed,
85
- };
80
+ const { collapsed: subgroupCollapsed, directory } = item.autogenerate;
81
+ const localeDir = locale ? locale + '/' + directory : directory;
82
+ const dirDocs = routes.filter(
83
+ (doc) =>
84
+ // Match against `foo.md` or `foo/index.md`.
85
+ stripExtension(doc.id) === localeDir ||
86
+ // Match against `foo/anything/else.md`.
87
+ doc.id.startsWith(localeDir + '/')
88
+ );
89
+ const tree = treeify(dirDocs, localeDir);
90
+ return {
91
+ type: 'group',
92
+ label: pickLang(item.translations, localeToLang(locale)) || item.label,
93
+ entries: sidebarFromDir(tree, currentPathname, locale, subgroupCollapsed ?? item.collapsed),
94
+ collapsed: item.collapsed,
95
+ };
86
96
  }
87
97
 
88
98
  /** Check if a string starts with one of `http://` or `https://`. */
@@ -90,207 +100,219 @@ const isAbsolute = (link: string) => /^https?:\/\//.test(link);
90
100
 
91
101
  /** Ensure the passed path starts and ends with trailing slashes. */
92
102
  function ensureLeadingAndTrailingSlashes(href: string): string {
93
- if (href[0] !== '/') href = '/' + href;
94
- if (href[href.length - 1] !== '/') href += '/';
95
- return href;
103
+ if (href[0] !== '/') href = '/' + href;
104
+ if (href[href.length - 1] !== '/') href += '/';
105
+ return href;
96
106
  }
97
107
 
98
108
  /** Create a link entry from a user config object. */
99
109
  function linkFromConfig(
100
- item: SidebarLinkItem,
101
- locale: string | undefined,
102
- currentPathname: string
110
+ item: SidebarLinkItem,
111
+ locale: string | undefined,
112
+ currentPathname: string
103
113
  ) {
104
- let href = item.link;
105
- if (!isAbsolute(href)) {
106
- href = ensureLeadingAndTrailingSlashes(href);
107
- // Inject current locale into link.
108
- if (locale) href = '/' + locale + href;
109
- }
110
- const label = pickLang(item.translations, localeToLang(locale)) || item.label;
111
- return makeLink(href, label, currentPathname);
114
+ let href = item.link;
115
+ if (!isAbsolute(href)) {
116
+ href = ensureLeadingAndTrailingSlashes(href);
117
+ // Inject current locale into link.
118
+ if (locale) href = '/' + locale + href;
119
+ }
120
+ const label = pickLang(item.translations, localeToLang(locale)) || item.label;
121
+ return makeLink(href, label, currentPathname);
112
122
  }
113
123
 
114
124
  /** Create a link entry. */
115
125
  function makeLink(href: string, label: string, currentPathname: string): Link {
116
- if (!isAbsolute(href)) href = pathWithBase(href);
117
- const isCurrent = href === currentPathname;
118
- return { type: 'link', label, href, isCurrent };
126
+ if (!isAbsolute(href)) href = pathWithBase(href);
127
+ const isCurrent = href === currentPathname;
128
+ return { type: 'link', label, href, isCurrent };
119
129
  }
120
130
 
121
131
  /** Get the segments leading to a page. */
122
132
  function getBreadcrumbs(path: string, baseDir: string): string[] {
123
- // Strip extension from path.
124
- const pathWithoutExt = stripExtension(path);
125
- // Index paths will match `baseDir` and don’t include breadcrumbs.
126
- if (pathWithoutExt === baseDir) return [];
127
- // Ensure base directory ends in a trailing slash.
128
- if (!baseDir.endsWith('/')) baseDir += '/';
129
- // Strip base directory from path if present.
130
- const relativePath = pathWithoutExt.startsWith(baseDir)
131
- ? pathWithoutExt.replace(baseDir, '')
132
- : pathWithoutExt;
133
- let dir = dirname(relativePath);
134
- // Return no breadcrumbs for items in the root directory.
135
- if (dir === '.') return [];
136
- return dir.split('/');
133
+ // Strip extension from path.
134
+ const pathWithoutExt = stripExtension(path);
135
+ // Index paths will match `baseDir` and don’t include breadcrumbs.
136
+ if (pathWithoutExt === baseDir) return [];
137
+ // Ensure base directory ends in a trailing slash.
138
+ if (!baseDir.endsWith('/')) baseDir += '/';
139
+ // Strip base directory from path if present.
140
+ const relativePath = pathWithoutExt.startsWith(baseDir)
141
+ ? pathWithoutExt.replace(baseDir, '')
142
+ : pathWithoutExt;
143
+ let dir = dirname(relativePath);
144
+ // Return no breadcrumbs for items in the root directory.
145
+ if (dir === '.') return [];
146
+ return dir.split('/');
137
147
  }
138
148
 
139
149
  /** Turn a flat array of routes into a tree structure. */
140
150
  function treeify(routes: Route[], baseDir: string): Dir {
141
- const treeRoot: Dir = {};
142
- routes.forEach((doc) => {
143
- const breadcrumbs = getBreadcrumbs(doc.id, baseDir);
151
+ const treeRoot: Dir = makeDir();
152
+ routes.forEach((doc) => {
153
+ const breadcrumbs = getBreadcrumbs(doc.id, baseDir);
144
154
 
145
- // Walk down the route’s path to generate the tree.
146
- let currentDir = treeRoot;
147
- breadcrumbs.forEach((dir) => {
148
- // Create new folder if needed.
149
- if (typeof currentDir[dir] === 'undefined') currentDir[dir] = {};
150
- // Go into the subdirectory.
151
- currentDir = currentDir[dir] as Dir;
152
- });
153
- // We’ve walked through the path. Register the route in this directory.
154
- currentDir[basename(doc.slug)] = doc.slug;
155
- });
156
- return treeRoot;
155
+ // Walk down the route’s path to generate the tree.
156
+ let currentDir = treeRoot;
157
+ breadcrumbs.forEach((dir) => {
158
+ // Create new folder if needed.
159
+ if (typeof currentDir[dir] === 'undefined') currentDir[dir] = makeDir();
160
+ // Go into the subdirectory.
161
+ currentDir = currentDir[dir] as Dir;
162
+ });
163
+ // We’ve walked through the path. Register the route in this directory.
164
+ currentDir[basename(doc.slug)] = doc;
165
+ });
166
+ return treeRoot;
157
167
  }
158
168
 
159
169
  /** Create a link entry for a given content collection entry. */
160
- function linkFromSlug(slug: string, currentPathname: string): Link {
161
- const doc = routes.find((doc) => doc.slug === slug)!;
162
- return makeLink(
163
- slugToPathname(doc.slug),
164
- doc.entry.data.title,
165
- currentPathname
166
- );
170
+ function linkFromRoute(route: Route, currentPathname: string): Link {
171
+ return makeLink(
172
+ slugToPathname(route.slug),
173
+ route.entry.data.sidebar.label || route.entry.data.title,
174
+ currentPathname
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Get the sort weight for a given route or directory. Lower numbers rank higher.
180
+ * Directories have the weight of the lowest weighted route they contain.
181
+ */
182
+ function getOrder(routeOrDir: Route | Dir): number {
183
+ return isDir(routeOrDir)
184
+ ? Math.min(...Object.values(routeOrDir).flatMap(getOrder))
185
+ : // If no order value is found, set it to the largest number possible.
186
+ routeOrDir.entry.data.sidebar.order ?? Number.MAX_VALUE;
187
+ }
188
+
189
+ /** Sort a directory’s entries by user-specified order or alphabetically if no order specified. */
190
+ function sortDirEntries(dir: [string, Dir | Route][]): [string, Dir | Route][] {
191
+ return dir.sort(([, a], [, b]) => {
192
+ const [aOrder, bOrder] = [getOrder(a), getOrder(b)];
193
+ // Pages are sorted by order in ascending order.
194
+ if (aOrder !== bOrder) return aOrder < bOrder ? -1 : 1;
195
+ // If two pages have the same order value they will be sorted by their slug.
196
+ return a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0;
197
+ });
167
198
  }
168
199
 
169
200
  /** Create a group entry for a given content collection directory. */
170
201
  function groupFromDir(
171
- dir: Dir,
172
- fullPath: string,
173
- dirName: string,
174
- currentPathname: string,
175
- locale: string | undefined,
176
- collapsed: boolean
202
+ dir: Dir,
203
+ fullPath: string,
204
+ dirName: string,
205
+ currentPathname: string,
206
+ locale: string | undefined,
207
+ collapsed: boolean
177
208
  ): Group {
178
- const entries = Object.entries(dir).map(([key, dirOrSlug]) =>
179
- dirToItem(dirOrSlug, `${fullPath}/${key}`, key, currentPathname, locale, collapsed)
180
- );
181
- return {
182
- type: 'group',
183
- label: dirName,
184
- entries,
185
- collapsed,
186
- };
209
+ const entries = sortDirEntries(Object.entries(dir)).map(([key, dirOrRoute]) =>
210
+ dirToItem(dirOrRoute, `${fullPath}/${key}`, key, currentPathname, locale, collapsed)
211
+ );
212
+ return {
213
+ type: 'group',
214
+ label: dirName,
215
+ entries,
216
+ collapsed,
217
+ };
187
218
  }
188
219
 
189
- /** Create a sidebar entry for a directory or content slug. */
220
+ /** Create a sidebar entry for a directory or content entry. */
190
221
  function dirToItem(
191
- dirOrSlug: Dir[string],
192
- fullPath: string,
193
- dirName: string,
194
- currentPathname: string,
195
- locale: string | undefined,
196
- collapsed: boolean
222
+ dirOrRoute: Dir[string],
223
+ fullPath: string,
224
+ dirName: string,
225
+ currentPathname: string,
226
+ locale: string | undefined,
227
+ collapsed: boolean
197
228
  ): SidebarEntry {
198
- return typeof dirOrSlug === 'string'
199
- ? linkFromSlug(dirOrSlug, currentPathname)
200
- : groupFromDir(dirOrSlug, fullPath, dirName, currentPathname, locale, collapsed);
229
+ return isDir(dirOrRoute)
230
+ ? groupFromDir(dirOrRoute, fullPath, dirName, currentPathname, locale, collapsed)
231
+ : linkFromRoute(dirOrRoute, currentPathname);
201
232
  }
202
233
 
203
234
  /** Create a sidebar entry for a given content directory. */
204
235
  function sidebarFromDir(
205
- tree: Dir,
206
- currentPathname: string,
207
- locale: string | undefined,
208
- collapsed: boolean
236
+ tree: Dir,
237
+ currentPathname: string,
238
+ locale: string | undefined,
239
+ collapsed: boolean
209
240
  ) {
210
- return Object.entries(tree).map(([key, dirOrSlug]) =>
211
- dirToItem(dirOrSlug, key, key, currentPathname, locale, collapsed)
212
- );
241
+ return sortDirEntries(Object.entries(tree)).map(([key, dirOrRoute]) =>
242
+ dirToItem(dirOrRoute, key, key, currentPathname, locale, collapsed)
243
+ );
213
244
  }
214
245
 
215
246
  /** Get the sidebar for the current page. */
216
- export function getSidebar(
217
- pathname: string,
218
- locale: string | undefined
219
- ): SidebarEntry[] {
220
- const routes = getLocaleRoutes(locale);
221
- if (config.sidebar) {
222
- return config.sidebar.map((group) =>
223
- configItemToEntry(group, pathname, locale, routes)
224
- );
225
- } else {
226
- const tree = treeify(routes, locale || '');
227
- return sidebarFromDir(tree, pathname, locale, false);
228
- }
247
+ export function getSidebar(pathname: string, locale: string | undefined): SidebarEntry[] {
248
+ const routes = getLocaleRoutes(locale);
249
+ if (config.sidebar) {
250
+ return config.sidebar.map((group) => configItemToEntry(group, pathname, locale, routes));
251
+ } else {
252
+ const tree = treeify(routes, locale || '');
253
+ return sidebarFromDir(tree, pathname, locale, false);
254
+ }
229
255
  }
230
256
 
231
257
  /** Turn the nested tree structure of a sidebar into a flat list of all the links. */
232
258
  export function flattenSidebar(sidebar: SidebarEntry[]): Link[] {
233
- return sidebar.flatMap((entry) =>
234
- entry.type === 'group' ? flattenSidebar(entry.entries) : entry
235
- );
259
+ return sidebar.flatMap((entry) =>
260
+ entry.type === 'group' ? flattenSidebar(entry.entries) : entry
261
+ );
236
262
  }
237
263
 
238
264
  /** Get previous/next pages in the sidebar or the ones from the frontmatter if any. */
239
265
  export function getPrevNextLinks(
240
- sidebar: SidebarEntry[],
241
- paginationEnabled: boolean,
242
- config: {
243
- prev?: PrevNextLinkConfig;
244
- next?: PrevNextLinkConfig;
245
- }
266
+ sidebar: SidebarEntry[],
267
+ paginationEnabled: boolean,
268
+ config: {
269
+ prev?: PrevNextLinkConfig;
270
+ next?: PrevNextLinkConfig;
271
+ }
246
272
  ): {
247
- prev: Link | undefined;
248
- next: Link | undefined;
273
+ prev: Link | undefined;
274
+ next: Link | undefined;
249
275
  } {
250
- const entries = flattenSidebar(sidebar);
251
- const currentIndex = entries.findIndex((entry) => entry.isCurrent);
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
- );
262
- return { prev, next };
276
+ const entries = flattenSidebar(sidebar);
277
+ const currentIndex = entries.findIndex((entry) => entry.isCurrent);
278
+ const prev = applyPrevNextLinkConfig(entries[currentIndex - 1], paginationEnabled, config.prev);
279
+ const next = applyPrevNextLinkConfig(
280
+ currentIndex > -1 ? entries[currentIndex + 1] : undefined,
281
+ paginationEnabled,
282
+ config.next
283
+ );
284
+ return { prev, next };
263
285
  }
264
286
 
265
287
  /** Apply a prev/next link config to a navigation link. */
266
288
  function applyPrevNextLinkConfig(
267
- link: Link | undefined,
268
- paginationEnabled: boolean,
269
- config: PrevNextLinkConfig | undefined
289
+ link: Link | undefined,
290
+ paginationEnabled: boolean,
291
+ config: PrevNextLinkConfig | undefined
270
292
  ): 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;
293
+ // Explicitly remove the link.
294
+ if (config === false) return undefined;
295
+ // Use the generated link if any.
296
+ else if (config === true) return link;
297
+ // If a link exists, update its label if needed.
298
+ else if (typeof config === 'string' && link) {
299
+ return { ...link, label: config };
300
+ } else if (typeof config === 'object') {
301
+ if (link) {
302
+ // If a link exists, update both its label and href if needed.
303
+ return {
304
+ ...link,
305
+ label: config.label ?? link.label,
306
+ href: config.link ?? link.href,
307
+ };
308
+ } else if (config.link && config.label) {
309
+ // If there is no link and the frontmatter contains both a URL and a label,
310
+ // create a new link.
311
+ return makeLink(config.link, config.label, config.link);
312
+ }
313
+ }
314
+ // Otherwise, if the global config is enabled, return the generated link if any.
315
+ return paginationEnabled ? link : undefined;
294
316
  }
295
317
 
296
318
  /** Remove the extension from a path. */