@moonwave99/goffre 0.0.4
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/LICENSE +22 -0
- package/README.md +125 -0
- package/index.js +1 -0
- package/lib/goffre.js +297 -0
- package/lib/helpers.js +84 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Diego Caponera <hello@diegocaponera.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Goffre
|
|
2
|
+
|
|
3
|
+
Goffre is a minimal static site generator available to the **node.js** ecosystem.
|
|
4
|
+
|
|
5
|
+
It uses [handlebars][handlebars] as templating system and [markdown + frontmatter][mdfront] as data layer, or whatever you decide to pass to `render()`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ npm install goffre --save
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { load, render } from "goffre";
|
|
17
|
+
|
|
18
|
+
(async () => {
|
|
19
|
+
const { pages } = await load();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const results = await render({ pages });
|
|
23
|
+
console.log(`Generated ${results.length} pages`);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.log("Error generating site", error);
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Default paths:
|
|
31
|
+
|
|
32
|
+
- **markdown files**: `./data` - used by `load()`
|
|
33
|
+
- **output folder**: `./dist` - used by `render()`
|
|
34
|
+
- **handlebars views**: `./src/views` - used by `render()`
|
|
35
|
+
|
|
36
|
+
See [examples](#examples) for a more advanced use case, and the [documentation][docs] for the complete reference.
|
|
37
|
+
|
|
38
|
+
## Data collecting and rendering are separate steps
|
|
39
|
+
|
|
40
|
+
This is the key for the maximum flexibility: `load()` gets all the `.md` files inside the data folder, and populates its return `pages` each with a unique `slug`.
|
|
41
|
+
|
|
42
|
+
The markdown body is available in the `content` key, and the YAML front matter is destructured - the output of `load()` of the following file:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
---
|
|
46
|
+
title: "Goffre | Mini static site generator"
|
|
47
|
+
slug: "index"
|
|
48
|
+
---
|
|
49
|
+
Goffre is a minimal static site generator available to the **node.js** ecosystem.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
will be:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
{
|
|
56
|
+
title: "Goffre | Mini static site generator",
|
|
57
|
+
slug: "index",
|
|
58
|
+
content: "Goffre is a minimal static site generator available to the **node.js** ecosystem."
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Note:** the markdown body is not yet parsed at this stage.
|
|
63
|
+
|
|
64
|
+
The `render()` method writes then every incoming page to `{page.slug}.html` - you can add further pages to the collected ones, like the text you are reading from the main `README.md` file of the repository:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const { pages } = await load({ dataPath });
|
|
68
|
+
const results = await render({
|
|
69
|
+
buildPath,
|
|
70
|
+
sitePath,
|
|
71
|
+
pages: [
|
|
72
|
+
...pages,
|
|
73
|
+
{
|
|
74
|
+
title: "Goffre | Mini static site generator",
|
|
75
|
+
description:
|
|
76
|
+
"Goffre is a minimal static site generator available to the node.js ecosystem.",
|
|
77
|
+
slug: "index",
|
|
78
|
+
content: await readFile(path.join("..", "README.md"), "utf8"),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## For a better development experience
|
|
85
|
+
|
|
86
|
+
Goffre does not provide any watching / serving features out of the box, but don't worry.
|
|
87
|
+
|
|
88
|
+
**Serving**: if you use [webpack][webpack] for bundling the frontend CSS and JS, just use its [dev server][webpack-dev-server] - see the [configuration file for this very page][webpack-config] as reference. If you don't, a simple [http-server][http-server] will do.
|
|
89
|
+
|
|
90
|
+
**Watching**: use [nodemon][nodemon] to watch the _generation script_, the _data folder_ and the _handlebars views folder_:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
$ nodemon -e js,json,md,handlebars --watch index.js --watch data --watch src/views
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The scripts of `package.json` will look more or less like:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"clean": "rm -rf dist",
|
|
101
|
+
"dev:client": "webpack serve --mode development",
|
|
102
|
+
"dev:site": "nodemon -e js,json,md,handlebars --watch index.js --watch data --watch src/views",
|
|
103
|
+
"build:client": "webpack --mode production",
|
|
104
|
+
"build:site": "node index.js"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Just `npm run dev:client` and `npm run dev:site` in two terminal tabs and you are done. Don't forget to `npm install` the needed dependencies of course!
|
|
109
|
+
|
|
110
|
+
## Examples
|
|
111
|
+
|
|
112
|
+
- [devblog][examples-devblog] - a personal website with blog posts and project pages
|
|
113
|
+
- this page of course
|
|
114
|
+
|
|
115
|
+
[handlebars]: https://handlebarsjs.com/
|
|
116
|
+
[express-handlebars]: https://www.npmjs.com/package/express-handlebars
|
|
117
|
+
[mdfront]: https://www.google.com/search?q=markdown+frontmatter
|
|
118
|
+
[webpack]: https://webpack.js.org/
|
|
119
|
+
[webpack-dev-server]: https://webpack.js.org/configuration/dev-server/
|
|
120
|
+
[http-server]: https://www.npmjs.com/package/http-server
|
|
121
|
+
[nodemon]: https://www.npmjs.com/package/nodemon
|
|
122
|
+
[example]: https://github.com/moonwave99/goffre/tree/main/examples/devblog
|
|
123
|
+
[docs]: https://github.com/moonwave99/goffre/tree/main/examples/devblog
|
|
124
|
+
[webpack-config]: https://github.com/moonwave99/goffre/blob/main/homepage/webpack.config.cjs
|
|
125
|
+
[examples-devblog]: https://goffre-examples-devblog.netlify.app/
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/goffre.js";
|
package/lib/goffre.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { globby } from "globby";
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import fs from "fs-extra";
|
|
7
|
+
import express from "express";
|
|
8
|
+
import { engine } from "express-handlebars";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import slugify from "slugify";
|
|
11
|
+
import * as defaultHelpers from "./helpers.js";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { readFile, outputFile } = fs;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_DATA_PATH = path.join(process.cwd(), "data");
|
|
17
|
+
const DEFAULT_VIEWS_PATH = path.join(process.cwd(), "src", "views");
|
|
18
|
+
const DEFAULT_BUILD_PATH = path.join(process.cwd(), "dist");
|
|
19
|
+
const MAX_SLUG_LOG_LENGTH = 40;
|
|
20
|
+
|
|
21
|
+
function log() {
|
|
22
|
+
console.log.apply(
|
|
23
|
+
null,
|
|
24
|
+
["[goffre]", ...arguments].map((x) => chalk.cyan(x))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getEnv() {
|
|
29
|
+
return {
|
|
30
|
+
mode: process.env.MODE || "dev",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function stringify(token) {
|
|
35
|
+
if (token instanceof Date) {
|
|
36
|
+
return token.toISOString().split("T")[0];
|
|
37
|
+
}
|
|
38
|
+
return token;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getSlug(slug, params) {
|
|
42
|
+
return slug
|
|
43
|
+
.split("/")
|
|
44
|
+
.reduce(
|
|
45
|
+
(memo, x) =>
|
|
46
|
+
!x.startsWith(":")
|
|
47
|
+
? [...memo, x]
|
|
48
|
+
: [
|
|
49
|
+
...memo,
|
|
50
|
+
slugify(stringify(params[x.slice(1)]), {
|
|
51
|
+
lower: true,
|
|
52
|
+
strict: true,
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
[]
|
|
56
|
+
)
|
|
57
|
+
.join("/");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getTemplate({ page, templates, defaultTemplate = "_default" }) {
|
|
61
|
+
if (page.template) {
|
|
62
|
+
return page.template;
|
|
63
|
+
}
|
|
64
|
+
if (templates.find((x) => x.startsWith(page.slug))) {
|
|
65
|
+
return page.slug;
|
|
66
|
+
}
|
|
67
|
+
return defaultTemplate;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderPage({ app, templates, buildPath, maxSlugLogLength, ...page }) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const template = getTemplate({ page, templates });
|
|
73
|
+
|
|
74
|
+
switch (app.locals.options.logLevel) {
|
|
75
|
+
case "silent":
|
|
76
|
+
break;
|
|
77
|
+
case "verbose":
|
|
78
|
+
log(
|
|
79
|
+
`Generating ${chalk.yellow(
|
|
80
|
+
page.slug.padEnd(maxSlugLogLength, " ")
|
|
81
|
+
)} with template ${chalk.green(template)}...`
|
|
82
|
+
);
|
|
83
|
+
break;
|
|
84
|
+
case "normal":
|
|
85
|
+
default:
|
|
86
|
+
log(`Generating ${chalk.yellow(page.slug)}...`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
app.render(
|
|
90
|
+
template,
|
|
91
|
+
{
|
|
92
|
+
...page,
|
|
93
|
+
layout:
|
|
94
|
+
typeof page.layout === "undefined" ? "main" : page.layout,
|
|
95
|
+
content: page.content ? marked.parse(page.content) : null,
|
|
96
|
+
},
|
|
97
|
+
async (error, html) => {
|
|
98
|
+
if (error) {
|
|
99
|
+
reject(error);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const outputFileName = `${page.slug}${page.extname || ".html"}`;
|
|
103
|
+
await outputFile(path.join(buildPath, outputFileName), html);
|
|
104
|
+
resolve({
|
|
105
|
+
...page,
|
|
106
|
+
outputFileName,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function load({ dataPath } = {}) {
|
|
114
|
+
return {
|
|
115
|
+
json: await loadJSON(dataPath || DEFAULT_DATA_PATH),
|
|
116
|
+
pages: await loadMarkdown(dataPath || DEFAULT_DATA_PATH),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function loadJSON(cwd) {
|
|
121
|
+
const files = await globby("**/*.json", { cwd });
|
|
122
|
+
return files.reduce(
|
|
123
|
+
(memo, x) => ({
|
|
124
|
+
...memo,
|
|
125
|
+
[path.basename(x, ".json")]: require(path.join(cwd, x)),
|
|
126
|
+
}),
|
|
127
|
+
{}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function excerpt(file) {
|
|
132
|
+
file.excerpt = file.content.split("\n")[1];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function loadMarkdown(cwd) {
|
|
136
|
+
const files = await globby("**/*.md", { cwd });
|
|
137
|
+
return Promise.all(
|
|
138
|
+
files.map(async (fileName) => {
|
|
139
|
+
const fullPath = path.join(cwd, fileName);
|
|
140
|
+
const contents = await readFile(fullPath, "utf-8");
|
|
141
|
+
const parsed = matter(contents, { excerpt });
|
|
142
|
+
const outputFileName = fileName.replace(".md", "");
|
|
143
|
+
const slug = !parsed.data.slug
|
|
144
|
+
? outputFileName
|
|
145
|
+
: getSlug(parsed.data.slug, parsed.data);
|
|
146
|
+
return {
|
|
147
|
+
...parsed.data,
|
|
148
|
+
excerpt: parsed.excerpt,
|
|
149
|
+
slug,
|
|
150
|
+
description: parsed.data.description || parsed.excerpt,
|
|
151
|
+
content: parsed.content,
|
|
152
|
+
};
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const getSorter =
|
|
158
|
+
({ sortBy, order }) =>
|
|
159
|
+
(a, b) => {
|
|
160
|
+
let output;
|
|
161
|
+
if (a[sortBy] instanceof Date) {
|
|
162
|
+
output = new Date(a[sortBy]) - new Date(b[sortBy]);
|
|
163
|
+
} else {
|
|
164
|
+
output = a[sortBy] - b[sortBy];
|
|
165
|
+
}
|
|
166
|
+
return order === "desc" ? -output : output;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export async function render({
|
|
170
|
+
pages,
|
|
171
|
+
viewsPath = DEFAULT_VIEWS_PATH,
|
|
172
|
+
buildPath = DEFAULT_BUILD_PATH,
|
|
173
|
+
domain,
|
|
174
|
+
uglyUrls = false,
|
|
175
|
+
logLevel = "normal",
|
|
176
|
+
locals = {},
|
|
177
|
+
markdown = {},
|
|
178
|
+
handlebars = {},
|
|
179
|
+
sitemap = {},
|
|
180
|
+
env = {},
|
|
181
|
+
}) {
|
|
182
|
+
const extname = handlebars.extname || ".handlebars";
|
|
183
|
+
const app = express();
|
|
184
|
+
app.engine(
|
|
185
|
+
extname,
|
|
186
|
+
engine({
|
|
187
|
+
...handlebars,
|
|
188
|
+
helpers: {
|
|
189
|
+
...defaultHelpers,
|
|
190
|
+
...handlebars.helpers,
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
app.set("view engine", "handlebars");
|
|
195
|
+
app.set("layoutsDir", path.join(viewsPath, "layouts"));
|
|
196
|
+
app.set("views", viewsPath);
|
|
197
|
+
|
|
198
|
+
const templates = await globby(`**/*${extname}`, {
|
|
199
|
+
cwd: viewsPath,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
app.locals = {
|
|
203
|
+
...app.locals,
|
|
204
|
+
...locals,
|
|
205
|
+
options: {
|
|
206
|
+
domain,
|
|
207
|
+
uglyUrls,
|
|
208
|
+
logLevel,
|
|
209
|
+
},
|
|
210
|
+
env: { ...getEnv(), ...env },
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
marked.use(markdown);
|
|
214
|
+
|
|
215
|
+
switch (logLevel) {
|
|
216
|
+
case "silent":
|
|
217
|
+
break;
|
|
218
|
+
case "verbose":
|
|
219
|
+
case "normal":
|
|
220
|
+
default:
|
|
221
|
+
log(`Start generation...`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const results = await Promise.all(
|
|
225
|
+
pages.map((x) =>
|
|
226
|
+
renderPage({
|
|
227
|
+
...x,
|
|
228
|
+
buildPath,
|
|
229
|
+
app,
|
|
230
|
+
templates,
|
|
231
|
+
maxSlugLogLength: Math.min(
|
|
232
|
+
Math.max.call(null, ...pages.map((x) => x.slug.length)),
|
|
233
|
+
MAX_SLUG_LOG_LENGTH
|
|
234
|
+
),
|
|
235
|
+
})
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
switch (logLevel) {
|
|
240
|
+
case "silent":
|
|
241
|
+
break;
|
|
242
|
+
case "verbose":
|
|
243
|
+
case "normal":
|
|
244
|
+
default:
|
|
245
|
+
log(`Generated ${results.length} pages`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (sitemap.generate) {
|
|
249
|
+
renderPage({
|
|
250
|
+
slug: "sitemap",
|
|
251
|
+
template: sitemap.template || "sitemap",
|
|
252
|
+
extname: ".xml",
|
|
253
|
+
layout: null,
|
|
254
|
+
pages: results,
|
|
255
|
+
buildPath,
|
|
256
|
+
app,
|
|
257
|
+
templates,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return results;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function paginate({
|
|
265
|
+
collection,
|
|
266
|
+
size = 10,
|
|
267
|
+
sortBy = "slug",
|
|
268
|
+
order = "asc",
|
|
269
|
+
}) {
|
|
270
|
+
const total = Math.ceil(collection.length / size);
|
|
271
|
+
return collection
|
|
272
|
+
.sort(getSorter({ sortBy, order }))
|
|
273
|
+
.reduce((memo, x, index) => {
|
|
274
|
+
if (index % size === 0) {
|
|
275
|
+
const page = Math.floor(index / size) + 1;
|
|
276
|
+
return [
|
|
277
|
+
...memo,
|
|
278
|
+
{
|
|
279
|
+
pagination: {
|
|
280
|
+
page,
|
|
281
|
+
prev: page > 1 ? page - 1 : null,
|
|
282
|
+
next: page < total ? page + 1 : null,
|
|
283
|
+
total,
|
|
284
|
+
},
|
|
285
|
+
items: [x],
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
return [
|
|
290
|
+
...memo.slice(0, -1),
|
|
291
|
+
{
|
|
292
|
+
...memo[memo.length - 1],
|
|
293
|
+
items: [...memo[memo.length - 1].items, x],
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
}, []);
|
|
297
|
+
}
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { getSorter, getSlug } from "./goffre.js";
|
|
2
|
+
import { marked } from "marked";
|
|
3
|
+
|
|
4
|
+
export const markdown = (text) => marked(text);
|
|
5
|
+
|
|
6
|
+
export const getParamLink = (url, options) => {
|
|
7
|
+
const output = getSlug(url, options.hash);
|
|
8
|
+
return getAsset(output, options);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const getAsset = (asset, context) => {
|
|
12
|
+
const { options, env } = context.data.root;
|
|
13
|
+
return env.mode === "prod" && options.domain
|
|
14
|
+
? `${options.domain}${asset.startsWith("/") ? "" : "/"}${asset}`
|
|
15
|
+
: asset;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getSitemapLink = (page, context) => {
|
|
19
|
+
const { options } = context.data.root;
|
|
20
|
+
return `${options.domain}${getLink(page, context)}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getLink = (page, context) => {
|
|
24
|
+
const base =
|
|
25
|
+
page.link || `${page.slug.startsWith("/") ? "" : "/"}${page.slug}`;
|
|
26
|
+
const { uglyUrls } = context.data.root.options;
|
|
27
|
+
if (uglyUrls) {
|
|
28
|
+
return getAsset(`${base === "/" ? "/index" : base}.html`, context);
|
|
29
|
+
}
|
|
30
|
+
return getAsset(base.replace(/^\/index/, "/"), context);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getNavClass = ({ slug }, currentPage) => {
|
|
34
|
+
const cleanSlug = slug && slug[0] === "/" ? slug.slice(1) : slug;
|
|
35
|
+
return currentPage.slug.startsWith(cleanSlug)
|
|
36
|
+
? `${cleanSlug} current`
|
|
37
|
+
: cleanSlug;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const list = (context, options) => {
|
|
41
|
+
const offset = parseInt(options.hash.offset, 10) || 0;
|
|
42
|
+
const limit = parseInt(options.hash.limit, 10) || 100;
|
|
43
|
+
const sortBy = options.hash.sortBy || "slug";
|
|
44
|
+
const order = options.hash.order || "asc";
|
|
45
|
+
|
|
46
|
+
let output = "";
|
|
47
|
+
let i, j;
|
|
48
|
+
|
|
49
|
+
const data = [...context].sort(getSorter({ sortBy, order }));
|
|
50
|
+
|
|
51
|
+
if (offset < 0) {
|
|
52
|
+
i = -offset < data.length ? data.length - -offset : 0;
|
|
53
|
+
} else {
|
|
54
|
+
i = offset < data.length ? offset : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
j = limit + i < data.length ? limit + i : data.length;
|
|
58
|
+
|
|
59
|
+
for (i, j; i < j; i++) {
|
|
60
|
+
output += options.fn(data[i]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return output;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const nextItem = (context, options) => {
|
|
67
|
+
const { list } = options.hash;
|
|
68
|
+
const index = list.findIndex((x) => x.slug === context.slug);
|
|
69
|
+
const next = list[index + 1];
|
|
70
|
+
if (!next) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return options.fn(next);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const prevItem = (context, options) => {
|
|
77
|
+
const { list } = options.hash;
|
|
78
|
+
const index = list.findIndex((x) => x.slug === context.slug);
|
|
79
|
+
const prev = list[index - 1];
|
|
80
|
+
if (!prev) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
return options.fn(prev);
|
|
84
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moonwave99/goffre",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Mini static site generator",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Diego Caponera",
|
|
7
|
+
"email": "hello@diegocaponera.com",
|
|
8
|
+
"url": "https://github.com/moonwwave99"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/moonwave99/goffre.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://moonwave99.github.io/goffre",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "ava **/*.test.js",
|
|
18
|
+
"lint": "npx eslint ./lib --ext .js,.jsx,.ts,.tsx"
|
|
19
|
+
},
|
|
20
|
+
"main": "index.js",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"front-matter",
|
|
23
|
+
"generate",
|
|
24
|
+
"generator",
|
|
25
|
+
"markdown",
|
|
26
|
+
"static",
|
|
27
|
+
"static-site",
|
|
28
|
+
"yaml"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"lib/goffre.js",
|
|
32
|
+
"lib/helpers.js"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"chalk": "^4.1.2",
|
|
37
|
+
"express": "^4.17.1",
|
|
38
|
+
"express-handlebars": "^6.0.1",
|
|
39
|
+
"fs-extra": "^10.0.0",
|
|
40
|
+
"globby": "^12.0.2",
|
|
41
|
+
"gray-matter": "^4.0.3",
|
|
42
|
+
"marked": "^4.0.3",
|
|
43
|
+
"slugify": "^1.6.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"ava": "^3.15.0",
|
|
47
|
+
"cheerio": "^1.0.0-rc.10",
|
|
48
|
+
"dirname-filename-esm": "^1.1.1",
|
|
49
|
+
"eslint": "^8.3.0",
|
|
50
|
+
"faker": "^5.5.3",
|
|
51
|
+
"js-yaml": "^4.1.0",
|
|
52
|
+
"rimraf": "^3.0.2",
|
|
53
|
+
"sinon": "^12.0.1"
|
|
54
|
+
}
|
|
55
|
+
}
|