bin-to-file
Version:
Converts JS Bin object to static HTML
214 lines (178 loc) • 5.88 kB
JavaScript
;
var doctypeRe = new RegExp(/^<!doctype[^>]*>\n?/im);
function afterLine(source, needle, value) {
if (!source.toLowerCase().includes(needle.toLowerCase())) {
return null;
}
var found = false;
var res = source.split('\n').reduce(function (acc, curr) {
acc.push(curr);
if (!found && curr.trim().toLowerCase().startsWith(needle)) {
acc.push(value.trim());
found = true;
}
return acc;
}, []).join('\n');
return found ? res : null;
}
function insert(source, needle, value) {
var after = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
needle = needle.toLowerCase();
var sourceLC = source.toLowerCase();
if (!sourceLC.includes(needle)) {
return null;
}
var left = source.substring(0, sourceLC.lastIndexOf(needle));
var ri = sourceLC.lastIndexOf(needle);
if (after) {
ri += needle.length;
left += needle + '\n';
}
var right = source.substring(ri);
if (left && right) {
return left + value + right;
}
return '';
}
function safeForHTML(s) {
return (s || '').replace(/<\/script>/gi, '<\\/script>').replace(/<!--/g, '<\\!--');
}
function binToFile(bin) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!bin) {
console.error('binToFile requires bin object', new Error().stack);
return '<!DOCTYPE html>';
}
// allows for the proto to be '' (not sure why you'd want that though...)
var proto = options.proto !== undefined ? options.proto : 'https:';
// protect myself from idiots, like me.
if (proto && proto.slice(-1) !== ':') {
proto += ':';
}
var file = '';
var html = (bin.html || '').replace(/(\r\n)/g, '\n'); // remove windows nl.
var css = safeForHTML(bin.css);
var javascript = safeForHTML(bin.javascript);
var source = bin.source,
_bin$processors = bin.processors,
processors = _bin$processors === undefined ? {} : _bin$processors;
var binId = [bin.url, bin.revision].filter(Boolean).join('/');
var meta = bin.meta || (bin.url ? '<meta name="source" content="https://jsbin.com/' + binId + '/edit">' : '');
// insert protocol if missing
html = html.replace(/(src|href)=('|")\/\//g, '$1=$2' + proto + '//');
if (meta && meta.slice(-1) !== '\n') {
meta += '\n'; // a nice new line for the meta data
}
/**
* 1. strip the doctype and print it then add comment (<!-- file... etc)
* 2. in remaining code:
* - is there %css%?
* yes: replace with CSS
* no: look for head - is there head?
* yes: insert style tag
* no: try after the <title> tag, or prepend to top: <style>css</style>
* - is there %code%
* yes: replace with JS
* no: look for closing </body> - is there closing </body>
* yes: insert above this
* no: append to end (closing HTML?)
* - is there closing body or html?
* yes: insert "source script tags" above
* no: append source scripts
*
*/
file = html;
if (css) {
if (file.includes('%css%')) {
file = file.split('%css%').join(bin.css);
} else {
// is there head tag?
css = '<style id="jsbin-css">\n' + css + '\n</style>\n';
var head = insert(file, '</head>', css);
if (head) {
file = head;
} else {
var title = insert(file, '</title>', css, true);
if (title) {
file = title;
} else {
// slap on the top (note that this is *before* the doctype)
file = css + file;
}
}
}
}
// try to insert above the head
if (meta) {
meta = '<!-- jsbin -->\n' + meta.trim() + '\n<!-- /jsbin -->\n';
var afterHTML = afterLine(file, '<html', meta);
if (afterHTML) {
file = afterHTML;
} else {
// only look for a doctype at the top of the document
var doctype = (html.trim().split('\n').shift().trim().match(doctypeRe) || [])[0] || '';
if (doctype) {
file = file.replace(doctypeRe, doctype + '\n' + meta);
// strip from original html
} else {
file = meta + file;
}
}
}
if (javascript) {
if (file.includes('%code%')) {
file = file.split('%code%').join(javascript);
} else {
// is there head tag?
var hasModule = javascript.includes('import ');
javascript = '<script id="jsbin-javascript" ' + (hasModule ? 'type="module" ' : '') + 'defer>\n' + javascript + '\n</script>';
var body = insert(file, '</body>', javascript + '\n');
if (body) {
file = body;
} else {
// slap on the bottom
file = file + '\n' + javascript;
}
}
}
// If we have the raw panel content - go ahead and stick that in scripts at the bottom.
if (source) {
if (source.css === css) {
delete source.css;
}
if (source.javascript === javascript) {
delete source.javascript;
}
if (source.html === html) {
delete source.html;
}
var sourceScripts = ['html', 'css', 'javascript'].map(function (type) {
if (source[type] === undefined) {
return '';
}
var content = safeForHTML(source[type]);
if (content) {
return '\n<script id="jsbin-source-' + type + '" type="text/source-' + (processors[type] || type) + '">' + content + '\n</script>';
}
return '';
}).join('');
var bodyTag = insert(file, '</body>', sourceScripts);
if (bodyTag) {
file = bodyTag;
} else {
file += sourceScripts;
}
}
return file;
}
if (typeof module !== 'undefined') {
module.exports = binToFile;
module.exports.afterLine = afterLine;
if (!module.parent) {
// cli mode
if (!process.stdin.isTTY) {
var stdinBuffer = require('fs').readFileSync(0); // STDIN_FILENO = 0
console.log(module.exports(JSON.parse(stdinBuffer.toString())));
}
}
}