docula 1.13.0 → 1.14.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/dist/docula.d.ts +2 -0
- package/dist/docula.js +68 -28
- package/package.json +17 -20
- package/templates/modern/api.hbs +1 -1
- package/templates/modern/css/api.css +22 -4
- package/templates/modern/css/styles.css +38 -0
- package/templates/modern/includes/scripts.hbs +42 -0
- package/templates/modern/includes/sidebar.hbs +7 -2
package/dist/docula.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ type ApiOperation = {
|
|
|
83
83
|
id: string;
|
|
84
84
|
method: string;
|
|
85
85
|
methodUpper: string;
|
|
86
|
+
methodShort: string;
|
|
86
87
|
path: string;
|
|
87
88
|
summary: string;
|
|
88
89
|
description: string;
|
|
@@ -448,6 +449,7 @@ declare class DoculaBuilder {
|
|
|
448
449
|
private readonly _console;
|
|
449
450
|
private readonly _hash;
|
|
450
451
|
onReleaseChangelog?: (entries: DoculaChangelogEntry[], console: DoculaConsole) => Promise<DoculaChangelogEntry[]> | DoculaChangelogEntry[];
|
|
452
|
+
onAutoReadme?: (content: string, sourcePath: string, console: DoculaConsole) => Promise<string> | string;
|
|
451
453
|
get console(): DoculaConsole;
|
|
452
454
|
constructor(options?: DoculaBuilderOptions, engineOptions?: any);
|
|
453
455
|
get options(): DoculaOptions;
|
package/dist/docula.js
CHANGED
|
@@ -12,7 +12,7 @@ import { blue, bold, cyan, dim, gray, green, magenta, red, white, yellow } from
|
|
|
12
12
|
import { CacheableNet } from "@cacheable/net";
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
//#region package.json
|
|
15
|
-
var version = "1.
|
|
15
|
+
var version = "1.14.0";
|
|
16
16
|
var package_default = {
|
|
17
17
|
name: "docula",
|
|
18
18
|
version,
|
|
@@ -69,36 +69,33 @@ var package_default = {
|
|
|
69
69
|
],
|
|
70
70
|
bin: { "docula": "./bin/docula.js" },
|
|
71
71
|
dependencies: {
|
|
72
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
73
|
-
"@ai-sdk/google": "^3.0.
|
|
74
|
-
"@ai-sdk/openai": "^3.0.
|
|
75
|
-
"@cacheable/net": "^2.0.
|
|
76
|
-
"ai": "^6.0.
|
|
72
|
+
"@ai-sdk/anthropic": "^3.0.69",
|
|
73
|
+
"@ai-sdk/google": "^3.0.63",
|
|
74
|
+
"@ai-sdk/openai": "^3.0.53",
|
|
75
|
+
"@cacheable/net": "^2.0.7",
|
|
76
|
+
"ai": "^6.0.164",
|
|
77
77
|
"colorette": "^2.0.20",
|
|
78
|
-
"ecto": "^4.8.
|
|
79
|
-
"
|
|
80
|
-
"hashery": "^1.5.1",
|
|
78
|
+
"ecto": "^4.8.4",
|
|
79
|
+
"hashery": "^2.0.0",
|
|
81
80
|
"jiti": "^2.6.1",
|
|
82
81
|
"serve-handler": "^6.1.7",
|
|
83
82
|
"update-notifier": "^7.3.1",
|
|
84
|
-
"writr": "^6.1.
|
|
83
|
+
"writr": "^6.1.1"
|
|
85
84
|
},
|
|
86
85
|
devDependencies: {
|
|
87
|
-
"@biomejs/biome": "^2.4.
|
|
88
|
-
"@playwright/test": "^1.
|
|
89
|
-
"@types/
|
|
90
|
-
"@types/js-yaml": "^4.0.9",
|
|
91
|
-
"@types/node": "^25.5.0",
|
|
86
|
+
"@biomejs/biome": "^2.4.12",
|
|
87
|
+
"@playwright/test": "^1.59.1",
|
|
88
|
+
"@types/node": "^25.6.0",
|
|
92
89
|
"@types/serve-handler": "^6.1.4",
|
|
93
90
|
"@types/update-notifier": "^6.0.8",
|
|
94
|
-
"@vitest/coverage-v8": "^4.1.
|
|
95
|
-
"dotenv": "^17.
|
|
91
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
92
|
+
"dotenv": "^17.4.2",
|
|
96
93
|
"postject": "1.0.0-alpha.6",
|
|
97
94
|
"rimraf": "^6.1.3",
|
|
98
|
-
"tsdown": "^0.21.
|
|
95
|
+
"tsdown": "^0.21.9",
|
|
99
96
|
"tsx": "^4.21.0",
|
|
100
|
-
"typescript": "^
|
|
101
|
-
"vitest": "^4.1.
|
|
97
|
+
"typescript": "^6.0.2",
|
|
98
|
+
"vitest": "^4.1.4"
|
|
102
99
|
},
|
|
103
100
|
files: [
|
|
104
101
|
"dist",
|
|
@@ -379,10 +376,13 @@ function parseOpenApiSpec(specJson) {
|
|
|
379
376
|
const requestBody = extractRequestBody(operation, spec);
|
|
380
377
|
const responses = extractResponses(operation, spec);
|
|
381
378
|
const codeExamples = generateCodeExamples(method, pathStr, servers.length > 0 ? servers[0].url : "", parameters, requestBody);
|
|
379
|
+
const operationId = operation.operationId ?? `${method}-${pathStr.replaceAll(/[^a-zA-Z0-9]/g, "-")}`;
|
|
380
|
+
const methodUpper = method.toUpperCase();
|
|
382
381
|
const apiOperation = {
|
|
383
|
-
id: slugify(
|
|
382
|
+
id: slugify(operationId),
|
|
384
383
|
method,
|
|
385
|
-
methodUpper
|
|
384
|
+
methodUpper,
|
|
385
|
+
methodShort: methodUpper === "DELETE" ? "DEL" : methodUpper === "OPTIONS" ? "OPT" : methodUpper,
|
|
386
386
|
path: pathStr,
|
|
387
387
|
summary: operation.summary ?? "",
|
|
388
388
|
description: operation.description ?? "",
|
|
@@ -1093,6 +1093,22 @@ function hashFile(hash, filePath) {
|
|
|
1093
1093
|
const content = fs.readFileSync(filePath);
|
|
1094
1094
|
return hash.toHashSync(content);
|
|
1095
1095
|
}
|
|
1096
|
+
function tryHashFile(hash, filePath) {
|
|
1097
|
+
try {
|
|
1098
|
+
return hashFile(hash, filePath);
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
if (error.code === "ENOENT") return;
|
|
1101
|
+
/* v8 ignore next 2 -- @preserve */
|
|
1102
|
+
throw error;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function hashConfigFile(hash, sitePath) {
|
|
1106
|
+
for (const name of ["docula.config.ts", "docula.config.mjs"]) {
|
|
1107
|
+
const configPath = path.join(sitePath, name);
|
|
1108
|
+
if (fs.existsSync(configPath)) return hashFile(hash, configPath);
|
|
1109
|
+
}
|
|
1110
|
+
return "";
|
|
1111
|
+
}
|
|
1096
1112
|
function hashOptions(hash, options) {
|
|
1097
1113
|
const relevant = {
|
|
1098
1114
|
siteUrl: options.siteUrl,
|
|
@@ -1119,19 +1135,29 @@ function hashOptions(hash, options) {
|
|
|
1119
1135
|
ai: options.ai,
|
|
1120
1136
|
googleTagManager: options.googleTagManager
|
|
1121
1137
|
};
|
|
1122
|
-
|
|
1138
|
+
const optionsHash = hash.toHashSync(JSON.stringify(relevant));
|
|
1139
|
+
const configHash = hashConfigFile(hash, options.sitePath);
|
|
1140
|
+
return hash.toHashSync(`${optionsHash}:${configHash}`);
|
|
1123
1141
|
}
|
|
1124
1142
|
function hashTemplateDirectory(hash, templatePath) {
|
|
1125
1143
|
/* v8 ignore next 3 -- @preserve */
|
|
1126
1144
|
if (!fs.existsSync(templatePath)) return "";
|
|
1127
|
-
const
|
|
1145
|
+
const files = listFilesRecursive(templatePath);
|
|
1146
|
+
const hashes = [];
|
|
1147
|
+
for (const f of files) {
|
|
1148
|
+
const fileHash = tryHashFile(hash, path.join(templatePath, f));
|
|
1149
|
+
if (fileHash !== void 0) hashes.push(fileHash);
|
|
1150
|
+
}
|
|
1128
1151
|
return hash.toHashSync(hashes.join(""));
|
|
1129
1152
|
}
|
|
1130
1153
|
function hashSourceFiles(hash, dir) {
|
|
1131
1154
|
const hashes = {};
|
|
1132
1155
|
if (!fs.existsSync(dir)) return hashes;
|
|
1133
1156
|
const files = listFilesRecursive(dir);
|
|
1134
|
-
for (const file of files)
|
|
1157
|
+
for (const file of files) {
|
|
1158
|
+
const fileHash = tryHashFile(hash, path.join(dir, file));
|
|
1159
|
+
if (fileHash !== void 0) hashes[file] = fileHash;
|
|
1160
|
+
}
|
|
1135
1161
|
return hashes;
|
|
1136
1162
|
}
|
|
1137
1163
|
function recordsEqual(a, b) {
|
|
@@ -2739,6 +2765,7 @@ var DoculaBuilder = class {
|
|
|
2739
2765
|
_console;
|
|
2740
2766
|
_hash = new Hashery();
|
|
2741
2767
|
onReleaseChangelog;
|
|
2768
|
+
onAutoReadme;
|
|
2742
2769
|
get console() {
|
|
2743
2770
|
return this._console;
|
|
2744
2771
|
}
|
|
@@ -3014,9 +3041,16 @@ var DoculaBuilder = class {
|
|
|
3014
3041
|
if (packageJson.name && typeof packageJson.name === "string") readmeContent = `# ${packageJson.name}\n\n${readmeContent}`;
|
|
3015
3042
|
} catch {}
|
|
3016
3043
|
}
|
|
3044
|
+
let content = readmeContent;
|
|
3045
|
+
if (this.onAutoReadme) try {
|
|
3046
|
+
content = await this.onAutoReadme(content, cwdReadmePath, this._console);
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3049
|
+
this._console.error(`onAutoReadme error: ${message}`);
|
|
3050
|
+
}
|
|
3017
3051
|
return {
|
|
3018
3052
|
sourcePath: cwdReadmePath,
|
|
3019
|
-
content
|
|
3053
|
+
content
|
|
3020
3054
|
};
|
|
3021
3055
|
}
|
|
3022
3056
|
async getGithubData(githubPath) {
|
|
@@ -3396,6 +3430,8 @@ var Docula = class {
|
|
|
3396
3430
|
const builder = new DoculaBuilder(Object.assign(this.options, { console: this._console }));
|
|
3397
3431
|
/* v8 ignore next 4 -- @preserve */
|
|
3398
3432
|
if (this._configFileModule.onReleaseChangelog) builder.onReleaseChangelog = this._configFileModule.onReleaseChangelog;
|
|
3433
|
+
/* v8 ignore next 4 -- @preserve */
|
|
3434
|
+
if (this._configFileModule.onAutoReadme) builder.onAutoReadme = this._configFileModule.onAutoReadme;
|
|
3399
3435
|
await builder.build();
|
|
3400
3436
|
return builder;
|
|
3401
3437
|
}
|
|
@@ -3496,12 +3532,16 @@ var Docula = class {
|
|
|
3496
3532
|
}
|
|
3497
3533
|
else {
|
|
3498
3534
|
const { createJiti } = await import("jiti");
|
|
3499
|
-
|
|
3535
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
3536
|
+
this._configFileModule = await jiti.import(absolutePath);
|
|
3500
3537
|
}
|
|
3501
3538
|
return;
|
|
3502
3539
|
}
|
|
3503
3540
|
/* v8 ignore next -- @preserve */
|
|
3504
|
-
if (fs.existsSync(mjsConfigFile))
|
|
3541
|
+
if (fs.existsSync(mjsConfigFile)) {
|
|
3542
|
+
const absolutePath = path.resolve(mjsConfigFile);
|
|
3543
|
+
this._configFileModule = await import(pathToFileURL(absolutePath).href);
|
|
3544
|
+
}
|
|
3505
3545
|
}
|
|
3506
3546
|
/**
|
|
3507
3547
|
* Watch the site path for file changes and rebuild on change
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docula",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Beautiful Website for Your Projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/docula.js",
|
|
@@ -40,36 +40,33 @@
|
|
|
40
40
|
"docula": "./bin/docula.js"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
44
|
-
"@ai-sdk/google": "^3.0.
|
|
45
|
-
"@ai-sdk/openai": "^3.0.
|
|
46
|
-
"@cacheable/net": "^2.0.
|
|
47
|
-
"ai": "^6.0.
|
|
43
|
+
"@ai-sdk/anthropic": "^3.0.69",
|
|
44
|
+
"@ai-sdk/google": "^3.0.63",
|
|
45
|
+
"@ai-sdk/openai": "^3.0.53",
|
|
46
|
+
"@cacheable/net": "^2.0.7",
|
|
47
|
+
"ai": "^6.0.164",
|
|
48
48
|
"colorette": "^2.0.20",
|
|
49
|
-
"ecto": "^4.8.
|
|
50
|
-
"
|
|
51
|
-
"hashery": "^1.5.1",
|
|
49
|
+
"ecto": "^4.8.4",
|
|
50
|
+
"hashery": "^2.0.0",
|
|
52
51
|
"jiti": "^2.6.1",
|
|
53
52
|
"serve-handler": "^6.1.7",
|
|
54
53
|
"update-notifier": "^7.3.1",
|
|
55
|
-
"writr": "^6.1.
|
|
54
|
+
"writr": "^6.1.1"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@biomejs/biome": "^2.4.
|
|
59
|
-
"@playwright/test": "^1.
|
|
60
|
-
"@types/
|
|
61
|
-
"@types/js-yaml": "^4.0.9",
|
|
62
|
-
"@types/node": "^25.5.0",
|
|
57
|
+
"@biomejs/biome": "^2.4.12",
|
|
58
|
+
"@playwright/test": "^1.59.1",
|
|
59
|
+
"@types/node": "^25.6.0",
|
|
63
60
|
"@types/serve-handler": "^6.1.4",
|
|
64
61
|
"@types/update-notifier": "^6.0.8",
|
|
65
|
-
"@vitest/coverage-v8": "^4.1.
|
|
66
|
-
"dotenv": "^17.
|
|
62
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
63
|
+
"dotenv": "^17.4.2",
|
|
67
64
|
"postject": "1.0.0-alpha.6",
|
|
68
65
|
"rimraf": "^6.1.3",
|
|
69
|
-
"tsdown": "^0.21.
|
|
66
|
+
"tsdown": "^0.21.9",
|
|
70
67
|
"tsx": "^4.21.0",
|
|
71
|
-
"typescript": "^
|
|
72
|
-
"vitest": "^4.1.
|
|
68
|
+
"typescript": "^6.0.2",
|
|
69
|
+
"vitest": "^4.1.4"
|
|
73
70
|
},
|
|
74
71
|
"files": [
|
|
75
72
|
"dist",
|
package/templates/modern/api.hbs
CHANGED
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
<div class="api-sidebar__group-items">
|
|
40
40
|
{{#each this.operations}}
|
|
41
41
|
<a href="#{{this.id}}" class="api-sidebar__item" data-method="{{this.method}}" data-path="{{this.path}}">
|
|
42
|
-
<span class="method-badge method-badge--{{this.method}}">{{this.methodUpper}}</span>
|
|
43
42
|
<span class="api-sidebar__item-path">{{this.path}}</span>
|
|
43
|
+
<span class="method-badge method-badge--{{this.method}}">{{this.methodShort}}</span>
|
|
44
44
|
</a>
|
|
45
45
|
{{/each}}
|
|
46
46
|
</div>
|
|
@@ -100,17 +100,20 @@
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
.api-sidebar__group-items {
|
|
103
|
-
padding:
|
|
103
|
+
padding: 4px 0 8px 12px;
|
|
104
|
+
margin-left: 10px;
|
|
105
|
+
border-left: 1px solid var(--border);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
.api-sidebar__item {
|
|
107
109
|
display: flex;
|
|
108
110
|
align-items: center;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
gap: 12px;
|
|
113
|
+
padding: 8px 10px;
|
|
111
114
|
font-size: 13px;
|
|
112
115
|
border-radius: 4px;
|
|
113
|
-
color: var(--fg);
|
|
116
|
+
color: var(--muted-fg);
|
|
114
117
|
white-space: nowrap;
|
|
115
118
|
overflow: hidden;
|
|
116
119
|
text-overflow: ellipsis;
|
|
@@ -125,8 +128,23 @@
|
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
.api-sidebar__item-path {
|
|
131
|
+
min-width: 0;
|
|
128
132
|
overflow: hidden;
|
|
129
133
|
text-overflow: ellipsis;
|
|
134
|
+
color: var(--muted-fg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.api-sidebar__item--active .api-sidebar__item-path {
|
|
138
|
+
color: var(--fg);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.api-sidebar__item .method-badge {
|
|
142
|
+
background: transparent;
|
|
143
|
+
padding: 0;
|
|
144
|
+
min-width: 0;
|
|
145
|
+
border-radius: 0;
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
letter-spacing: 0.5px;
|
|
130
148
|
}
|
|
131
149
|
|
|
132
150
|
/* Method Badges */
|
|
@@ -289,6 +289,44 @@ body {
|
|
|
289
289
|
letter-spacing: 0.24px;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
.nav-sidebar__title:has(.nav-sidebar__toggle) {
|
|
293
|
+
padding: 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.nav-sidebar__toggle {
|
|
297
|
+
all: unset;
|
|
298
|
+
box-sizing: border-box;
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: space-between;
|
|
302
|
+
width: 100%;
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
padding: 0 8px 0 10px;
|
|
305
|
+
font: inherit;
|
|
306
|
+
color: inherit;
|
|
307
|
+
text-transform: inherit;
|
|
308
|
+
letter-spacing: inherit;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.nav-sidebar__toggle:focus-visible {
|
|
312
|
+
outline: 2px solid var(--accent, currentColor);
|
|
313
|
+
outline-offset: 2px;
|
|
314
|
+
border-radius: 4px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.nav-sidebar__chevron {
|
|
318
|
+
flex-shrink: 0;
|
|
319
|
+
transition: transform 150ms ease;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.nav-sidebar__section--collapsed .nav-sidebar__chevron {
|
|
323
|
+
transform: rotate(-90deg);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.nav-sidebar__section--collapsed .nav-sidebar__list {
|
|
327
|
+
display: none;
|
|
328
|
+
}
|
|
329
|
+
|
|
292
330
|
.nav-sidebar__list {
|
|
293
331
|
margin-block: 2px;
|
|
294
332
|
}
|
|
@@ -227,6 +227,48 @@
|
|
|
227
227
|
}
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
+
// Sidebar section collapse/expand
|
|
231
|
+
const SIDEBAR_STORAGE_KEY = 'docula:sidebar-sections';
|
|
232
|
+
const collapsibleSections = document.querySelectorAll('.nav-sidebar__section--collapsible');
|
|
233
|
+
if (collapsibleSections.length > 0) {
|
|
234
|
+
let storedSectionState = {};
|
|
235
|
+
try {
|
|
236
|
+
storedSectionState = JSON.parse(localStorage.getItem(SIDEBAR_STORAGE_KEY) || '{}');
|
|
237
|
+
} catch (e) { storedSectionState = {}; }
|
|
238
|
+
|
|
239
|
+
const activeSidebarLink = document.querySelector('.nav-sidebar__item--active');
|
|
240
|
+
const activeSection = activeSidebarLink ? activeSidebarLink.closest('.nav-sidebar__section--collapsible') : null;
|
|
241
|
+
const defaultOpenSection = activeSection || collapsibleSections[0];
|
|
242
|
+
|
|
243
|
+
const setSectionOpen = (section, toggle, open) => {
|
|
244
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
245
|
+
section.classList.toggle('nav-sidebar__section--collapsed', !open);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
collapsibleSections.forEach((section, idx) => {
|
|
249
|
+
const toggle = section.querySelector('.nav-sidebar__toggle');
|
|
250
|
+
const list = section.querySelector('.nav-sidebar__list');
|
|
251
|
+
if (!toggle || !list) return;
|
|
252
|
+
const listId = 'nav-sidebar-section-' + idx;
|
|
253
|
+
list.id = listId;
|
|
254
|
+
toggle.setAttribute('aria-controls', listId);
|
|
255
|
+
|
|
256
|
+
const key = 'section-' + idx;
|
|
257
|
+
const hasStored = Object.prototype.hasOwnProperty.call(storedSectionState, key);
|
|
258
|
+
const isOpen = hasStored ? !!storedSectionState[key] : section === defaultOpenSection;
|
|
259
|
+
setSectionOpen(section, toggle, isOpen);
|
|
260
|
+
|
|
261
|
+
toggle.addEventListener('click', () => {
|
|
262
|
+
const next = toggle.getAttribute('aria-expanded') !== 'true';
|
|
263
|
+
setSectionOpen(section, toggle, next);
|
|
264
|
+
storedSectionState[key] = next;
|
|
265
|
+
try {
|
|
266
|
+
localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(storedSectionState));
|
|
267
|
+
} catch (e) { /* storage unavailable */ }
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
230
272
|
// Active header nav link highlighting
|
|
231
273
|
const navLinks = document.querySelectorAll('.header-bottom__item');
|
|
232
274
|
navLinks.forEach((link) => {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{{#forEach sidebarItems}}
|
|
2
2
|
{{#if children}}
|
|
3
|
-
<section class="nav-sidebar__section">
|
|
4
|
-
<h2 class="nav-sidebar__title">
|
|
3
|
+
<section class="nav-sidebar__section nav-sidebar__section--collapsible">
|
|
4
|
+
<h2 class="nav-sidebar__title">
|
|
5
|
+
<button type="button" class="nav-sidebar__toggle" aria-expanded="true">
|
|
6
|
+
<span class="nav-sidebar__toggle-label">{{name}}</span>
|
|
7
|
+
<svg class="nav-sidebar__chevron" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
|
8
|
+
</button>
|
|
9
|
+
</h2>
|
|
5
10
|
<ul class="nav-sidebar__list">
|
|
6
11
|
{{#forEach children}}
|
|
7
12
|
<li>
|