UNPKG

bin-to-file

Version:

Converts JS Bin object to static HTML

214 lines (178 loc) 5.88 kB
'use strict'; 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()))); } } }