@mgks/docmd 0.2.5 → 0.2.6
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/README.md +65 -42
- package/bin/docmd.js +7 -2
- package/bin/postinstall.js +14 -0
- package/config.js +1 -1
- package/docs/overview.md +7 -1
- package/package.json +4 -1
- package/src/commands/build.js +15 -64
- package/src/core/html-generator.js +1 -1
- package/src/core/logger.js +21 -0
- package/src/core/navigation-helper.js +62 -0
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<b>Generate beautiful, lightweight static documentation sites from Markdown files.</b
|
|
9
|
-
Zero clutter, just content.
|
|
8
|
+
<b>Generate beautiful, lightweight static documentation sites from Markdown files.</b>
|
|
9
|
+
<br>Zero clutter, just content.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -15,71 +15,94 @@
|
|
|
15
15
|
<a href="https://github.com/mgks/docmd/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mgks/docmd.svg" alt="license"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
Docmd
|
|
18
|
+
Docmd is a Node.js command-line tool for generating fast, beautiful, and lightweight static documentation sites from standard Markdown files. It champions the philosophy of "zero clutter, just content," prioritizing a simple authoring experience and a clean, performant result for readers.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**:rocket: [Live Preview](https://docmd.mgks.dev): Official documentation site powered by `docmd`.**
|
|
21
21
|
|
|
22
|
-
## Features
|
|
22
|
+
## Key Features
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
24
|
+
- **Markdown First:** Write your content in standard Markdown with simple YAML frontmatter.
|
|
25
|
+
- **Beautiful Themes:** Comes with multiple built-in themes (`sky`, `ruby`, `retro`) and automatic light/dark mode support.
|
|
26
|
+
- **Fast & Lightweight:** Blazing fast static site generation with a minimal client-side footprint.
|
|
27
|
+
- **Rich Content:** Go beyond basic Markdown with custom components like callouts, cards, steps, tabs, and Mermaid diagrams.
|
|
28
|
+
- **Built-in Plugins:** SEO meta tags, sitemap, and analytics are all included out-of-the-box.
|
|
29
|
+
- **No-Style Pages:** Create completely custom pages (like landing pages) with full control over the HTML.
|
|
30
|
+
- **Customizable:** Easily extend or override styles with your own CSS and JavaScript.
|
|
31
|
+
- **Simple CLI:** A straightforward workflow with three main commands: `init`, `dev`, and `build`.
|
|
32
|
+
- **Deploy Anywhere:** The generated `site/` folder can be hosted on any static web host (GitHub Pages, Netlify, Vercel, etc.).
|
|
33
33
|
|
|
34
34
|
## Installation
|
|
35
|
+
**Prerequisites:** [Node.js](https://nodejs.org/) (version 22.x or higher)
|
|
35
36
|
|
|
36
|
-
###
|
|
37
|
+
### Quick Start: Your First Site in 60 Seconds
|
|
38
|
+
|
|
39
|
+
No global installation is required. You can create and run your site in a new folder with one command.
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
#
|
|
40
|
-
|
|
42
|
+
# Create a new project in 'my-docs' and navigate into it
|
|
43
|
+
npx @mgks/docmd init my-docs && cd my-docs
|
|
41
44
|
|
|
42
|
-
#
|
|
43
|
-
npm
|
|
45
|
+
# Start the development server
|
|
46
|
+
npm start
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
Your new documentation site is now running at `http://localhost:3000` *(or, at your selected or available port)*.
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
# Initialize a new documentation project
|
|
50
|
-
docmd init
|
|
51
|
+
### Global Installation
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
docmd dev
|
|
53
|
+
For frequent use, or if you prefer to have the command available system-wide, you can install `docmd` globally using npm.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
docmd
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g @mgks/docmd
|
|
57
57
|
```
|
|
58
|
+
After installation, you can run the `docmd` commands from any directory.
|
|
58
59
|
|
|
59
|
-
###
|
|
60
|
+
### Basic Workflow
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
1. **Initialize a Project:**
|
|
63
|
+
```bash
|
|
64
|
+
docmd init
|
|
65
|
+
```
|
|
66
|
+
This creates a `docs/` directory, a `docmd.config.js` file, and a sample `index.md` to get you started.
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
2. **Start the Dev Server:**
|
|
69
|
+
```bash
|
|
70
|
+
docmd dev
|
|
71
|
+
```
|
|
72
|
+
This starts a live-reloading server to preview your site as you write.
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
3. **Build for Production:**
|
|
75
|
+
```bash
|
|
76
|
+
docmd build
|
|
77
|
+
```
|
|
78
|
+
This generates the complete, optimized static site into the `site/` directory, ready for deployment.
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/docmd.git`
|
|
69
|
-
3. Install dependencies: `npm install`
|
|
70
|
-
4. Make your changes and test them
|
|
71
|
-
5. Submit a pull request
|
|
80
|
+
## Documentation
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
For a complete guide covering all features, including theming, custom containers, and plugin configuration, please visit the official documentation website: **[docmd.mgks.dev](https://docmd.mgks.dev)**.
|
|
83
|
+
|
|
84
|
+
## Contributing
|
|
85
|
+
|
|
86
|
+
We welcome contributions of all kinds! Whether it's reporting a bug, suggesting a feature, or submitting a pull request, your help is appreciated.
|
|
74
87
|
|
|
75
|
-
|
|
88
|
+
1. Fork the repository.
|
|
89
|
+
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/docmd.git`
|
|
90
|
+
3. Install dependencies: `npm install`
|
|
91
|
+
4. Make your changes and test them thoroughly.
|
|
92
|
+
5. Submit a pull request to the `main` branch.
|
|
93
|
+
|
|
94
|
+
Please check our [contributing guidelines](https://docmd.mgks.dev/contributing/) for more detailed information.
|
|
76
95
|
|
|
77
96
|
## Support the Project
|
|
78
97
|
|
|
79
98
|
If you find `docmd` useful, please consider:
|
|
80
99
|
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
100
|
+
- Starring the repository on GitHub.
|
|
101
|
+
- Sharing it with others who might benefit.
|
|
102
|
+
- Reporting issues or submitting pull requests.
|
|
103
|
+
|
|
104
|
+
❤️ **[GitHub Sponsors](https://github.com/sponsors/mgks): Become a sponsor to support the ongoing development of `docmd`.**
|
|
105
|
+
|
|
106
|
+
## License
|
|
84
107
|
|
|
85
|
-
|
|
108
|
+
Docmd is licensed under the [MIT License](https://github.com/mgks/docmd/blob/main/LICENSE).
|
package/bin/docmd.js
CHANGED
|
@@ -7,6 +7,7 @@ const { version } = require('../package.json');
|
|
|
7
7
|
const { initProject } = require('../src/commands/init');
|
|
8
8
|
const { buildSite } = require('../src/commands/build');
|
|
9
9
|
const { startDevServer } = require('../src/commands/dev');
|
|
10
|
+
const { printBanner } = require('../src/core/logger');
|
|
10
11
|
|
|
11
12
|
// Helper function to find the config file
|
|
12
13
|
const findConfigFile = () => {
|
|
@@ -52,6 +53,8 @@ program
|
|
|
52
53
|
.option('--silent', 'Suppress log output')
|
|
53
54
|
.action(async (options) => {
|
|
54
55
|
try {
|
|
56
|
+
if (!options.silent) { printBanner(); }
|
|
57
|
+
|
|
55
58
|
const originalLog = console.log;
|
|
56
59
|
if (options.silent) { console.log = () => {}; }
|
|
57
60
|
|
|
@@ -68,7 +71,7 @@ program
|
|
|
68
71
|
|
|
69
72
|
} catch (error) {
|
|
70
73
|
console.error('❌ Build failed:', error.message);
|
|
71
|
-
console.error(error.stack);
|
|
74
|
+
// console.error(error.stack);
|
|
72
75
|
process.exit(1);
|
|
73
76
|
}
|
|
74
77
|
});
|
|
@@ -83,6 +86,8 @@ program
|
|
|
83
86
|
.option('--silent', 'Suppress log output')
|
|
84
87
|
.action(async (options) => {
|
|
85
88
|
try {
|
|
89
|
+
if (!options.silent) { printBanner(); }
|
|
90
|
+
|
|
86
91
|
if (options.silent) {
|
|
87
92
|
const originalLog = console.log;
|
|
88
93
|
console.log = (message) => {
|
|
@@ -96,7 +101,7 @@ program
|
|
|
96
101
|
|
|
97
102
|
} catch (error) {
|
|
98
103
|
console.error('❌ Dev server failed:', error.message);
|
|
99
|
-
console.error(error.stack);
|
|
104
|
+
// console.error(error.stack);
|
|
100
105
|
process.exit(1);
|
|
101
106
|
}
|
|
102
107
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// bin/postinstall.js
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
// This script runs after 'npm install', runs only when the user installs it globally and not in a CI environment
|
|
6
|
+
|
|
7
|
+
if (process.env.npm_config_global && !process.env.CI) {
|
|
8
|
+
console.log(chalk.green('\n🎉 Thank you for installing docmd!'));
|
|
9
|
+
console.log('\nTo get started, run the following commands:');
|
|
10
|
+
console.log(`\n ${chalk.cyan('docmd init my-awesome-docs')}`);
|
|
11
|
+
console.log(` ${chalk.cyan('cd my-awesome-docs')}`);
|
|
12
|
+
console.log(` ${chalk.cyan('npm start')}`);
|
|
13
|
+
console.log('\nFor complete documentation, visit: https://docmd.mgks.dev');
|
|
14
|
+
}
|
package/config.js
CHANGED
|
@@ -169,7 +169,7 @@ module.exports = {
|
|
|
169
169
|
// Sponsor Ribbon Configuration
|
|
170
170
|
sponsor: {
|
|
171
171
|
enabled: true, // Enable/disable the sponsor ribbon
|
|
172
|
-
title: '
|
|
172
|
+
title: 'Sponsor', // Text to display on the ribbon
|
|
173
173
|
link: 'https://github.com/sponsors/mgks', // URL for the sponsor link
|
|
174
174
|
},
|
|
175
175
|
|
package/docs/overview.md
CHANGED
|
@@ -3,7 +3,13 @@ title: "Minimalist Markdown Docs Generator"
|
|
|
3
3
|
description: "Generate beautiful, lightweight static documentation sites directly from your Markdown files with docmd. Zero clutter, just content."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
```
|
|
7
|
+
_ _
|
|
8
|
+
_| |___ ___ _____ _| |
|
|
9
|
+
| . | . | _| | . |
|
|
10
|
+
|___|___|___|_|_|_|___|
|
|
11
|
+
|
|
12
|
+
```
|
|
7
13
|
|
|
8
14
|
**Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.**
|
|
9
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgks/docmd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node ./bin/docmd.js dev",
|
|
17
17
|
"build": "node ./bin/docmd.js build",
|
|
18
|
+
"postinstall": "node ./bin/postinstall.js",
|
|
18
19
|
"lint": "eslint .",
|
|
19
20
|
"format": "prettier --write .",
|
|
20
21
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -41,6 +42,8 @@
|
|
|
41
42
|
},
|
|
42
43
|
"homepage": "https://github.com/mgks/docmd#readme",
|
|
43
44
|
"dependencies": {
|
|
45
|
+
"@mgks/docmd": "^0.2.5",
|
|
46
|
+
"chalk": "^4.1.2",
|
|
44
47
|
"chokidar": "^4.0.3",
|
|
45
48
|
"commander": "^14.0.0",
|
|
46
49
|
"ejs": "^3.1.9",
|
package/src/commands/build.js
CHANGED
|
@@ -6,6 +6,7 @@ const { loadConfig } = require('../core/config-loader');
|
|
|
6
6
|
const { createMarkdownItInstance, processMarkdownFile, findMarkdownFiles } = require('../core/file-processor');
|
|
7
7
|
const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
|
|
8
8
|
const { renderIcon, clearWarnedIcons } = require('../core/icon-renderer');
|
|
9
|
+
const { findPageNeighbors } = require('../core/navigation-helper');
|
|
9
10
|
const { generateSitemap } = require('../plugins/sitemap');
|
|
10
11
|
const { version } = require('../../package.json');
|
|
11
12
|
const matter = require('gray-matter');
|
|
@@ -218,8 +219,12 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
218
219
|
|
|
219
220
|
const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
let relativePathToRoot = path.relative(path.dirname(finalOutputHtmlPath), OUTPUT_DIR);
|
|
223
|
+
if (relativePathToRoot === '') {
|
|
224
|
+
relativePathToRoot = './';
|
|
225
|
+
} else {
|
|
226
|
+
relativePathToRoot = relativePathToRoot.replace(/\\/g, '/') + '/';
|
|
227
|
+
}
|
|
223
228
|
|
|
224
229
|
let normalizedPath = path.relative(SRC_DIR, filePath).replace(/\\/g, '/');
|
|
225
230
|
if (path.basename(normalizedPath) === 'index.md') {
|
|
@@ -247,74 +252,20 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
|
|
|
247
252
|
config
|
|
248
253
|
);
|
|
249
254
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const flatNavigation = [];
|
|
255
|
-
|
|
256
|
-
function createNormalizedPath(item) {
|
|
257
|
-
if (!item.path) return null;
|
|
258
|
-
return item.path.startsWith('/') ? item.path : '/' + item.path;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function extractNavigationItems(items) {
|
|
262
|
-
if (!items || !Array.isArray(items)) return;
|
|
263
|
-
|
|
264
|
-
for (const item of items) {
|
|
265
|
-
if (item.external) continue;
|
|
266
|
-
|
|
267
|
-
if (item.path) {
|
|
268
|
-
let normalizedItemPath = createNormalizedPath(item);
|
|
269
|
-
if (item.children && !normalizedItemPath.endsWith('/')) {
|
|
270
|
-
normalizedItemPath += '/';
|
|
271
|
-
}
|
|
272
|
-
flatNavigation.push({
|
|
273
|
-
title: item.title,
|
|
274
|
-
path: normalizedItemPath,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (item.children && Array.isArray(item.children)) {
|
|
279
|
-
extractNavigationItems(item.children);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
extractNavigationItems(config.navigation);
|
|
285
|
-
|
|
286
|
-
currentPageIndex = flatNavigation.findIndex(item => {
|
|
287
|
-
const itemPath = item.path;
|
|
288
|
-
const currentPagePath = normalizedPath;
|
|
289
|
-
if (itemPath === currentPagePath) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
if (itemPath.endsWith('/') && itemPath.slice(0, -1) === currentPagePath) {
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
if (currentPagePath.endsWith('/') && currentPagePath.slice(0, -1) === itemPath) {
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (currentPageIndex >= 0) {
|
|
302
|
-
if (currentPageIndex > 0) prevPage = flatNavigation[currentPageIndex - 1];
|
|
303
|
-
if (currentPageIndex < flatNavigation.length - 1) nextPage = flatNavigation[currentPageIndex + 1];
|
|
304
|
-
}
|
|
305
|
-
|
|
255
|
+
// Find previous and next pages for navigation
|
|
256
|
+
const { prevPage, nextPage } = findPageNeighbors(config.navigation, normalizedPath);
|
|
257
|
+
|
|
306
258
|
if (prevPage) {
|
|
307
|
-
const cleanPath = prevPage.path.
|
|
308
|
-
prevPage.url = relativePathToRoot +
|
|
309
|
-
if (prevPage.path === '/') prevPage.url = relativePathToRoot;
|
|
259
|
+
const cleanPath = prevPage.path.substring(1);
|
|
260
|
+
prevPage.url = relativePathToRoot + cleanPath;
|
|
310
261
|
}
|
|
311
262
|
|
|
312
263
|
if (nextPage) {
|
|
313
|
-
const cleanPath = nextPage.path.
|
|
314
|
-
nextPage.url = relativePathToRoot +
|
|
315
|
-
if (nextPage.path === '/') nextPage.url = relativePathToRoot;
|
|
264
|
+
const cleanPath = nextPage.path.substring(1);
|
|
265
|
+
nextPage.url = relativePathToRoot + cleanPath;
|
|
316
266
|
}
|
|
317
267
|
|
|
268
|
+
// Log navigation paths for debugging
|
|
318
269
|
const pageDataForTemplate = {
|
|
319
270
|
content: htmlContent,
|
|
320
271
|
pageTitle: pageFrontmatter.title || 'Untitled',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const { version } = require('../../package.json');
|
|
6
|
+
|
|
7
|
+
const printBanner = () => {
|
|
8
|
+
const logo = `
|
|
9
|
+
|
|
10
|
+
${chalk.blue(' _ _ ')}
|
|
11
|
+
${chalk.blue(' _| |___ ___ _____ _| |')}
|
|
12
|
+
${chalk.blue(' | . | . | _| | . |')}
|
|
13
|
+
${chalk.blue(' |___|___|___|_|_|_|___|')}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
console.log(logo);
|
|
17
|
+
console.log(` ${chalk.dim(`v${version}`)}`);
|
|
18
|
+
console.log(`\n`);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = { printBanner };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Source file from the docmd project — https://github.com/mgks/docmd
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flattens the navigation tree and finds the previous and next pages relative to the current page.
|
|
5
|
+
* @param {Array} navItems - The navigation array from config.
|
|
6
|
+
* @param {string} currentPagePath - The normalized path of the current page.
|
|
7
|
+
* @returns {{prevPage: object|null, nextPage: object|null}}
|
|
8
|
+
*/
|
|
9
|
+
function findPageNeighbors(navItems, currentPagePath) {
|
|
10
|
+
const flatNavigation = [];
|
|
11
|
+
|
|
12
|
+
// Recursive function to flatten the navigation tree
|
|
13
|
+
function extractNavigationItems(items) {
|
|
14
|
+
if (!items || !Array.isArray(items)) return;
|
|
15
|
+
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
if (item.external || !item.path || item.path === '#') {
|
|
18
|
+
// If it's a category with no path but has children, recurse into them
|
|
19
|
+
if (item.children && Array.isArray(item.children)) {
|
|
20
|
+
extractNavigationItems(item.children);
|
|
21
|
+
}
|
|
22
|
+
continue; // Skip external links and parent items without a direct path
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let normalizedItemPath = item.path;
|
|
26
|
+
|
|
27
|
+
// Ensure it starts with a slash
|
|
28
|
+
if (!normalizedItemPath.startsWith('/')) {
|
|
29
|
+
normalizedItemPath = '/' + normalizedItemPath;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure it ends with a slash (unless it's the root path)
|
|
33
|
+
if (normalizedItemPath.length > 1 && !normalizedItemPath.endsWith('/')) {
|
|
34
|
+
normalizedItemPath += '/';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
flatNavigation.push({
|
|
38
|
+
title: item.title,
|
|
39
|
+
path: normalizedItemPath,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (item.children && Array.isArray(item.children)) {
|
|
43
|
+
extractNavigationItems(item.children);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
extractNavigationItems(navItems);
|
|
49
|
+
|
|
50
|
+
const currentPageIndex = flatNavigation.findIndex(item => item.path === currentPagePath);
|
|
51
|
+
|
|
52
|
+
if (currentPageIndex === -1) {
|
|
53
|
+
return { prevPage: null, nextPage: null };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const prevPage = currentPageIndex > 0 ? flatNavigation[currentPageIndex - 1] : null;
|
|
57
|
+
const nextPage = currentPageIndex < flatNavigation.length - 1 ? flatNavigation[currentPageIndex + 1] : null;
|
|
58
|
+
|
|
59
|
+
return { prevPage, nextPage };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { findPageNeighbors };
|