1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-29 09:40:10 +02:00

port saveAsHtml

This commit is contained in:
Kushagra Gour
2018-06-07 11:05:59 +05:30
parent d3ba0534aa
commit 6794e309af
4 changed files with 373 additions and 350 deletions

View File

@@ -2,7 +2,7 @@ import { h, Component } from 'preact';
import UserCodeMirror from './UserCodeMirror.jsx'; import UserCodeMirror from './UserCodeMirror.jsx';
import { computeHtml, computeCss, computeJs } from '../computes'; import { computeHtml, computeCss, computeJs } from '../computes';
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes'; import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { log, writeFile, loadJS } from '../utils'; import { log, writeFile, loadJS, getCompleteHtml } from '../utils';
import { SplitPane } from './SplitPane.jsx'; import { SplitPane } from './SplitPane.jsx';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
import CodeMirror from '../CodeMirror'; import CodeMirror from '../CodeMirror';
@@ -76,78 +76,15 @@ export default class ContentWrap extends Component {
} }
clearConsole() {} clearConsole() {}
/* eslint max-params: ["error", 4] */
getCompleteHtml(html, css, js, isForExport) {
if (!this.props.currentItem) {
return '';
}
var externalJs = this.props.currentItem.externalLibs.js
.split('\n')
.reduce(function(scripts, url) {
return scripts + (url ? '\n<script src="' + url + '"></script>' : '');
}, '');
var externalCss = this.props.currentItem.externalLibs.css
.split('\n')
.reduce(function(links, url) {
return (
links +
(url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '')
);
}, '');
var contents =
'<!DOCTYPE html>\n' +
'<html>\n<head>\n' +
'<meta charset="UTF-8" />\n' +
externalCss +
'\n' +
'<style id="webmakerstyle">\n' +
css +
'\n</style>\n' +
'</head>\n' +
'<body>\n' +
html +
'\n' +
externalJs +
'\n';
if (!isForExport) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/screenlog.js')
: `${location.origin}${BASE_PATH}/lib/screenlog.js`) +
'"></script>';
}
if (this.jsMode === JsModes.ES6) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/transpilers/babel-polyfill.min.js')
: `${
location.origin
}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) +
'"></script>';
}
if (typeof js === 'string') {
contents += '<script>\n' + js + '\n//# sourceURL=userscript.js';
} else {
var origin = chrome.i18n.getMessage()
? `chrome-extension://${chrome.i18n.getMessage('@@extension_id')}`
: `${location.origin}`;
contents +=
'<script src="' + `filesystem:${origin}/temporary/script.js` + '">';
}
contents += '\n</script>\n</body>\n</html>';
return contents;
}
createPreviewFile(html, css, js) { createPreviewFile(html, css, js) {
const shouldInlineJs = const shouldInlineJs =
!window.webkitRequestFileSystem || !window.IS_EXTENSION; !window.webkitRequestFileSystem || !window.IS_EXTENSION;
var contents = this.getCompleteHtml(html, css, shouldInlineJs ? js : null); var contents = getCompleteHtml(
html,
css,
shouldInlineJs ? js : null,
this.props.currentItem
);
var blob = new Blob([contents], { type: 'text/plain;charset=UTF-8' }); var blob = new Blob([contents], { type: 'text/plain;charset=UTF-8' });
var blobjs = new Blob([js], { type: 'text/plain;charset=UTF-8' }); var blobjs = new Blob([js], { type: 'text/plain;charset=UTF-8' });

View File

@@ -17,6 +17,7 @@ export default class Footer extends Component {
<div id="footer" class="footer"> <div id="footer" class="footer">
<div class="footer__right fr"> <div class="footer__right fr">
<a <a
onClick={this.props.saveHtmlBtnClickHandler}
id="saveHtmlBtn" id="saveHtmlBtn"
class="mode-btn hint--rounded hint--top-left hide-on-mobile" class="mode-btn hint--rounded hint--top-left hide-on-mobile"
data-hint="Save as HTML file" data-hint="Save as HTML file"

View File

@@ -9,7 +9,7 @@ import AddLibrary from './AddLibrary.jsx';
import Modal from './Modal.jsx'; import Modal from './Modal.jsx';
import HelpModal from './HelpModal.jsx'; import HelpModal from './HelpModal.jsx';
import Login from './Login.jsx'; import Login from './Login.jsx';
import { log, generateRandomId, semverCompare } from '../utils'; import { log, generateRandomId, semverCompare, saveAsHtml } from '../utils';
import { itemService } from '../itemService'; import { itemService } from '../itemService';
import '../db'; import '../db';
import Notifications from './Notifications'; import Notifications from './Notifications';
@@ -446,40 +446,6 @@ export default class App extends Component {
}); });
} }
saveFile() {
var htmlPromise = computeHtml();
var cssPromise = computeCss();
var jsPromise = computeJs(false);
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function(result) {
var html = result[0],
css = result[1],
js = result[2];
var fileContent = getCompleteHtml(html, css, js, true);
var d = new Date();
var fileName = [
'web-maker',
d.getFullYear(),
d.getMonth() + 1,
d.getDate(),
d.getHours(),
d.getMinutes(),
d.getSeconds()
].join('-');
if (this.state.currentItem.title) {
fileName = this.state.currentItem.title;
}
fileName += '.html';
var blob = new Blob([fileContent], { type: 'text/html;charset=UTF-8' });
utils.downloadFile(fileName, blob);
trackEvent('fn', 'saveFileComplete');
});
}
closeAllOverlays() { closeAllOverlays() {
if (this.state.isSavedItemPaneOpen) { if (this.state.isSavedItemPaneOpen) {
this.setState({ isSavedItemPaneOpen: false }); this.setState({ isSavedItemPaneOpen: false });
@@ -833,6 +799,11 @@ export default class App extends Component {
trackEvent('ui', 'openInCodepen'); trackEvent('ui', 'openInCodepen');
e.preventDefault(); e.preventDefault();
} }
saveHtmlBtnClickHandler(e) {
saveAsHtml(this.state.currentItem);
trackEvent('ui', 'saveHtmlClick');
e.preventDefault();
}
render() { render() {
return ( return (
@@ -880,6 +851,7 @@ export default class App extends Component {
this this
)} )}
codepenBtnClickHandler={this.codepenBtnClickHandler.bind(this)} codepenBtnClickHandler={this.codepenBtnClickHandler.bind(this)}
saveHtmlBtnClickHandler={this.saveHtmlBtnClickHandler.bind(this)}
hasUnseenChangelog={this.state.hasUnseenChangelog} hasUnseenChangelog={this.state.hasUnseenChangelog}
/> />
</div> </div>

View File

@@ -1,18 +1,27 @@
import { import {
trackEvent trackEvent
} from './analytics'; } from './analytics';
import { import {
computeHtml,
computeCss,
computeJs
} from './computes';
import {
JsModes
} from './codeModes';
import {
deferred deferred
} from './deferred'; } from './deferred';
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
window.$ = document.querySelector.bind(document); window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
window.$all = selector => [...document.querySelectorAll(selector)]; window.$ = document.querySelector.bind(document);
var alphaNum = window.$all = selector => [...document.querySelectorAll(selector)];
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const BASE_PATH = chrome.extension || window.DEBUG ? '/' : '/app';
/** var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
* The following 2 functions are supposed to find the next/previous sibling until the * The following 2 functions are supposed to find the next/previous sibling until the
* passed `selector` is matched. But for now it actually finds the next/previous * passed `selector` is matched. But for now it actually finds the next/previous
* element of `this` element in the list of `selector` matched element inside `this`'s * element of `this` element in the list of `selector` matched element inside `this`'s
@@ -20,31 +29,31 @@
* @param Selector that should match for next siblings * @param Selector that should match for next siblings
* @return element Next element that mathes `selector` * @return element Next element that mathes `selector`
*/ */
Node.prototype.nextUntil = function (selector) { Node.prototype.nextUntil = function (selector) {
const siblings = [...this.parentNode.querySelectorAll(selector)]; const siblings = [...this.parentNode.querySelectorAll(selector)];
const index = siblings.indexOf(this); const index = siblings.indexOf(this);
return siblings[index + 1]; return siblings[index + 1];
}; };
// Safari doesn't have this! // Safari doesn't have this!
window.requestIdleCallback = window.requestIdleCallback =
window.requestIdleCallback || window.requestIdleCallback ||
function (fn) { function (fn) {
setTimeout(fn, 10); setTimeout(fn, 10);
}; };
/* /*
* @param Selector that should match for next siblings * @param Selector that should match for next siblings
* @return element Next element that mathes `selector` * @return element Next element that mathes `selector`
*/ */
Node.prototype.previousUntil = function (selector) { Node.prototype.previousUntil = function (selector) {
const siblings = [...this.parentNode.querySelectorAll(selector)]; const siblings = [...this.parentNode.querySelectorAll(selector)];
const index = siblings.indexOf(this); const index = siblings.indexOf(this);
return siblings[index - 1]; return siblings[index - 1];
}; };
// https://github.com/substack/semver-compare/blob/master/index.js // https://github.com/substack/semver-compare/blob/master/index.js
export function semverCompare(a, b) { export function semverCompare(a, b) {
var pa = a.split('.'); var pa = a.split('.');
var pb = b.split('.'); var pb = b.split('.');
for (var i = 0; i < 3; i++) { for (var i = 0; i < 3; i++) {
@@ -64,51 +73,51 @@
} }
} }
return 0; return 0;
} }
export function generateRandomId(len) { export function generateRandomId(len) {
var length = len || 10; var length = len || 10;
var id = ''; var id = '';
for (var i = length; i--;) { for (var i = length; i--;) {
id += alphaNum[~~(Math.random() * alphaNum.length)]; id += alphaNum[~~(Math.random() * alphaNum.length)];
} }
return id; return id;
} }
export function onButtonClick(btn, listener) { export function onButtonClick(btn, listener) {
btn.addEventListener('click', function buttonClickListener(e) { btn.addEventListener('click', function buttonClickListener(e) {
listener(e); listener(e);
return false; return false;
}); });
} }
export function log() { export function log() {
if (window.DEBUG) { if (window.DEBUG) {
console.log(Date.now(), ...arguments); console.log(Date.now(), ...arguments);
} }
} }
/** /**
* Adds timed limit on the loops found in the passed code. * Adds timed limit on the loops found in the passed code.
* Contributed by Ariya Hidayat! * Contributed by Ariya Hidayat!
* @param code {string} Code to be protected from infinite loops. * @param code {string} Code to be protected from infinite loops.
*/ */
export function addInfiniteLoopProtection(code, { export function addInfiniteLoopProtection(code, {
timeout timeout
}) { }) {
var loopId = 1; var loopId = 1;
var patches = []; var patches = [];
var varPrefix = '_wmloopvar'; var varPrefix = '_wmloopvar';
var varStr = 'var %d = Date.now();\n'; var varStr = 'var %d = Date.now();\n';
var checkStr = `\nif (Date.now() - %d > ${timeout}) { window.top.previewException(new Error("Infinite loop")); break;}\n`; var checkStr = `\nif (Date.now() - %d > ${timeout}) { window.top.previewException(new Error("Infinite loop")); break;}\n`;
esprima.parse(code, { esprima.parse(
code, {
tolerant: true, tolerant: true,
range: true, range: true,
jsx: true jsx: true
}, function ( },
node function (node) {
) {
switch (node.type) { switch (node.type) {
case 'DoWhileStatement': case 'DoWhileStatement':
case 'ForStatement': case 'ForStatement':
@@ -145,7 +154,8 @@
default: default:
break; break;
} }
}); }
);
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
patches patches
@@ -158,9 +168,9 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
return code; return code;
} }
export function getHumanDate(timestamp) { export function getHumanDate(timestamp) {
var d = new Date(timestamp); var d = new Date(timestamp);
var retVal = var retVal =
d.getDate() + d.getDate() +
@@ -181,10 +191,10 @@
' ' + ' ' +
d.getFullYear(); d.getFullYear();
return retVal; return retVal;
} }
// create a one-time event // create a one-time event
export function once(node, type, callback) { export function once(node, type, callback) {
// create event // create event
node.addEventListener(type, function (e) { node.addEventListener(type, function (e) {
// remove event // remove event
@@ -192,9 +202,9 @@
// call handler // call handler
return callback(e); return callback(e);
}); });
} }
export function downloadFile(fileName, blob) { export function downloadFile(fileName, blob) {
function downloadWithAnchor() { function downloadWithAnchor() {
var a = document.createElement('a'); var a = document.createElement('a');
a.href = window.URL.createObjectURL(blob); a.href = window.URL.createObjectURL(blob);
@@ -220,9 +230,9 @@
} else { } else {
downloadWithAnchor(); downloadWithAnchor();
} }
} }
export function writeFile(name, blob, cb) { export function writeFile(name, blob, cb) {
var fileWritten = false; var fileWritten = false;
function getErrorHandler(type) { function getErrorHandler(type) {
@@ -275,9 +285,9 @@
}, },
getErrorHandler('webkitRequestFileSystemFail') getErrorHandler('webkitRequestFileSystemFail')
); );
} }
export function loadJS(src) { export function loadJS(src) {
var d = deferred(); var d = deferred();
var ref = window.document.getElementsByTagName('script')[0]; var ref = window.document.getElementsByTagName('script')[0];
var script = window.document.createElement('script'); var script = window.document.createElement('script');
@@ -288,16 +298,119 @@
d.resolve(); d.resolve();
}; };
return d.promise; return d.promise;
}; }
window.chrome = window.chrome || {}; export function getCompleteHtml(html, css, js, item, isForExport) {
window.chrome.i18n = { if (!item) {
getMessage: () => {} return '';
};
window.IS_EXTENSION = !!window.chrome.extension;
if (window.IS_EXTENSION) {
document.body.classList.add('is-extension');
} else {
document.body.classList.add('is-app');
} }
var externalJs = item.externalLibs.js
.split('\n')
.reduce(function (scripts, url) {
return scripts + (url ? '\n<script src="' + url + '"></script>' : '');
}, '');
var externalCss = item.externalLibs.css
.split('\n')
.reduce(function (links, url) {
return (
links +
(url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '')
);
}, '');
var contents =
'<!DOCTYPE html>\n' +
'<html>\n<head>\n' +
'<meta charset="UTF-8" />\n' +
externalCss +
'\n' +
'<style id="webmakerstyle">\n' +
css +
'\n</style>\n' +
'</head>\n' +
'<body>\n' +
html +
'\n' +
externalJs +
'\n';
if (!isForExport) {
contents +=
'<script src="' +
(chrome.extension ?
chrome.extension.getURL('lib/screenlog.js') :
`${location.origin}${BASE_PATH}/lib/screenlog.js`) +
'"></script>';
}
if (item.jsMode === JsModes.ES6) {
contents +=
'<script src="' +
(chrome.extension ?
chrome.extension.getURL('lib/transpilers/babel-polyfill.min.js') :
`${
location.origin
}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) +
'"></script>';
}
if (typeof js === 'string') {
contents += '<script>\n' + js + '\n//# sourceURL=userscript.js';
} else {
var origin = chrome.i18n.getMessage() ?
`chrome-extension://${chrome.i18n.getMessage('@@extension_id')}` :
`${location.origin}`;
contents +=
'<script src="' + `filesystem:${origin}/temporary/script.js` + '">';
}
contents += '\n</script>\n</body>\n</html>';
return contents;
}
export function saveAsHtml(item) {
var htmlPromise = computeHtml(item.html, item.htmlMode);
var cssPromise = computeCss(item.css, item.cssMode);
var jsPromise = computeJs(item.js, item.jsMode, false);
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function (result) {
var html = result[0],
css = result[1],
js = result[2];
var fileContent = getCompleteHtml(html, css, js, item, true);
var d = new Date();
var fileName = [
'web-maker',
d.getFullYear(),
d.getMonth() + 1,
d.getDate(),
d.getHours(),
d.getMinutes(),
d.getSeconds()
].join('-');
if (item.title) {
fileName = item.title;
}
fileName += '.html';
var blob = new Blob([fileContent], {
type: 'text/html;charset=UTF-8'
});
downloadFile(fileName, blob);
trackEvent('fn', 'saveFileComplete');
});
}
window.chrome = window.chrome || {};
window.chrome.i18n = {
getMessage: () => {}
};
window.IS_EXTENSION = !!window.chrome.extension;
if (window.IS_EXTENSION) {
document.body.classList.add('is-extension');
} else {
document.body.classList.add('is-app');
}