@litodocs/cli 1.1.0 → 1.2.1
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/package.json +1 -1
- package/src/core/landing-sync.js +263 -25
- package/src/core/sync.js +1 -1
package/package.json
CHANGED
package/src/core/landing-sync.js
CHANGED
|
@@ -13,6 +13,46 @@ const { copy, ensureDir, readFile, writeFile, pathExists, readJson } = pkg;
|
|
|
13
13
|
import { join, relative, basename, extname } from 'path';
|
|
14
14
|
import { readdir } from 'fs/promises';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Add is:inline to all <script> and <style> tags in HTML so Astro ships
|
|
18
|
+
* them as-is. Without this, Astro treats scripts as ES modules (scoping
|
|
19
|
+
* declarations, breaking onclick handlers) and scopes styles (breaking
|
|
20
|
+
* global CSS like :root variables, animations, etc.).
|
|
21
|
+
*/
|
|
22
|
+
function inlineForAstro(html) {
|
|
23
|
+
// Add is:inline to <script> tags that don't already have it
|
|
24
|
+
html = html.replace(/<script(?![^>]*is:inline)([^>]*>)/gi, '<script is:inline$1');
|
|
25
|
+
// Add is:inline to <style> tags that don't already have is:inline or is:global
|
|
26
|
+
html = html.replace(/<style(?![^>]*is:(?:inline|global))([^>]*>)/gi, '<style is:inline$1');
|
|
27
|
+
return html;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if HTML is a full document (has <html> or <!doctype>).
|
|
32
|
+
* If so, extract head content, body content, and html/body attributes
|
|
33
|
+
* so we can merge them into the Astro template properly.
|
|
34
|
+
*/
|
|
35
|
+
function parseFullHtmlDocument(html) {
|
|
36
|
+
const isFullDoc = /<!doctype\s+html|<html[\s>]/i.test(html);
|
|
37
|
+
if (!isFullDoc) return null;
|
|
38
|
+
|
|
39
|
+
// Extract <html> tag attributes
|
|
40
|
+
const htmlTagMatch = html.match(/<html([^>]*)>/i);
|
|
41
|
+
const htmlAttrs = htmlTagMatch ? htmlTagMatch[1].trim() : '';
|
|
42
|
+
|
|
43
|
+
// Extract <head> inner content
|
|
44
|
+
const headMatch = html.match(/<head[^>]*>([\s\S]*)<\/head>/i);
|
|
45
|
+
const headContent = headMatch ? headMatch[1].trim() : '';
|
|
46
|
+
|
|
47
|
+
// Extract <body> tag attributes and inner content
|
|
48
|
+
const bodyTagMatch = html.match(/<body([^>]*)>/i);
|
|
49
|
+
const bodyAttrs = bodyTagMatch ? bodyTagMatch[1].trim() : '';
|
|
50
|
+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
51
|
+
const bodyContent = bodyMatch ? bodyMatch[1].trim() : '';
|
|
52
|
+
|
|
53
|
+
return { htmlAttrs, headContent, bodyContent, bodyAttrs };
|
|
54
|
+
}
|
|
55
|
+
|
|
16
56
|
/**
|
|
17
57
|
* Landing page types
|
|
18
58
|
*/
|
|
@@ -98,11 +138,17 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
|
|
|
98
138
|
// Read all files from _landing/
|
|
99
139
|
const files = await readdir(landingSource, { withFileTypes: true });
|
|
100
140
|
|
|
141
|
+
// Check if navbar/footer are explicitly hidden
|
|
142
|
+
const navbarHidden = landingConfig.navbar === false;
|
|
143
|
+
const footerHidden = landingConfig.footer === false;
|
|
144
|
+
|
|
101
145
|
// Separate files by type
|
|
102
146
|
const htmlFiles = [];
|
|
103
147
|
const cssFiles = [];
|
|
104
148
|
const jsFiles = [];
|
|
105
149
|
const assetFiles = [];
|
|
150
|
+
let navbarHtml = null;
|
|
151
|
+
let footerHtml = null;
|
|
106
152
|
|
|
107
153
|
for (const file of files) {
|
|
108
154
|
if (file.isDirectory()) {
|
|
@@ -114,8 +160,17 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
|
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
const ext = extname(file.name).toLowerCase();
|
|
163
|
+
const name = basename(file.name, ext).toLowerCase();
|
|
164
|
+
|
|
117
165
|
if (ext === '.html' || ext === '.htm') {
|
|
118
|
-
|
|
166
|
+
// Detect custom navbar/footer HTML files (skip if hidden)
|
|
167
|
+
if (!navbarHidden && (name === 'navbar' || name === 'nav' || name === 'header')) {
|
|
168
|
+
navbarHtml = file.name;
|
|
169
|
+
} else if (!footerHidden && name === 'footer') {
|
|
170
|
+
footerHtml = file.name;
|
|
171
|
+
} else if (!((name === 'navbar' || name === 'nav' || name === 'header') || name === 'footer')) {
|
|
172
|
+
htmlFiles.push(file.name);
|
|
173
|
+
}
|
|
119
174
|
} else if (ext === '.css') {
|
|
120
175
|
cssFiles.push(file.name);
|
|
121
176
|
} else if (ext === '.js' || ext === '.mjs') {
|
|
@@ -123,6 +178,31 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
|
|
|
123
178
|
}
|
|
124
179
|
}
|
|
125
180
|
|
|
181
|
+
// Read custom navbar/footer content if present
|
|
182
|
+
let navbarContent = navbarHidden ? '__hidden__' : null;
|
|
183
|
+
let footerContent = footerHidden ? '__hidden__' : null;
|
|
184
|
+
|
|
185
|
+
if (!navbarHidden && navbarHtml) {
|
|
186
|
+
navbarContent = await readFile(join(landingSource, navbarHtml), 'utf-8');
|
|
187
|
+
}
|
|
188
|
+
if (!footerHidden && footerHtml) {
|
|
189
|
+
footerContent = await readFile(join(landingSource, footerHtml), 'utf-8');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Also check config for custom navbar/footer (skip if hidden)
|
|
193
|
+
if (!navbarHidden && !navbarContent && landingConfig.navbar?.html) {
|
|
194
|
+
const navPath = join(landingSource, '..', landingConfig.navbar.html);
|
|
195
|
+
if (await pathExists(navPath)) {
|
|
196
|
+
navbarContent = await readFile(navPath, 'utf-8');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!footerHidden && !footerContent && landingConfig.footer?.html) {
|
|
200
|
+
const footerPath = join(landingSource, '..', landingConfig.footer.html);
|
|
201
|
+
if (await pathExists(footerPath)) {
|
|
202
|
+
footerContent = await readFile(footerPath, 'utf-8');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
126
206
|
// Generate landing page based on framework
|
|
127
207
|
await generateLandingForFramework(
|
|
128
208
|
projectDir,
|
|
@@ -133,6 +213,8 @@ export async function syncCustomLanding(sourcePath, projectDir, frameworkConfig,
|
|
|
133
213
|
cssFiles,
|
|
134
214
|
jsFiles,
|
|
135
215
|
assetFiles,
|
|
216
|
+
navbarContent,
|
|
217
|
+
footerContent,
|
|
136
218
|
config: landingConfig,
|
|
137
219
|
}
|
|
138
220
|
);
|
|
@@ -170,12 +252,55 @@ export async function syncSectionsLanding(sourcePath, projectDir, frameworkConfi
|
|
|
170
252
|
}
|
|
171
253
|
}
|
|
172
254
|
|
|
255
|
+
// Check if navbar/footer are explicitly hidden
|
|
256
|
+
const navbarHidden = landingConfig.navbar === false;
|
|
257
|
+
const footerHidden = landingConfig.footer === false;
|
|
258
|
+
|
|
259
|
+
// Check for custom navbar/footer in _landing/ folder
|
|
260
|
+
const landingSource = join(sourcePath, landingConfig.source || '_landing');
|
|
261
|
+
let navbarContent = navbarHidden ? '__hidden__' : null;
|
|
262
|
+
let footerContent = footerHidden ? '__hidden__' : null;
|
|
263
|
+
|
|
264
|
+
if (!navbarHidden) {
|
|
265
|
+
const navbarNames = ['navbar.html', 'nav.html', 'header.html'];
|
|
266
|
+
for (const name of navbarNames) {
|
|
267
|
+
const navPath = join(landingSource, name);
|
|
268
|
+
if (await pathExists(navPath)) {
|
|
269
|
+
navbarContent = await readFile(navPath, 'utf-8');
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!footerHidden) {
|
|
276
|
+
const footerPath = join(landingSource, 'footer.html');
|
|
277
|
+
if (await pathExists(footerPath)) {
|
|
278
|
+
footerContent = await readFile(footerPath, 'utf-8');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Also check config for custom navbar/footer (skip if hidden)
|
|
283
|
+
if (!navbarHidden && !navbarContent && landingConfig.navbar?.html) {
|
|
284
|
+
const navPath = join(sourcePath, landingConfig.navbar.html);
|
|
285
|
+
if (await pathExists(navPath)) {
|
|
286
|
+
navbarContent = await readFile(navPath, 'utf-8');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!footerHidden && !footerContent && landingConfig.footer?.html) {
|
|
290
|
+
const fPath = join(sourcePath, landingConfig.footer.html);
|
|
291
|
+
if (await pathExists(fPath)) {
|
|
292
|
+
footerContent = await readFile(fPath, 'utf-8');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
173
296
|
// Generate sections landing for framework
|
|
174
297
|
await generateSectionsLandingForFramework(
|
|
175
298
|
projectDir,
|
|
176
299
|
frameworkConfig,
|
|
177
300
|
{
|
|
178
301
|
sections: processedSections,
|
|
302
|
+
navbarContent,
|
|
303
|
+
footerContent,
|
|
179
304
|
config: landingConfig,
|
|
180
305
|
}
|
|
181
306
|
);
|
|
@@ -213,7 +338,7 @@ async function generateLandingForFramework(projectDir, frameworkConfig, landingD
|
|
|
213
338
|
* Generate Astro landing page (standalone, no template imports)
|
|
214
339
|
*/
|
|
215
340
|
async function generateAstroLanding(projectDir, landingData) {
|
|
216
|
-
const { sourcePath, htmlFiles, cssFiles, jsFiles, config } = landingData;
|
|
341
|
+
const { sourcePath, htmlFiles, cssFiles, jsFiles, navbarContent, footerContent, config } = landingData;
|
|
217
342
|
|
|
218
343
|
// Read main HTML file
|
|
219
344
|
const mainHtml = htmlFiles.includes('index.html') ? 'index.html' : htmlFiles[0];
|
|
@@ -224,13 +349,20 @@ async function generateAstroLanding(projectDir, landingData) {
|
|
|
224
349
|
|
|
225
350
|
let htmlContent = await readFile(join(sourcePath, mainHtml), 'utf-8');
|
|
226
351
|
|
|
227
|
-
//
|
|
352
|
+
// Make all <script> tags in the user's HTML pass through Astro untouched
|
|
353
|
+
htmlContent = inlineForAstro(htmlContent);
|
|
354
|
+
|
|
355
|
+
// Read CSS files and write to a separate file
|
|
228
356
|
let cssContent = '';
|
|
229
357
|
for (const cssFile of cssFiles) {
|
|
230
358
|
const css = await readFile(join(sourcePath, cssFile), 'utf-8');
|
|
231
359
|
cssContent += `/* ${cssFile} */\n${css}\n\n`;
|
|
232
360
|
}
|
|
233
361
|
|
|
362
|
+
// Write landing CSS to a separate file so Vite processes it through the full pipeline
|
|
363
|
+
const landingCssPath = join(projectDir, 'src', 'styles', 'landing.css');
|
|
364
|
+
await writeFile(landingCssPath, cssContent, 'utf-8');
|
|
365
|
+
|
|
234
366
|
// Read JS files
|
|
235
367
|
let jsContent = '';
|
|
236
368
|
for (const jsFile of jsFiles) {
|
|
@@ -238,13 +370,110 @@ async function generateAstroLanding(projectDir, landingData) {
|
|
|
238
370
|
jsContent += `// ${jsFile}\n${js}\n\n`;
|
|
239
371
|
}
|
|
240
372
|
|
|
241
|
-
//
|
|
242
|
-
const
|
|
373
|
+
// Check if the user's HTML is a full document (has <html>, <head>, <body>)
|
|
374
|
+
const parsed = parseFullHtmlDocument(htmlContent);
|
|
375
|
+
|
|
376
|
+
let astroContent;
|
|
377
|
+
|
|
378
|
+
if (parsed) {
|
|
379
|
+
// Full HTML document: merge the user's head/body into the Astro page
|
|
380
|
+
// instead of nesting an entire HTML document inside another one.
|
|
381
|
+
astroContent = generateAstroFromFullDoc(parsed, { cssFiles, jsContent, navbarContent, footerContent });
|
|
382
|
+
} else {
|
|
383
|
+
// HTML fragment: wrap it in a full Astro page
|
|
384
|
+
astroContent = generateAstroFromFragment(htmlContent, { jsContent, navbarContent, footerContent });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Write to index.astro
|
|
388
|
+
const indexPath = join(projectDir, 'src', 'pages', 'index.astro');
|
|
389
|
+
await writeFile(indexPath, astroContent, 'utf-8');
|
|
390
|
+
|
|
391
|
+
// Copy assets if they exist
|
|
392
|
+
await copyLandingAssets(sourcePath, projectDir);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Generate Astro page from a full HTML document.
|
|
397
|
+
* Extracts <head> and <body> content, preserves the user's structure.
|
|
398
|
+
*/
|
|
399
|
+
function generateAstroFromFullDoc(parsed, { cssFiles, jsContent, navbarContent, footerContent }) {
|
|
400
|
+
const { htmlAttrs, headContent, bodyContent, bodyAttrs } = parsed;
|
|
401
|
+
|
|
402
|
+
// Determine header/footer rendering
|
|
403
|
+
const navbarIsHidden = navbarContent === '__hidden__';
|
|
404
|
+
const footerIsHidden = footerContent === '__hidden__';
|
|
405
|
+
const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
|
|
406
|
+
const hasCustomFooter = !footerIsHidden && !!footerContent;
|
|
407
|
+
|
|
408
|
+
const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
|
|
409
|
+
const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
|
|
410
|
+
const headerRender = navbarIsHidden
|
|
411
|
+
? ''
|
|
412
|
+
: hasCustomNavbar
|
|
413
|
+
? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
|
|
414
|
+
: '<Header />';
|
|
415
|
+
const footerRender = footerIsHidden
|
|
416
|
+
? ''
|
|
417
|
+
: hasCustomFooter
|
|
418
|
+
? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
|
|
419
|
+
: '<Footer />';
|
|
420
|
+
|
|
421
|
+
return `---
|
|
422
|
+
// Custom landing page - auto-generated by Lito CLI
|
|
423
|
+
// Source: _landing/ folder (full HTML document)
|
|
424
|
+
import '../styles/landing.css';
|
|
425
|
+
${headerImport}
|
|
426
|
+
${footerImport}
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
<!doctype html>
|
|
430
|
+
<html ${htmlAttrs}>
|
|
431
|
+
<head>
|
|
432
|
+
${headContent}
|
|
433
|
+
</head>
|
|
434
|
+
<body ${bodyAttrs}>
|
|
435
|
+
${headerRender}
|
|
436
|
+
|
|
437
|
+
${bodyContent}
|
|
438
|
+
|
|
439
|
+
${footerRender}
|
|
440
|
+
|
|
441
|
+
${jsContent ? `<script is:inline>\n${jsContent}\n</script>` : ''}
|
|
442
|
+
</body>
|
|
443
|
+
</html>
|
|
444
|
+
`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Generate Astro page from an HTML fragment.
|
|
449
|
+
* Wraps it in a full Astro page with Lito's defaults.
|
|
450
|
+
*/
|
|
451
|
+
function generateAstroFromFragment(htmlContent, { jsContent, navbarContent, footerContent }) {
|
|
452
|
+
const navbarIsHidden = navbarContent === '__hidden__';
|
|
453
|
+
const footerIsHidden = footerContent === '__hidden__';
|
|
454
|
+
const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
|
|
455
|
+
const hasCustomFooter = !footerIsHidden && !!footerContent;
|
|
456
|
+
|
|
457
|
+
const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
|
|
458
|
+
const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
|
|
459
|
+
const headerRender = navbarIsHidden
|
|
460
|
+
? ''
|
|
461
|
+
: hasCustomNavbar
|
|
462
|
+
? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
|
|
463
|
+
: '<Header />';
|
|
464
|
+
const footerRender = footerIsHidden
|
|
465
|
+
? ''
|
|
466
|
+
: hasCustomFooter
|
|
467
|
+
? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
|
|
468
|
+
: '<Footer />';
|
|
469
|
+
|
|
470
|
+
return `---
|
|
243
471
|
// Custom landing page - auto-generated by Lito CLI
|
|
244
472
|
// Source: _landing/ folder
|
|
245
473
|
import '../styles/global.css';
|
|
246
|
-
import
|
|
247
|
-
|
|
474
|
+
import '../styles/landing.css';
|
|
475
|
+
${headerImport}
|
|
476
|
+
${footerImport}
|
|
248
477
|
import { getConfigFile } from '../utils/config';
|
|
249
478
|
|
|
250
479
|
const config = await getConfigFile();
|
|
@@ -269,30 +498,20 @@ const config = await getConfigFile();
|
|
|
269
498
|
document.documentElement.classList.add('light');
|
|
270
499
|
}
|
|
271
500
|
</script>
|
|
272
|
-
<style>
|
|
273
|
-
${cssContent}
|
|
274
|
-
</style>
|
|
275
501
|
</head>
|
|
276
502
|
<body class="bg-background text-foreground font-sans antialiased">
|
|
277
|
-
|
|
503
|
+
${headerRender}
|
|
278
504
|
|
|
279
505
|
<main class="landing-custom">
|
|
280
506
|
${htmlContent}
|
|
281
507
|
</main>
|
|
282
508
|
|
|
283
|
-
|
|
509
|
+
${footerRender}
|
|
284
510
|
|
|
285
|
-
${jsContent ? `<script>\n${jsContent}\n</script>` : ''}
|
|
511
|
+
${jsContent ? `<script is:inline>\n${jsContent}\n</script>` : ''}
|
|
286
512
|
</body>
|
|
287
513
|
</html>
|
|
288
514
|
`;
|
|
289
|
-
|
|
290
|
-
// Write to index.astro
|
|
291
|
-
const indexPath = join(projectDir, 'src', 'pages', 'index.astro');
|
|
292
|
-
await writeFile(indexPath, astroContent, 'utf-8');
|
|
293
|
-
|
|
294
|
-
// Copy assets if they exist
|
|
295
|
-
await copyLandingAssets(sourcePath, projectDir);
|
|
296
515
|
}
|
|
297
516
|
|
|
298
517
|
/**
|
|
@@ -571,7 +790,7 @@ async function generateSectionsLandingForFramework(projectDir, frameworkConfig,
|
|
|
571
790
|
* Generate Astro sections landing page
|
|
572
791
|
*/
|
|
573
792
|
async function generateAstroSectionsLanding(projectDir, landingData) {
|
|
574
|
-
const { sections, config } = landingData;
|
|
793
|
+
const { sections, navbarContent, footerContent, config } = landingData;
|
|
575
794
|
|
|
576
795
|
// Generate section renders
|
|
577
796
|
const sectionRenders = sections.map((section, index) => {
|
|
@@ -591,11 +810,30 @@ async function generateAstroSectionsLanding(projectDir, landingData) {
|
|
|
591
810
|
}
|
|
592
811
|
}).join('\n');
|
|
593
812
|
|
|
813
|
+
// Determine header/footer: hidden ('__hidden__'), custom (string HTML), or default (null)
|
|
814
|
+
const navbarIsHidden = navbarContent === '__hidden__';
|
|
815
|
+
const footerIsHidden = footerContent === '__hidden__';
|
|
816
|
+
const hasCustomNavbar = !navbarIsHidden && !!navbarContent;
|
|
817
|
+
const hasCustomFooter = !footerIsHidden && !!footerContent;
|
|
818
|
+
|
|
819
|
+
const headerImport = navbarIsHidden || hasCustomNavbar ? '' : "import Header from '../components/Header.astro';";
|
|
820
|
+
const footerImport = footerIsHidden || hasCustomFooter ? '' : "import Footer from '../components/Footer.astro';";
|
|
821
|
+
const headerRender = navbarIsHidden
|
|
822
|
+
? ''
|
|
823
|
+
: hasCustomNavbar
|
|
824
|
+
? `<header class="landing-custom-navbar">\n ${inlineForAstro(navbarContent)}\n </header>`
|
|
825
|
+
: '<Header />';
|
|
826
|
+
const footerRender = footerIsHidden
|
|
827
|
+
? ''
|
|
828
|
+
: hasCustomFooter
|
|
829
|
+
? `<footer class="landing-custom-footer">\n ${inlineForAstro(footerContent)}\n </footer>`
|
|
830
|
+
: '<Footer />';
|
|
831
|
+
|
|
594
832
|
const astroContent = `---
|
|
595
833
|
// Sections-based landing page - auto-generated by Lito CLI
|
|
596
834
|
import '../styles/global.css';
|
|
597
|
-
|
|
598
|
-
|
|
835
|
+
${headerImport}
|
|
836
|
+
${footerImport}
|
|
599
837
|
import { getConfigFile } from '../utils/config';
|
|
600
838
|
|
|
601
839
|
const config = await getConfigFile();
|
|
@@ -621,13 +859,13 @@ const config = await getConfigFile();
|
|
|
621
859
|
</script>
|
|
622
860
|
</head>
|
|
623
861
|
<body class="bg-background text-foreground font-sans antialiased">
|
|
624
|
-
|
|
862
|
+
${headerRender}
|
|
625
863
|
|
|
626
864
|
<main class="landing-sections">
|
|
627
865
|
${sectionRenders}
|
|
628
866
|
</main>
|
|
629
867
|
|
|
630
|
-
|
|
868
|
+
${footerRender}
|
|
631
869
|
</body>
|
|
632
870
|
</html>
|
|
633
871
|
`;
|
package/src/core/sync.js
CHANGED
|
@@ -13,7 +13,7 @@ const KNOWN_LOCALES = [
|
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
// Special folders that are not content
|
|
16
|
-
const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', 'public'];
|
|
16
|
+
const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', '_navbar', '_footer', 'public'];
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Get i18n configuration from docs-config.json
|