@eit6609/storyteller 1.0.7 → 2.0.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/.eslintrc +1 -1
- package/README.md +30 -21
- package/package.json +5 -4
- package/src/generator.js +15 -6
- package/src/page.js +3 -3
- package/src/template.js +56 -12
package/.eslintrc
CHANGED
package/README.md
CHANGED
|
@@ -116,12 +116,13 @@ The methods `reset()` and `move()` are modifiers, and will be used with the `got
|
|
|
116
116
|
|
|
117
117
|
The pages of the ebook are generated by a set of XHTML templates, one for every location of the game.
|
|
118
118
|
|
|
119
|
-
After many experiments with the most popular templating engines for Node.js, I have chosen
|
|
120
|
-
[Pug](https://pugjs.org/api/getting-started.html) because it gives you enough freedom to call JavaScript code inside
|
|
121
|
-
the template. This is vital, but many engines (the very popular [Handlebars](https://handlebarsjs.com) among others)
|
|
122
|
-
make the call of methods on a class instance a nightmare.
|
|
119
|
+
After many experiments with the most popular templating engines for Node.js, I have chosen:
|
|
123
120
|
|
|
124
|
-
|
|
121
|
+
* [EJS](https://ejs.co)
|
|
122
|
+
* [Pug](https://pugjs.org/api/getting-started.html)
|
|
123
|
+
|
|
124
|
+
There are many engines (the very popular [Handlebars](https://handlebarsjs.com) among others) but most
|
|
125
|
+
of them make the call of methods on a class instance a nightmare.
|
|
125
126
|
|
|
126
127
|
I have added **experimental** support for markdown templating by means of my
|
|
127
128
|
[Markdown Templates](https://github.com/eit6609/markdown-templates) engine.
|
|
@@ -129,7 +130,7 @@ I have added **experimental** support for markdown templating by means of my
|
|
|
129
130
|
Let's see an example of template:
|
|
130
131
|
|
|
131
132
|
```pug
|
|
132
|
-
doctype
|
|
133
|
+
doctype 1.1
|
|
133
134
|
html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
|
|
134
135
|
head
|
|
135
136
|
link(href="style-epub.css", rel="stylesheet", type="text/css")
|
|
@@ -144,7 +145,7 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
|
|
|
144
145
|
li Peg 2: #{state.pegs[1]}
|
|
145
146
|
li Peg 3: #{state.pegs[2]}
|
|
146
147
|
if state.isFinished()
|
|
147
|
-
h1 YOU
|
|
148
|
+
h1 YOU WIN!
|
|
148
149
|
br
|
|
149
150
|
p.first Want to #[a(href=goto((state) => state.reset())) play again]?
|
|
150
151
|
else
|
|
@@ -235,14 +236,17 @@ These are the supported options:
|
|
|
235
236
|
as input for the [ePUB creator](https://github.com/eit6609/epub-creator), so you can put in this directory any extra
|
|
236
237
|
file (images, stylesheets) that you need in the ePUB.
|
|
237
238
|
* `metadata`, object, required, the options for the ePUB creator, with these properties:
|
|
238
|
-
* `title`, string, optional, default `
|
|
239
|
+
* `title`, string, optional, default `Untitled`: the title of the ePUB
|
|
239
240
|
* `author`, string, optional, default no author: the author of the ePUB
|
|
240
241
|
* `language`, string, optional, default `en`: the language of the ePUB
|
|
242
|
+
* `description`, string, optional, default no description: the description of the ePUB
|
|
243
|
+
* `isbn`, string, optional, default no ISBN: the ISBN of the ePUB
|
|
244
|
+
* `tags`, array of string, optional, default no subjects: the subjects of the ePUB
|
|
241
245
|
* `cover`, string, optional, default no cover: a path relative to `outputDir` of an image that will become the cover
|
|
242
246
|
of the ePUB
|
|
243
247
|
* `filename`, string, required: the path of the generated ePUB
|
|
244
|
-
* `
|
|
245
|
-
|
|
248
|
+
* `templateEngine`, string, required, possible values: `ejs`, `pug`, `mt`: the template engine used to
|
|
249
|
+
generate the pages.
|
|
246
250
|
* `debug`, boolean, optional, default `false`: if `true` the `debug()` function called in the templates will return the
|
|
247
251
|
page key, otherwise the empty string.
|
|
248
252
|
* `contentBefore`, array, optional, default `[]`. Extra, static pages to insert into the generated ePUB before the
|
|
@@ -287,15 +291,15 @@ The parameters are:
|
|
|
287
291
|
|
|
288
292
|
* `templateName`: the path of a template file, without the extension, relative to the `templatesDir`. It defaults to
|
|
289
293
|
the current template's name.
|
|
290
|
-
* `action`: a function that receives a state as its only parameter and modifies it. It should return a falsy
|
|
294
|
+
* `action`: a function that receives a clone of the state as its only parameter and modifies it. It should return a falsy
|
|
291
295
|
value unless it wants to replace the received state with a new one: in this case it should return the new state. This is
|
|
292
296
|
handy for complex games because it enables you to move through independent stages of the game. More about this later,
|
|
293
297
|
in the *Tips & Tricks* section.
|
|
294
298
|
|
|
295
299
|
With this function you actually trigger the generation of the pages, because, if the requested page does not exist,
|
|
296
|
-
the generator creates an empty page and enqueues it for the build, that is the execution of the template with
|
|
297
|
-
of the page. That execution could find and execute some `goto()` that could trigger the creation of new
|
|
298
|
-
on.
|
|
300
|
+
the generator creates an empty page and enqueues it for the build, that is the execution of the template with a clone
|
|
301
|
+
of the state of the page. That execution could find and execute some `goto()` that could trigger the creation of new
|
|
302
|
+
pages, and so on.
|
|
299
303
|
|
|
300
304
|
## Examples
|
|
301
305
|
|
|
@@ -313,14 +317,19 @@ But if you are lazy you can just download the generated ePUBs.
|
|
|
313
317
|
|
|
314
318
|
A classic puzzle!
|
|
315
319
|
|
|
316
|
-
There are
|
|
320
|
+
There are three scripts:
|
|
317
321
|
|
|
318
|
-
* `main-
|
|
322
|
+
* `main-pug.js`, that uses the Pug (.pug) templates
|
|
323
|
+
* `main-ejs.js`, that uses the EJS (.html) templates
|
|
319
324
|
* `main-markdown.js`, that uses the experimental Markdown Templates (.md) templates
|
|
320
325
|
|
|
321
326
|
The generated ePUBs should be the same.
|
|
322
327
|
|
|
323
|
-
You can download the generated
|
|
328
|
+
You can download the generated ePUBs here:
|
|
329
|
+
|
|
330
|
+
* [pug](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-pug.epub)
|
|
331
|
+
* [ejs](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-ejs.epub)
|
|
332
|
+
* [markdown](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-markdown.epub)
|
|
324
333
|
|
|
325
334
|
### Desert Traversal
|
|
326
335
|
|
|
@@ -365,7 +374,7 @@ class Safe {
|
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
choose (digit) {
|
|
368
|
-
this.ok = String(digit) === this.combination.charAt(this.index);
|
|
377
|
+
this.ok = this.ok && String(digit) === this.combination.charAt(this.index);
|
|
369
378
|
this.index++;
|
|
370
379
|
}
|
|
371
380
|
|
|
@@ -382,7 +391,7 @@ class Safe {
|
|
|
382
391
|
with this template:
|
|
383
392
|
|
|
384
393
|
```pug
|
|
385
|
-
doctype
|
|
394
|
+
doctype 1.1
|
|
386
395
|
html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
|
|
387
396
|
head
|
|
388
397
|
link(href="style-epub.css", rel="stylesheet", type="text/css")
|
|
@@ -391,9 +400,9 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
|
|
|
391
400
|
h3 Open the Safe
|
|
392
401
|
hr
|
|
393
402
|
if state.isRight()
|
|
394
|
-
h1 YOU
|
|
403
|
+
h1 YOU WIN!
|
|
395
404
|
else if state.isWrong()
|
|
396
|
-
h1 YOU
|
|
405
|
+
h1 YOU LOSE!
|
|
397
406
|
else
|
|
398
407
|
p.first Choose digit ##{state.index + 1}:
|
|
399
408
|
ul
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eit6609/storyteller",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "An interactive ebooks generator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"epub",
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
"jasmine-spec-reporter": "^3.2.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@eit6609/epub-creator": "^1.
|
|
41
|
+
"@eit6609/epub-creator": "^1.1.0",
|
|
42
42
|
"@eit6609/markdown-templates": "^1.0.0",
|
|
43
43
|
"@hapi/joi": "^16.1.7",
|
|
44
|
+
"ejs": "^3.1.8",
|
|
44
45
|
"lodash": "^4.17.10",
|
|
45
|
-
"marked": "^0.
|
|
46
|
-
"pug": "^
|
|
46
|
+
"marked": "^4.0.10",
|
|
47
|
+
"pug": "^3.0.1"
|
|
47
48
|
}
|
|
48
49
|
}
|
package/src/generator.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const
|
|
4
4
|
{ inspect } = require('util'),
|
|
5
|
-
{ filter, forEach, get, isUndefined,
|
|
5
|
+
{ filter, forEach, get, isUndefined, omit, sortBy } = require('lodash'),
|
|
6
6
|
Joi = require('@hapi/joi'),
|
|
7
7
|
{ EPUBCreator } = require('@eit6609/epub-creator'),
|
|
8
8
|
Page = require('./page.js'),
|
|
@@ -20,10 +20,14 @@ const optionsSchema = Joi.object({
|
|
|
20
20
|
title: Joi.string(),
|
|
21
21
|
author: Joi.string(),
|
|
22
22
|
language: Joi.string(),
|
|
23
|
+
isbn: Joi.string(),
|
|
24
|
+
description: Joi.string(),
|
|
25
|
+
tags: Joi.array().items(Joi.string()),
|
|
23
26
|
cover: Joi.string(),
|
|
24
|
-
filename: Joi.string().required()
|
|
27
|
+
filename: Joi.string().required(),
|
|
25
28
|
}).required(),
|
|
26
|
-
|
|
29
|
+
advancedMetadata: Joi.array(),
|
|
30
|
+
templateEngine: Joi.string().valid('pug', 'ejs', 'mt').required(),
|
|
27
31
|
debug: Joi.boolean(),
|
|
28
32
|
contentBefore: Joi.array().items(extraContentSchema),
|
|
29
33
|
contentAfter: Joi.array().items(extraContentSchema),
|
|
@@ -41,7 +45,8 @@ class Generator {
|
|
|
41
45
|
this.templatesDir = options.templatesDir;
|
|
42
46
|
this.outputDir = options.outputDir;
|
|
43
47
|
this.metadata = options.metadata;
|
|
44
|
-
this.
|
|
48
|
+
this.advancedMetadata = options.advancedMetadata || [];
|
|
49
|
+
this.templateEngine = options.templateEngine;
|
|
45
50
|
this.debug = options.debug === true;
|
|
46
51
|
this.contentBefore = options.contentBefore || [];
|
|
47
52
|
this.contentAfter = options.contentAfter || [];
|
|
@@ -96,6 +101,9 @@ class Generator {
|
|
|
96
101
|
createEpub () {
|
|
97
102
|
const spine = [];
|
|
98
103
|
const toc = [];
|
|
104
|
+
if (this.metadata.cover) {
|
|
105
|
+
toc.push([{ label: 'Cover', href: 'cover-page.html' }]);
|
|
106
|
+
}
|
|
99
107
|
for (const { tocLabel, fileName } of this.contentBefore) {
|
|
100
108
|
spine.push(fileName);
|
|
101
109
|
if (tocLabel) {
|
|
@@ -118,14 +126,15 @@ class Generator {
|
|
|
118
126
|
spine,
|
|
119
127
|
toc,
|
|
120
128
|
cover,
|
|
121
|
-
simpleMetadata:
|
|
129
|
+
simpleMetadata: omit(this.metadata, ['cover', 'filename']),
|
|
130
|
+
metadata: this.advancedMetadata,
|
|
122
131
|
});
|
|
123
132
|
return epubCreator.create(this.metadata.filename);
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
printReport () {
|
|
127
136
|
function makeFilterByTemplateName (templateName) {
|
|
128
|
-
return (pageKey) => pageKey.substring(0, pageKey.
|
|
137
|
+
return (pageKey) => pageKey.substring(0, pageKey.indexOf('{') - 1) === templateName;
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
const templateNames = sortBy(Array.from(this.templates.keys()));
|
package/src/page.js
CHANGED
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
toPairsIn
|
|
16
16
|
} = require('lodash'),
|
|
17
17
|
fs = require('fs'),
|
|
18
|
-
marked = require('marked');
|
|
18
|
+
{ marked } = require('marked');
|
|
19
19
|
|
|
20
20
|
function wrapBody (body) {
|
|
21
21
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
@@ -98,8 +98,8 @@ class Page {
|
|
|
98
98
|
followLink (templateName, action) {
|
|
99
99
|
templateName = templateName || this.template.name;
|
|
100
100
|
let { state } = this;
|
|
101
|
+
state = cloneDeep(state);
|
|
101
102
|
if (action) {
|
|
102
|
-
state = cloneDeep(state);
|
|
103
103
|
const newState = action.call(null, state);
|
|
104
104
|
// this way the action can create a new state and initialize a new stage of the game:
|
|
105
105
|
if (newState) {
|
|
@@ -110,7 +110,7 @@ class Page {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
build () {
|
|
113
|
-
if (this.builder.
|
|
113
|
+
if (this.builder.templateEngine === 'mt') {
|
|
114
114
|
const text = this.template.build(this);
|
|
115
115
|
const markedOptions = {
|
|
116
116
|
smartypants: true,
|
package/src/template.js
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const
|
|
4
|
+
fs = require('fs'),
|
|
4
5
|
pug = require('pug'),
|
|
5
|
-
mt = require('@eit6609/markdown-templates')
|
|
6
|
+
mt = require('@eit6609/markdown-templates'),
|
|
7
|
+
ejs = require('ejs');
|
|
8
|
+
|
|
9
|
+
class Pug {
|
|
10
|
+
getFileExtension () {
|
|
11
|
+
return 'pug';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
loadTemplate (fileName, { mockPug }) {
|
|
15
|
+
return (mockPug || pug).compileFile(fileName, { pretty: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class EJS {
|
|
20
|
+
getFileExtension () {
|
|
21
|
+
return 'ejs';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
loadTemplate (fileName, { mockEJS }) {
|
|
25
|
+
if (mockEJS) {
|
|
26
|
+
return mockEJS.compile(mockEJS.getFileContent());
|
|
27
|
+
}
|
|
28
|
+
const src = fs.readFileSync(fileName, 'utf-8');
|
|
29
|
+
return ejs.compile(src);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class MarkdownTemplates {
|
|
34
|
+
getFileExtension () {
|
|
35
|
+
return 'md';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
loadTemplate (fileName, { mockMT }) {
|
|
39
|
+
return (mockMT || mt).compileFile(fileName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
6
42
|
|
|
7
43
|
class Template {
|
|
8
44
|
|
|
9
|
-
|
|
45
|
+
static createTemplateEngineAdapter (name) {
|
|
46
|
+
switch (name) {
|
|
47
|
+
case 'pug':
|
|
48
|
+
return new Pug();
|
|
49
|
+
case 'ejs':
|
|
50
|
+
return new EJS();
|
|
51
|
+
case 'mt':
|
|
52
|
+
return new MarkdownTemplates();
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Unknown template engine: "${name}"`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor (name, builder, { mockMT, mockPug, mockEJS } = {}) {
|
|
10
59
|
this.name = name;
|
|
11
60
|
this.templatesDir = builder.templatesDir;
|
|
12
|
-
this.
|
|
13
|
-
this.load(mockMT, mockPug);
|
|
61
|
+
this.adapter = Template.createTemplateEngineAdapter(builder.templateEngine || 'ejs');
|
|
62
|
+
this.load({ mockMT, mockPug, mockEJS });
|
|
14
63
|
}
|
|
15
64
|
|
|
16
|
-
load (mockMT, mockPug) {
|
|
17
|
-
|
|
18
|
-
this.template = (mockMT || mt).compileFile(this.getFileName());
|
|
19
|
-
} else {
|
|
20
|
-
this.template = (mockPug || pug).compileFile(this.getFileName(), { pretty: true });
|
|
21
|
-
}
|
|
65
|
+
load ({ mockMT, mockPug, mockEJS }) {
|
|
66
|
+
this.template = this.adapter.loadTemplate(this.getFileName(), { mockMT, mockPug, mockEJS });
|
|
22
67
|
}
|
|
23
68
|
|
|
24
69
|
getFileName () {
|
|
25
|
-
return `${this.templatesDir}/${this.name}.${this.
|
|
70
|
+
return `${this.templatesDir}/${this.name}.${this.adapter.getFileExtension()}`;
|
|
26
71
|
}
|
|
27
72
|
|
|
28
73
|
build (page) {
|
|
@@ -31,5 +76,4 @@ class Template {
|
|
|
31
76
|
|
|
32
77
|
}
|
|
33
78
|
|
|
34
|
-
|
|
35
79
|
module.exports = Template;
|