UNPKG

7.36 kBJavaScriptView Raw
1/**
2 * Styledown is available as a Node.js package.
3 *
4 * var Styledown = require('styledown');
5 */
6
7var Marked = require('marked'),
8 Cheerio = require('cheerio'),
9 extend = require('util')._extend,
10 mdextract = require('mdextract');
11
12module.exports = Styledown;
13
14var addClasses = require('./lib/filters').addClasses;
15var sectionize = require('./lib/filters').sectionize;
16var unpackExample = require('./lib/filters').unpackExample;
17var processConfig = require('./lib/filters').processConfig;
18var removeConfig = require('./lib/filters').removeConfig;
19var isolateTextBlocks = require('./lib/filters').isolateTextBlocks;
20var htmlize = require('./lib/utils').htmlize;
21var prefixClass = require('./lib/utils').prefixClass;
22
23/**
24 * Styledown.parse() : Styledown.parse(source, [options])
25 * Generates HTML from a given `source`.
26 *
27 * Styledown.parse('### hello *world*');
28 * => "<!doctype html><html>..."
29 *
30 * `source` can be a String or an Array. as a string, it's assumed to be a
31 * Markdown document. As an array, it's assumed to be a list of files. It's
32 * expected that it contains objects with `name` and `data` keys.
33 *
34 * In array mode, Styledown treats each file differently. Inline comments are
35 * extracted from those with that end in CSS extensions (css, less, sass, etc),
36 * while the rest are assumed to be Markdown documents.
37 *
38 * var docs = [
39 * { name: 'css/style.css', data: '...' },
40 * { name: 'config.md', data: '...' }
41 * ];
42 *
43 * Styledown.parse(docs);
44 * => "<!doctype html><html>..."
45 *
46 * You may pass `options` as the second parameter. Available options are:
47 *
48 * ~ prefix (String): prefix for classnames. Defaults to `sg`.
49 * ~ template (String): HTML template. Defaults to a simple HTML template.
50 * ~ head (String): HTML to put in the head. Default to `false`.
51 * ~ body (String): HTML to put in the body. Defaults to `<div sg-content></div>`.
52 * ~ indentSize (Number): Number of spaces to indent. Defaults to `2`.
53 * ~ inline (Boolean): if `true`, then inline CSS mode is forced.
54 *
55 * This is shorthand for `new Styledown().toHTML()`. You can use `Styledown` as a class.
56 */
57
58Styledown.parse = function (source, options) {
59 return new Styledown(source, options).toHTML();
60};
61
62/**
63 * Styledown.version:
64 * The version number in semver format.
65 */
66
67Styledown.version = require('./package.json').version;
68
69/**
70 * Styledown.defaults:
71 * The returns the default configuration file, JS file and CSS files.
72 *
73 * Styledown.defaults.conf()
74 * Styledown.defaults.js()
75 * Styledown.defaults.css()
76 */
77
78Styledown.defaults = {
79 conf: function () {
80 return require('./lib/default_conf');
81 },
82 js: function () {
83 return require('fs').readFileSync(__dirname + '/data/styledown.js');
84 },
85 css: function () {
86 return require('fs').readFileSync(__dirname + '/data/styledown.css');
87 },
88};
89
90/***
91 * Styledown() : new Styledown(source, [options])
92 * Parses the source `source` into a Styledown document. `source` can be a
93 * Markdown document.
94 *
95 * doc = new Styledown(markdown);
96 * doc.toHTML();
97 *
98 * You may also use `Styledown.parse()` as a shorthand.
99 */
100
101function Styledown (src, options) {
102 this.options = extend(extend({}, Styledown.defaultOptions), options || {});
103 this.raw = this.extract(src);
104 this.$ = Cheerio.load(Marked(this.raw));
105
106 this.process();
107}
108
109Styledown.defaultOptions = {
110
111 /* HTML template */
112 template: [
113 "<!doctype html>",
114 "<html>",
115 "<head>",
116 "<meta charset='utf-8'>",
117 "<title>Styledown</title>",
118 "</head>",
119 "<body>",
120 "</body>",
121 "</html>"
122 ].join("\n"),
123
124 /* Things to put into `head` */
125 head: false,
126
127 /* Force inline mode */
128 inline: false,
129
130 /* Things to put into `body` */
131 body: "<div sg-content></div>",
132
133 /* Prefix for classnames */
134 prefix: 'sg',
135
136 /* Indentation spaces */
137 indentSize: 2
138};
139
140Styledown.prototype = {
141
142 /**
143 * toHTML() : doc.toHTML()
144 * Returns the full HTML source based on the Styledown document.
145 *
146 * doc.toHTML()
147 * => "<!doctype html><html>..."
148 */
149
150 toHTML: function() {
151 var html = this.toBareHTML();
152
153 if (this.options.head !== false) {
154 // Unpack template
155 var $ = Cheerio.load(htmlize(this.options.template));
156 $('body').append(htmlize(this.options.body));
157 $('[sg-content]').append(html).removeAttr('sg-content');
158 $('html, body').addClass(this.options.prefix);
159 $('head').append(htmlize(this.options.head));
160
161 html = $.html();
162 }
163
164 html = this.prettyprint(html, { wrap_line_length: 0 });
165 return html;
166 },
167
168 /**
169 * toBareHTML() : doc.toBareHTML()
170 * Returns the bare HTML without the head/body templates.
171 *
172 * doc.toBareHTML()
173 * => "<div><h3>Your document</h3>..."
174 */
175
176 toBareHTML: function () {
177 return this.$.html();
178 },
179
180 /**
181 * extract():
182 * (private) extracts a Markdown source from given `src`.
183 */
184
185 extract: function (src) {
186 var self = this;
187
188 if (typeof src === 'string')
189 return src;
190
191 if (Array.isArray(src)) {
192 return src.map(function (f) {
193 if (self.options.inline || f.name && f.name.match(/(sass|scss|styl|less|css)$/)) {
194 return mdextract(f.data, { lang: 'css' }).toMarkdown();
195 } else {
196 return f.data;
197 }
198 }).join("\n");
199 }
200 },
201
202 /**
203 * process() : doc.process()
204 * (private) processes things. Done on the constructor.
205 */
206
207 process: function () {
208 var highlightHTML = this.highlightHTML.bind(this);
209 var p = this.prefix.bind(this);
210 var src = this.raw;
211
212 processConfig(src, this.options);
213 removeConfig(this.$);
214
215 var pre = this.options.prefix;
216 var $ = this.$;
217
218 addClasses($, p);
219 sectionize($, 'h3', p, { 'class': p('block') });
220 sectionize($, 'h2', p, { 'class': p('section'), until: 'h1, h2' });
221
222 $('pre').each(function () {
223 unpackExample($(this), p, highlightHTML);
224 });
225
226 isolateTextBlocks(this.$, p);
227 },
228
229 /**
230 * prettyprint() : doc.prettyprint(html)
231 * (private) Reindents given `html` based on the indent size option.
232 *
233 * doc.prettyprint('<div><a>hello</a></div>')
234 * => "<div>\n <a>hello</a>\n</div>"
235 */
236
237 prettyprint: function (html, options) {
238 var beautify = require('js-beautify').html_beautify;
239
240 var opts = {
241 indent_size: this.options.indentSize,
242 wrap_line_length: 120,
243 unformatted: ['pre']
244 };
245
246 // js-beautify sometimes trips when the first character isn't a <. not
247 // sure... but might as well do this.
248 html = html.trim();
249
250 var output = beautify(html, extend(opts, options));
251
252 // cheerio output tends to have a bunch of extra newlines. kill them.
253 output = output.replace(/\n\n+/g, "\n\n");
254
255 return output;
256 },
257
258 /**
259 * highlightHTML():
260 * (private) Syntax highlighting helper
261 */
262
263 highlightHTML: function (html) {
264 var Hljs = require('highlight.js');
265
266 html = this.prettyprint(html);
267 html = Hljs.highlight('html', html).value;
268 return html;
269 },
270
271 /**
272 * prefix():
273 * (private) Prefix classnames. Takes `options.prefix` into account.
274 *
275 * prefix('block')
276 * => 'sg-block'
277 */
278
279 prefix: function(klass) {
280 return klass ?
281 prefixClass(klass, this.options.prefix) :
282 this.options.prefix;
283 }
284};