Squashed commit of the following:
commit fb5e82cea930b011792983c7d1cc9f6ecacc7dd4 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Wed Nov 16 12:28:10 2016 -0500 add server side rendering, untested commit 5c60fb30c46ea49a8d9a0ecb56f39ec778464a8b Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Tue Nov 15 18:26:06 2016 -0500 add redux-form bandage post react update, should probably update to redux-form 6 at some point commit 057b5871e7137179abc93f7821a9690f0ea52c92 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Tue Nov 15 16:30:09 2016 -0500 remove passing jsFiles and cssFiles to PreviewFrame, fix rendering bug commit 88c56fd36d3a8d88902c79642171988ce37825f2 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Tue Nov 15 16:21:59 2016 -0500 code cleanup, untested commit 82e5dcf8bca461892f1daf06d38f1eaebe72983f Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Tue Nov 15 15:53:50 2016 -0500 update react and react router, fix a few bugs in rendering code, add ability to parse inline js and css commit e02f4b67803ea45328eff4e53659222f3149964c Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Tue Nov 15 14:43:38 2016 -0500 add almost full code to create preview html correctly, untested commit 12f61b2a1aed4607fab24d01572b647ca6210262 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Wed Nov 2 17:09:26 2016 -0400 refactor some of the preview html generation code commit 111825846703d5c8959cb18795a3aadb7ebe505c Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Wed Nov 2 11:06:36 2016 -0400 add comments as plan of action commit 1cc2cf5203674732b4057382f1937de38b687078 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Thu Oct 27 19:34:55 2016 -0400 add href parsing commit e67189298cda9b70645f454ecd541a363980f0e4 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Thu Oct 27 10:48:36 2016 -0400 continue parsing html commit 1458fb940a15a3dc5d74890211a3073e920b84b8 Author: Cassie Tarakajian <ctarakajian@gmail.com> Date: Wed Oct 26 17:40:31 2016 -0400 start to add html parsing
This commit is contained in:
parent
ced885d03f
commit
81475c0e16
15 changed files with 384 additions and 245 deletions
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
function LoginForm(props) {
|
function LoginForm(props) {
|
||||||
const { fields: { email, password }, handleSubmit, submitting, pristine } = props;
|
const { fields: { email, password }, handleSubmit, submitting, pristine } = props;
|
||||||
|
@ -10,7 +11,7 @@ function LoginForm(props) {
|
||||||
aria-label="email"
|
aria-label="email"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
{...email}
|
{...domOnlyProps(email)}
|
||||||
/>
|
/>
|
||||||
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
@ -20,7 +21,7 @@ function LoginForm(props) {
|
||||||
aria-label="password"
|
aria-label="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
{...password}
|
{...domOnlyProps(password)}
|
||||||
/>
|
/>
|
||||||
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
class NewFileForm extends React.Component {
|
class NewFileForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -21,7 +22,7 @@ class NewFileForm extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
ref="fileName"
|
ref="fileName"
|
||||||
{...name}
|
{...domOnlyProps(name)}
|
||||||
/>
|
/>
|
||||||
<input type="submit" value="Add File" aria-label="add file" />
|
<input type="submit" value="Add File" aria-label="add file" />
|
||||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
class NewFolderForm extends React.Component {
|
class NewFolderForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -21,7 +22,7 @@ class NewFolderForm extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
ref="fileName"
|
ref="fileName"
|
||||||
{...name}
|
{...domOnlyProps(name)}
|
||||||
/>
|
/>
|
||||||
<input type="submit" value="Add Folder" aria-label="add folder" />
|
<input type="submit" value="Add Folder" aria-label="add folder" />
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
function NewPasswordForm(props) {
|
function NewPasswordForm(props) {
|
||||||
const { fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props;
|
const { fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props;
|
||||||
|
@ -10,7 +11,7 @@ function NewPasswordForm(props) {
|
||||||
aria-label="password"
|
aria-label="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
{...password}
|
{...domOnlyProps(password)}
|
||||||
/>
|
/>
|
||||||
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
@ -20,7 +21,7 @@ function NewPasswordForm(props) {
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Confirm Password"
|
placeholder="Confirm Password"
|
||||||
aria-label="confirm password"
|
aria-label="confirm password"
|
||||||
{...confirmPassword}
|
{...domOnlyProps(confirmPassword)}
|
||||||
/>
|
/>
|
||||||
{confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>}
|
{confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import escapeStringRegexp from 'escape-string-regexp';
|
// import escapeStringRegexp from 'escape-string-regexp';
|
||||||
import srcDoc from 'srcdoc-polyfill';
|
import srcDoc from 'srcdoc-polyfill';
|
||||||
|
|
||||||
import loopProtect from 'loop-protect';
|
import loopProtect from 'loop-protect';
|
||||||
import { getBlobUrl } from '../actions/files';
|
import { getBlobUrl } from '../actions/files';
|
||||||
|
import { resolvePathToFile } from '../../../../server/utils/filePath';
|
||||||
|
|
||||||
const startTag = '@fs-';
|
const startTag = '@fs-';
|
||||||
const MEDIA_FILE_REGEX = /^('|")(?!(http:\/\/|https:\/\/)).*\.(png|jpg|jpeg|gif|bmp|mp3|wav|aiff|ogg|json|txt|csv|svg|obj|mp4|ogg|webm|mov)('|")$/i;
|
const MEDIA_FILE_REGEX = /^('|")(?!(http:\/\/|https:\/\/)).*\.(png|jpg|jpeg|gif|bmp|mp3|wav|aiff|ogg|json|txt|csv|svg|obj|mp4|ogg|webm|mov)('|")$/i;
|
||||||
|
const MEDIA_FILE_REGEX_NO_QUOTES = /^(?!(http:\/\/|https:\/\/)).*\.(png|jpg|jpeg|gif|bmp|mp3|wav|aiff|ogg|json|txt|csv|svg|obj|mp4|ogg|webm|mov)$/i;
|
||||||
const STRING_REGEX = /(['"])((\\\1|.)*?)\1/gm;
|
const STRING_REGEX = /(['"])((\\\1|.)*?)\1/gm;
|
||||||
|
const TEXT_FILE_REGEX = /(.+\.json$|.+\.txt$|.+\.csv$)/i;
|
||||||
|
const NOT_EXTERNAL_LINK_REGEX = /^(?!(http:\/\/|https:\/\/))/;
|
||||||
|
const EXTERNAL_LINK_REGEX = /^(http:\/\/|https:\/\/)/;
|
||||||
|
|
||||||
function getAllScriptOffsets(htmlFile) {
|
function getAllScriptOffsets(htmlFile) {
|
||||||
const offs = [];
|
const offs = [];
|
||||||
|
@ -33,50 +38,8 @@ function getAllScriptOffsets(htmlFile) {
|
||||||
return offs;
|
return offs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function hijackConsoleLogsScript() {
|
|
||||||
const s = `<script>
|
|
||||||
var iframeWindow = window;
|
|
||||||
var originalConsole = iframeWindow.console;
|
|
||||||
iframeWindow.console = {};
|
|
||||||
|
|
||||||
var methods = [
|
|
||||||
'debug', 'clear', 'error', 'info', 'log', 'warn'
|
|
||||||
];
|
|
||||||
|
|
||||||
var consoleBuffer = [];
|
|
||||||
var LOGWAIT = 500;
|
|
||||||
|
|
||||||
methods.forEach( function(method) {
|
|
||||||
iframeWindow.console[method] = function() {
|
|
||||||
originalConsole[method].apply(originalConsole, arguments);
|
|
||||||
|
|
||||||
var args = Array.from(arguments);
|
|
||||||
args = args.map(function(i) {
|
|
||||||
// catch objects
|
|
||||||
return (typeof i === 'string') ? i : JSON.stringify(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
consoleBuffer.push({
|
|
||||||
method: method,
|
|
||||||
arguments: args,
|
|
||||||
source: 'sketch'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(function() {
|
|
||||||
if (consoleBuffer.length > 0) {
|
|
||||||
window.parent.postMessage(consoleBuffer, '*');
|
|
||||||
consoleBuffer.length = 0;
|
|
||||||
}
|
|
||||||
}, LOGWAIT);
|
|
||||||
</script>`;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hijackConsoleErrorsScript(offs) {
|
function hijackConsoleErrorsScript(offs) {
|
||||||
const s = `<script>
|
const s = `
|
||||||
function getScriptOff(line) {
|
function getScriptOff(line) {
|
||||||
var offs = ${offs};
|
var offs = ${offs};
|
||||||
var l = 0;
|
var l = 0;
|
||||||
|
@ -111,7 +74,7 @@ function hijackConsoleErrorsScript(offs) {
|
||||||
}], '*');
|
}], '*');
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
</script>`;
|
`;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,117 +135,178 @@ class PreviewFrame extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
injectLocalFiles() {
|
injectLocalFiles() {
|
||||||
let htmlFile = this.props.htmlFile.content;
|
const htmlFile = this.props.htmlFile.content;
|
||||||
let scriptOffs = [];
|
let scriptOffs = [];
|
||||||
|
|
||||||
// have to build the array manually because the spread operator is only
|
const resolvedFiles = this.resolveJSAndCSSLinks(this.props.files);
|
||||||
// one level down...
|
|
||||||
|
|
||||||
htmlFile = hijackConsoleLogsScript() + htmlFile;
|
const parser = new DOMParser();
|
||||||
const mediaFiles = this.props.files.filter(file => file.url);
|
const sketchDoc = parser.parseFromString(htmlFile, 'text/html');
|
||||||
const textFiles = this.props.files.filter(file => file.name.match(/(.+\.json$|.+\.txt$|.+\.csv$)/i) && file.url === undefined);
|
|
||||||
|
|
||||||
const jsFiles = [];
|
this.resolvePathsForElementsWithAttribute('src', sketchDoc, resolvedFiles);
|
||||||
this.props.jsFiles.forEach(jsFile => {
|
this.resolvePathsForElementsWithAttribute('href', sketchDoc, resolvedFiles);
|
||||||
const newJSFile = { ...jsFile };
|
// should also include background, data, poster, but these are used way less often
|
||||||
let jsFileStrings = newJSFile.content.match(STRING_REGEX);
|
|
||||||
jsFileStrings = jsFileStrings || [];
|
|
||||||
jsFileStrings.forEach(jsFileString => {
|
|
||||||
if (jsFileString.match(MEDIA_FILE_REGEX)) {
|
|
||||||
const filePath = jsFileString.substr(1, jsFileString.length - 2);
|
|
||||||
const filePathArray = filePath.split('/');
|
|
||||||
const fileName = filePathArray[filePathArray.length - 1];
|
|
||||||
mediaFiles.forEach(file => {
|
|
||||||
if (file.name === fileName) {
|
|
||||||
newJSFile.content = newJSFile.content.replace(filePath, file.url); // eslint-disable-line
|
|
||||||
}
|
|
||||||
});
|
|
||||||
textFiles.forEach(file => {
|
|
||||||
if (file.name === fileName) {
|
|
||||||
const blobURL = getBlobUrl(file);
|
|
||||||
this.props.setBlobUrl(file, blobURL);
|
|
||||||
newJSFile.content = newJSFile.content.replace(filePath, blobURL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
newJSFile.content = loopProtect(newJSFile.content);
|
|
||||||
jsFiles.push(newJSFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
const cssFiles = [];
|
this.resolveScripts(sketchDoc, resolvedFiles);
|
||||||
this.props.cssFiles.forEach(cssFile => {
|
this.resolveStyles(sketchDoc, resolvedFiles);
|
||||||
const newCSSFile = { ...cssFile };
|
|
||||||
let cssFileStrings = newCSSFile.content.match(STRING_REGEX);
|
|
||||||
cssFileStrings = cssFileStrings || [];
|
|
||||||
cssFileStrings.forEach(cssFileString => {
|
|
||||||
if (cssFileString.match(MEDIA_FILE_REGEX)) {
|
|
||||||
const filePath = cssFileString.substr(1, cssFileString.length - 2);
|
|
||||||
const filePathArray = filePath.split('/');
|
|
||||||
const fileName = filePathArray[filePathArray.length - 1];
|
|
||||||
mediaFiles.forEach(file => {
|
|
||||||
if (file.name === fileName) {
|
|
||||||
newCSSFile.content = newCSSFile.content.replace(filePath, file.url); // eslint-disable-line
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cssFiles.push(newCSSFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
jsFiles.forEach(jsFile => {
|
let scriptsToInject = [
|
||||||
const fileName = escapeStringRegexp(jsFile.name);
|
'/loop-protect.min.js',
|
||||||
const fileRegex = new RegExp(`<script.*?src=('|")((\.\/)|\/)?${fileName}('|").*?>([\s\S]*?)<\/script>`, 'gmi');
|
'/hijackConsole.js'
|
||||||
let replacementString;
|
];
|
||||||
if (jsFile.url) {
|
if (this.props.isTextOutputPlaying) {
|
||||||
replacementString = `<script data-tag="${startTag}${jsFile.name}" src="${jsFile.url}"></script>`;
|
let interceptorScripts = [];
|
||||||
} else {
|
if (this.props.textOutput === 1) {
|
||||||
replacementString = `<script data-tag="${startTag}${jsFile.name}">\n${jsFile.content}\n</script>`;
|
interceptorScripts = [
|
||||||
|
'/interceptor/loadData.js',
|
||||||
|
'/interceptor/intercept-helper-functions.js',
|
||||||
|
'/interceptor/textInterceptor/interceptor-functions.js',
|
||||||
|
'/interceptor/textInterceptor/intercept-p5.js',
|
||||||
|
'/interceptor/ntc.min.js'
|
||||||
|
];
|
||||||
|
} else if (this.props.textOutput === 2) {
|
||||||
|
interceptorScripts = [
|
||||||
|
'/interceptor/loadData.js',
|
||||||
|
'/interceptor/intercept-helper-functions.js',
|
||||||
|
'/interceptor/gridInterceptor/interceptor-functions.js',
|
||||||
|
'/interceptor/gridInterceptor/intercept-p5.js',
|
||||||
|
'/interceptor/ntc.min.js'
|
||||||
|
];
|
||||||
|
} else if (this.props.textOutput === 3) {
|
||||||
|
interceptorScripts = [
|
||||||
|
'/interceptor/loadData.js',
|
||||||
|
'/interceptor/soundInterceptor/intercept-p5.js'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
htmlFile = htmlFile.replace(fileRegex, replacementString);
|
scriptsToInject = scriptsToInject.concat(interceptorScripts);
|
||||||
});
|
|
||||||
|
|
||||||
cssFiles.forEach(cssFile => {
|
|
||||||
const fileName = escapeStringRegexp(cssFile.name);
|
|
||||||
const fileRegex = new RegExp(`<link.*?href=('|")((\.\/)|\/)?${fileName}('|").*?>`, 'gmi');
|
|
||||||
let replacementString;
|
|
||||||
if (cssFile.url) {
|
|
||||||
replacementString = `<link rel="stylesheet" href="${cssFile.url}" >`;
|
|
||||||
} else {
|
|
||||||
replacementString = `<style>\n${cssFile.content}\n</style>`;
|
|
||||||
}
|
|
||||||
htmlFile = htmlFile.replace(fileRegex, replacementString);
|
|
||||||
});
|
|
||||||
|
|
||||||
const htmlHead = htmlFile.match(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi);
|
|
||||||
const headRegex = new RegExp('head', 'i');
|
|
||||||
let htmlHeadContents = htmlHead[0].split(headRegex)[1];
|
|
||||||
htmlHeadContents = htmlHeadContents.slice(1, htmlHeadContents.length - 2);
|
|
||||||
htmlHeadContents += '<script type="text/javascript" src="/loop-protect.min.js"></script>\n';
|
|
||||||
|
|
||||||
if (this.props.textOutput === 1 || this.props.isTextOutputPlaying) {
|
|
||||||
htmlHeadContents += '<script src="/interceptor/loadData.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/intercept-helper-functions.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/textInterceptor/interceptor-functions.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/textInterceptor/intercept-p5.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script type="text/javascript" src="/interceptor/ntc.min.js"></script>';
|
|
||||||
} else if (this.props.textOutput === 2 || this.props.isTextOutputPlaying) {
|
|
||||||
htmlHeadContents += '<script src="/interceptor/loadData.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/intercept-helper-functions.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/gridInterceptor/interceptor-functions.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/gridInterceptor/intercept-p5.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script type="text/javascript" src="/interceptor/ntc.min.js"></script>';
|
|
||||||
} else if (this.props.textOutput === 3 || this.props.isTextOutputPlaying) {
|
|
||||||
htmlHeadContents += '<script src="/interceptor/loadData.js"></script>\n';
|
|
||||||
htmlHeadContents += '<script src="/interceptor/soundInterceptor/intercept-p5.js"></script>\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlFile = htmlFile.replace(/(?:<head.*?>)([\s\S]*?)(?:<\/head>)/gmi, `<head>\n${htmlHeadContents}\n</head>`);
|
scriptsToInject.forEach(scriptToInject => {
|
||||||
|
const script = sketchDoc.createElement('script');
|
||||||
|
script.src = scriptToInject;
|
||||||
|
sketchDoc.head.appendChild(script);
|
||||||
|
});
|
||||||
|
|
||||||
scriptOffs = getAllScriptOffsets(htmlFile);
|
const sketchDocString = `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
|
||||||
htmlFile += hijackConsoleErrorsScript(JSON.stringify(scriptOffs));
|
scriptOffs = getAllScriptOffsets(sketchDocString);
|
||||||
|
const consoleErrorsScript = sketchDoc.createElement('script');
|
||||||
|
consoleErrorsScript.innerHTML = hijackConsoleErrorsScript(JSON.stringify(scriptOffs));
|
||||||
|
sketchDoc.head.appendChild(consoleErrorsScript);
|
||||||
|
|
||||||
return htmlFile;
|
return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
||||||
|
const elements = sketchDoc.querySelectorAll(`[${attr}]`);
|
||||||
|
elements.forEach(element => {
|
||||||
|
if (element.getAttribute(attr).match(MEDIA_FILE_REGEX_NO_QUOTES)) {
|
||||||
|
const resolvedFile = resolvePathToFile(element.getAttribute(attr), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
element.setAttribute(attr, resolvedFile.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveJSAndCSSLinks(files) {
|
||||||
|
const newFiles = [];
|
||||||
|
files.forEach(file => {
|
||||||
|
const newFile = { ...file };
|
||||||
|
if (file.name.match(/.*\.js$/i)) {
|
||||||
|
newFile.content = this.resolveJSLinksInString(newFile.content, files);
|
||||||
|
} else if (file.name.match(/.*\.css$/i)) {
|
||||||
|
newFile.content = this.resolveCSSLinksInString(newFile.content, files);
|
||||||
|
}
|
||||||
|
newFiles.push(newFile);
|
||||||
|
});
|
||||||
|
return newFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveJSLinksInString(content, files) {
|
||||||
|
let newContent = content;
|
||||||
|
let jsFileStrings = content.match(STRING_REGEX);
|
||||||
|
jsFileStrings = jsFileStrings || [];
|
||||||
|
jsFileStrings.forEach(jsFileString => {
|
||||||
|
if (jsFileString.match(MEDIA_FILE_REGEX)) {
|
||||||
|
const filePath = jsFileString.substr(1, jsFileString.length - 2);
|
||||||
|
const resolvedFile = resolvePathToFile(filePath, files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
newContent = newContent.replace(filePath, resolvedFile.url);
|
||||||
|
} else if (resolvedFile.name.match(TEXT_FILE_REGEX)) {
|
||||||
|
// could also pull file from API instead of using bloburl
|
||||||
|
const blobURL = getBlobUrl(resolvedFile);
|
||||||
|
this.props.setBlobUrl(resolvedFile, blobURL);
|
||||||
|
newContent = newContent.replace(filePath, blobURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newContent = loopProtect(newContent);
|
||||||
|
return newContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveCSSLinksInString(content, files) {
|
||||||
|
let newContent = content;
|
||||||
|
let cssFileStrings = content.match(STRING_REGEX);
|
||||||
|
cssFileStrings = cssFileStrings || [];
|
||||||
|
cssFileStrings.forEach(cssFileString => {
|
||||||
|
if (cssFileString.match(MEDIA_FILE_REGEX)) {
|
||||||
|
const filePath = cssFileString.substr(1, cssFileString.length - 2);
|
||||||
|
const resolvedFile = resolvePathToFile(filePath, files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
newContent = newContent.replace(filePath, resolvedFile.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveScripts(sketchDoc, files) {
|
||||||
|
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
||||||
|
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
||||||
|
scriptsInHTMLArray.forEach(script => {
|
||||||
|
if (script.getAttribute('src') && script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||||
|
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
script.setAttribute('src', resolvedFile.url);
|
||||||
|
} else {
|
||||||
|
script.removeAttribute('src');
|
||||||
|
script.innerHTML = resolvedFile.content; // eslint-disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!(script.getAttribute('src') && script.getAttribute('src').match(EXTERNAL_LINK_REGEX)) !== null) {
|
||||||
|
script.innerHTML = this.resolveJSLinksInString(script.innerHTML, files); // eslint-disable-line
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveStyles(sketchDoc, files) {
|
||||||
|
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
|
||||||
|
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
|
||||||
|
inlineCSSInHTMLArray.forEach(style => {
|
||||||
|
style.innerHTML = this.resolveCSSLinksInString(style.innerHTML, files); // eslint-disable-line
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssLinksInHTML = sketchDoc.querySelectorAll('link[rel="stylesheet"]');
|
||||||
|
cssLinksInHTML.forEach(css => {
|
||||||
|
if (css.getAttribute('href') && css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||||
|
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
css.href = resolvedFile.url; // eslint-disable-line
|
||||||
|
} else {
|
||||||
|
const style = sketchDoc.createElement('style');
|
||||||
|
style.innerHTML = `\n${resolvedFile.content}`;
|
||||||
|
sketchDoc.head.appendChild(style);
|
||||||
|
css.parentElement.removeChild(css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSketch() {
|
renderSketch() {
|
||||||
|
@ -331,8 +355,6 @@ PreviewFrame.propTypes = {
|
||||||
htmlFile: PropTypes.shape({
|
htmlFile: PropTypes.shape({
|
||||||
content: PropTypes.string.isRequired
|
content: PropTypes.string.isRequired
|
||||||
}),
|
}),
|
||||||
jsFiles: PropTypes.array.isRequired,
|
|
||||||
cssFiles: PropTypes.array.isRequired,
|
|
||||||
files: PropTypes.array.isRequired,
|
files: PropTypes.array.isRequired,
|
||||||
dispatchConsoleEvent: PropTypes.func,
|
dispatchConsoleEvent: PropTypes.func,
|
||||||
children: PropTypes.element,
|
children: PropTypes.element,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
function ResetPasswordForm(props) {
|
function ResetPasswordForm(props) {
|
||||||
const { fields: { email }, handleSubmit, submitting, invalid, pristine } = props;
|
const { fields: { email }, handleSubmit, submitting, invalid, pristine } = props;
|
||||||
|
@ -10,7 +11,7 @@ function ResetPasswordForm(props) {
|
||||||
aria-label="email"
|
aria-label="email"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Email used for registration"
|
placeholder="Email used for registration"
|
||||||
{...email}
|
{...domOnlyProps(email)}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<input type="submit" disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate} value="Send password reset email" aria-label="Send email to reset password" />
|
<input type="submit" disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate} value="Send password reset email" aria-label="Send email to reset password" />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
function SignupForm(props) {
|
function SignupForm(props) {
|
||||||
const { fields: { username, email, password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props;
|
const { fields: { username, email, password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props;
|
||||||
|
@ -10,7 +11,7 @@ function SignupForm(props) {
|
||||||
aria-label="username"
|
aria-label="username"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
{...username}
|
{...domOnlyProps(username)}
|
||||||
/>
|
/>
|
||||||
{username.touched && username.error && <span className="form-error">{username.error}</span>}
|
{username.touched && username.error && <span className="form-error">{username.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
@ -20,7 +21,7 @@ function SignupForm(props) {
|
||||||
aria-label="email"
|
aria-label="email"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
{...email}
|
{...domOnlyProps(email)}
|
||||||
/>
|
/>
|
||||||
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
@ -30,7 +31,7 @@ function SignupForm(props) {
|
||||||
aria-label="password"
|
aria-label="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
{...password}
|
{...domOnlyProps(password)}
|
||||||
/>
|
/>
|
||||||
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
{password.touched && password.error && <span className="form-error">{password.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
@ -40,7 +41,7 @@ function SignupForm(props) {
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Confirm Password"
|
placeholder="Confirm Password"
|
||||||
aria-label="confirm password"
|
aria-label="confirm password"
|
||||||
{...confirmPassword}
|
{...domOnlyProps(confirmPassword)}
|
||||||
/>
|
/>
|
||||||
{confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>}
|
{confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -22,7 +22,7 @@ import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
||||||
import * as PreferencesActions from '../actions/preferences';
|
import * as PreferencesActions from '../actions/preferences';
|
||||||
import * as UserActions from '../../User/actions';
|
import * as UserActions from '../../User/actions';
|
||||||
import * as ToastActions from '../actions/toast';
|
import * as ToastActions from '../actions/toast';
|
||||||
import { getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files';
|
import { getHTMLFile } from '../reducers/files';
|
||||||
import SplitPane from 'react-split-pane';
|
import SplitPane from 'react-split-pane';
|
||||||
import Overlay from '../../App/components/Overlay';
|
import Overlay from '../../App/components/Overlay';
|
||||||
import SketchList from '../components/SketchList';
|
import SketchList from '../components/SketchList';
|
||||||
|
@ -344,8 +344,6 @@ class IDEView extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<PreviewFrame
|
<PreviewFrame
|
||||||
htmlFile={this.props.htmlFile}
|
htmlFile={this.props.htmlFile}
|
||||||
jsFiles={this.props.jsFiles}
|
|
||||||
cssFiles={this.props.cssFiles}
|
|
||||||
files={this.props.files}
|
files={this.props.files}
|
||||||
content={this.props.selectedFile.content}
|
content={this.props.selectedFile.content}
|
||||||
isPlaying={this.props.ide.isPlaying}
|
isPlaying={this.props.ide.isPlaying}
|
||||||
|
@ -559,8 +557,6 @@ IDEView.propTypes = {
|
||||||
}),
|
}),
|
||||||
setSelectedFile: PropTypes.func.isRequired,
|
setSelectedFile: PropTypes.func.isRequired,
|
||||||
htmlFile: PropTypes.object.isRequired,
|
htmlFile: PropTypes.object.isRequired,
|
||||||
jsFiles: PropTypes.array.isRequired,
|
|
||||||
cssFiles: PropTypes.array.isRequired,
|
|
||||||
dispatchConsoleEvent: PropTypes.func.isRequired,
|
dispatchConsoleEvent: PropTypes.func.isRequired,
|
||||||
newFile: PropTypes.func.isRequired,
|
newFile: PropTypes.func.isRequired,
|
||||||
closeNewFileModal: PropTypes.func.isRequired,
|
closeNewFileModal: PropTypes.func.isRequired,
|
||||||
|
@ -617,8 +613,6 @@ function mapStateToProps(state) {
|
||||||
files: state.files,
|
files: state.files,
|
||||||
selectedFile: state.files.find(file => file.isSelectedFile),
|
selectedFile: state.files.find(file => file.isSelectedFile),
|
||||||
htmlFile: getHTMLFile(state.files),
|
htmlFile: getHTMLFile(state.files),
|
||||||
jsFiles: getJSFiles(state.files),
|
|
||||||
cssFiles: getCSSFiles(state.files),
|
|
||||||
ide: state.ide,
|
ide: state.ide,
|
||||||
preferences: state.preferences,
|
preferences: state.preferences,
|
||||||
editorAccessibility: state.editorAccessibility,
|
editorAccessibility: state.editorAccessibility,
|
||||||
|
|
16
client/utils/reduxFormUtils.js
Normal file
16
client/utils/reduxFormUtils.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
export const domOnlyProps = ({
|
||||||
|
initialValue,
|
||||||
|
autofill,
|
||||||
|
onUpdate,
|
||||||
|
valid,
|
||||||
|
invalid,
|
||||||
|
dirty,
|
||||||
|
pristine,
|
||||||
|
active,
|
||||||
|
touched,
|
||||||
|
visited,
|
||||||
|
autofilled,
|
||||||
|
error,
|
||||||
|
...domProps }) => domProps;
|
||||||
|
/* eslint-enable */
|
|
@ -57,7 +57,7 @@
|
||||||
"webpack-manifest-plugin": "^1.1.0"
|
"webpack-manifest-plugin": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^1.1.0",
|
"archiver": "^1.1.0",
|
||||||
|
@ -82,6 +82,7 @@
|
||||||
"file-type": "^3.8.0",
|
"file-type": "^3.8.0",
|
||||||
"htmlhint": "^0.9.13",
|
"htmlhint": "^0.9.13",
|
||||||
"js-beautify": "^1.6.4",
|
"js-beautify": "^1.6.4",
|
||||||
|
"jsdom": "^9.8.3",
|
||||||
"jshint": "^2.9.2",
|
"jshint": "^2.9.2",
|
||||||
"lodash": "^4.16.4",
|
"lodash": "^4.16.4",
|
||||||
"loop-protect": "git+https://git@github.com/catarak/loop-protect.git",
|
"loop-protect": "git+https://git@github.com/catarak/loop-protect.git",
|
||||||
|
@ -93,11 +94,11 @@
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.1.0",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.1.0",
|
||||||
"react-inlinesvg": "^0.4.2",
|
"react-inlinesvg": "^0.4.2",
|
||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
"react-router": "^2.4.1",
|
"react-router": "^2.6.0",
|
||||||
"react-split-pane": "^0.1.44",
|
"react-split-pane": "^0.1.44",
|
||||||
"redux": "^3.5.2",
|
"redux": "^3.5.2",
|
||||||
"redux-form": "^5.3.3",
|
"redux-form": "^5.3.3",
|
||||||
|
|
|
@ -2,35 +2,12 @@ import Project from '../models/project';
|
||||||
import escapeStringRegexp from 'escape-string-regexp';
|
import escapeStringRegexp from 'escape-string-regexp';
|
||||||
const startTag = '@fs-';
|
const startTag = '@fs-';
|
||||||
import { resolvePathToFile } from '../utils/filePath';
|
import { resolvePathToFile } from '../utils/filePath';
|
||||||
|
import {
|
||||||
function injectMediaUrls(filesToInject, allFiles, projectId) {
|
injectMediaUrls,
|
||||||
filesToInject.forEach(file => {
|
resolvePathsForElementsWithAttribute,
|
||||||
let fileStrings = file.content.match(/(['"])((\\\1|.)*?)\1/gm);
|
resolveScripts,
|
||||||
const fileStringRegex = /^('|")(?!(http:\/\/|https:\/\/)).*('|")$/i;
|
resolveStyles } from '../utils/previewGeneration';
|
||||||
fileStrings = fileStrings || [];
|
import jsdom, { serializeDocument } from 'jsdom';
|
||||||
fileStrings.forEach(fileString => {
|
|
||||||
//if string does not begin with http or https
|
|
||||||
if (fileString.match(fileStringRegex)) {
|
|
||||||
const filePath = fileString.substr(1, fileString.length - 2);
|
|
||||||
const resolvedFile = resolvePathToFile(filePath, allFiles);
|
|
||||||
if (resolvedFile) {
|
|
||||||
if (resolvedFile.url) {
|
|
||||||
file.content = file.content.replace(filePath,resolvedFile.url);
|
|
||||||
} else if (resolvedFile.name.match(/(.+\.json$|.+\.txt$|.+\.csv$)/i)) {
|
|
||||||
let resolvedFilePath = filePath;
|
|
||||||
if (resolvedFilePath.startsWith('.')) {
|
|
||||||
resolvedFilePath = resolvedFilePath.substr(1);
|
|
||||||
}
|
|
||||||
while (resolvedFilePath.startsWith('/')) {
|
|
||||||
resolvedFilePath = resolvedFilePath.substr(1);
|
|
||||||
}
|
|
||||||
file.content = file.content.replace(filePath, `/api/projects/${projectId}/${resolvedFilePath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serveProject(req, res) {
|
export function serveProject(req, res) {
|
||||||
Project.findById(req.params.project_id)
|
Project.findById(req.params.project_id)
|
||||||
|
@ -38,25 +15,17 @@ export function serveProject(req, res) {
|
||||||
//TODO this does not parse html
|
//TODO this does not parse html
|
||||||
const files = project.files;
|
const files = project.files;
|
||||||
let htmlFile = files.find(file => file.name.match(/\.html$/i)).content;
|
let htmlFile = files.find(file => file.name.match(/\.html$/i)).content;
|
||||||
const jsFiles = files.filter(file => file.name.match(/\.js$/i));
|
const filesToInject = files.filter(file => file.name.match(/\.(js|css)$/i));
|
||||||
const cssFiles = files.filter(file => file.name.match(/\.css$/i));
|
injectMediaUrls(filesToInject, files, req.params.project_id);
|
||||||
|
|
||||||
injectMediaUrls(jsFiles, files, req.params.project_id);
|
jsdom.env(htmlFile, (err, window) => {
|
||||||
injectMediaUrls(cssFiles, files, req.params.project_id);
|
const sketchDoc = window.document;
|
||||||
|
resolvePathsForElementsWithAttribute('src', sketchDoc, files);
|
||||||
|
resolvePathsForElementsWithAttribute('href', sketchDoc, files);
|
||||||
|
resolveScripts(sketchDoc, files);
|
||||||
|
resolveStyles(sketchDoc, files);
|
||||||
|
|
||||||
jsFiles.forEach(jsFile => {
|
res.send(serializeDocument(sketchDoc));
|
||||||
const fileName = escapeStringRegexp(jsFile.name);
|
|
||||||
const fileRegex = new RegExp(`<script.*?src=('|")((\.\/)|\/)?${fileName}('|").*?>([\s\S]*?)<\/script>`, 'gmi');
|
|
||||||
const replacementString = `<script data-tag="${startTag}${jsFile.name}">\n${jsFile.content}\n</script>`;
|
|
||||||
htmlFile = htmlFile.replace(fileRegex, replacementString);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cssFiles.forEach(cssFile => {
|
|
||||||
const fileName = escapeStringRegexp(cssFile.name);
|
|
||||||
const fileRegex = new RegExp(`<link.*?href=('|")((\.\/)|\/)?${fileName}('|").*?>`, 'gmi');
|
|
||||||
htmlFile = htmlFile.replace(fileRegex, `<style>\n${cssFile.content}\n</style>`);
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send(htmlFile);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,38 +1,32 @@
|
||||||
export function resolvePathToFile(filePath, files) {
|
export function resolvePathToFile(filePath, files) {
|
||||||
const filePathArray = filePath.split('/');
|
const filePathArray = filePath.split('/');
|
||||||
let resolvedFile;
|
let resolvedFile;
|
||||||
let currentFile;
|
let currentFile = files.find(file => file.name === 'root');
|
||||||
filePathArray.some((filePathSegment, index) => {
|
filePathArray.some((filePathSegment, index) => {
|
||||||
if (filePathSegment === "" || filePathSegment === ".") {
|
if (filePathSegment === '' || filePathSegment === '.') {
|
||||||
return false;
|
return false;
|
||||||
} else if (filePathSegment === "..") {
|
} else if (filePathSegment === '..') {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
if (!currentFile) {
|
|
||||||
const file = files.find(file => file.name === filePathSegment);
|
|
||||||
if (!file) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
currentFile = file;
|
|
||||||
if (index === filePathArray.length - 1) {
|
|
||||||
resolvedFile = file;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const childFiles = currentFile.children.map(childFileId => {
|
|
||||||
return files.find(file => {
|
|
||||||
return file._id.valueOf().toString() === childFileId.valueOf();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
childFiles.some(childFile => {
|
|
||||||
if (childFile.name === filePathSegment) {
|
|
||||||
currentFile = childFile;
|
|
||||||
if (index === filePathArray.length - 1) {
|
|
||||||
resolvedFile = childFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let foundChild = false;
|
||||||
|
const childFiles = currentFile.children.map(childFileId =>
|
||||||
|
files.find(file =>
|
||||||
|
file._id.valueOf().toString() === childFileId.valueOf()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
childFiles.some(childFile => {
|
||||||
|
if (childFile.name === filePathSegment) {
|
||||||
|
currentFile = childFile;
|
||||||
|
foundChild = true;
|
||||||
|
if (index === filePathArray.length - 1) {
|
||||||
|
resolvedFile = childFile;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return !foundChild;
|
||||||
});
|
});
|
||||||
return resolvedFile;
|
return resolvedFile;
|
||||||
}
|
}
|
102
server/utils/previewGeneration.js
Normal file
102
server/utils/previewGeneration.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { resolvePathToFile } from '../utils/filePath';
|
||||||
|
|
||||||
|
const MEDIA_FILE_REGEX = /^('|")(?!(http:\/\/|https:\/\/)).*\.(png|jpg|jpeg|gif|bmp|mp3|wav|aiff|ogg|json|txt|csv|svg|obj|mp4|ogg|webm|mov)('|")$/i;
|
||||||
|
const MEDIA_FILE_REGEX_NO_QUOTES = /^(?!(http:\/\/|https:\/\/)).*\.(png|jpg|jpeg|gif|bmp|mp3|wav|aiff|ogg|json|txt|csv|svg|obj|mp4|ogg|webm|mov)$/i;
|
||||||
|
const STRING_REGEX = /(['"])((\\\1|.)*?)\1/gm;
|
||||||
|
const TEXT_FILE_REGEX = /(.+\.json$|.+\.txt$|.+\.csv$)/i;
|
||||||
|
const EXTERNAL_LINK_REGEX = /^(http:\/\/|https:\/\/)/;
|
||||||
|
const NOT_EXTERNAL_LINK_REGEX = /^(?!(http:\/\/|https:\/\/))/;
|
||||||
|
|
||||||
|
function resolveLinksInString(content, files, projectId) {
|
||||||
|
let newContent = content;
|
||||||
|
let fileStrings = content.match(STRING_REGEX);
|
||||||
|
const fileStringRegex = /^('|")(?!(http:\/\/|https:\/\/)).*('|")$/i;
|
||||||
|
fileStrings = fileStrings || [];
|
||||||
|
fileStrings.forEach(fileString => {
|
||||||
|
//if string does not begin with http or https
|
||||||
|
if (fileString.match(fileStringRegex)) {
|
||||||
|
const filePath = fileString.substr(1, fileString.length - 2);
|
||||||
|
const resolvedFile = resolvePathToFile(filePath, files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
newContent = newContent.replace(filePath,resolvedFile.url);
|
||||||
|
} else if (resolvedFile.name.match(/(.+\.json$|.+\.txt$|.+\.csv$)/i)) {
|
||||||
|
let resolvedFilePath = filePath;
|
||||||
|
if (resolvedFilePath.startsWith('.')) {
|
||||||
|
resolvedFilePath = resolvedFilePath.substr(1);
|
||||||
|
}
|
||||||
|
while (resolvedFilePath.startsWith('/')) {
|
||||||
|
resolvedFilePath = resolvedFilePath.substr(1);
|
||||||
|
}
|
||||||
|
newContent = newContent.replace(filePath, `/api/projects/${projectId}/${resolvedFilePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function injectMediaUrls(filesToInject, allFiles, projectId) {
|
||||||
|
filesToInject.forEach((file, index) => {
|
||||||
|
file.content = resolveLinksInString(file.content, allFiles, projectId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePathsForElementsWithAttribute(attr, sketchDoc, files) {
|
||||||
|
const elements = sketchDoc.querySelectorAll(`[${attr}]`);
|
||||||
|
const elementsArray = Array.prototype.slice.call(elements);
|
||||||
|
elementsArray.forEach(element => {
|
||||||
|
if (element.getAttribute(attr).match(MEDIA_FILE_REGEX_NO_QUOTES)) {
|
||||||
|
const resolvedFile = resolvePathToFile(element.getAttribute(attr), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
element.setAttribute(attr, resolvedFile.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveScripts(sketchDoc, files, projectId) {
|
||||||
|
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
|
||||||
|
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
|
||||||
|
scriptsInHTMLArray.forEach(script => {
|
||||||
|
if (script.getAttribute('src') && script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||||
|
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
script.setAttribute('src', resolvedFile.url);
|
||||||
|
} else {
|
||||||
|
script.removeAttribute('src');
|
||||||
|
script.innerHTML = resolvedFile.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!(script.getAttribute('src') && script.getAttribute('src').match(EXTERNAL_LINK_REGEX) !== null)) {
|
||||||
|
script.innerHTML = resolveLinksInString(script.innerHTML, files, projectId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveStyles(sketchDoc, files, projectId) {
|
||||||
|
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
|
||||||
|
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
|
||||||
|
inlineCSSInHTMLArray.forEach(style => {
|
||||||
|
style.innerHTML = resolveLinksInString(style.innerHTML, files, projectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssLinksInHTML = sketchDoc.querySelectorAll('link[rel="stylesheet"]');
|
||||||
|
const cssLinksInHTMLArray = Array.prototype.slice.call(cssLinksInHTML);
|
||||||
|
cssLinksInHTMLArray.forEach(css => {
|
||||||
|
if (css.getAttribute('href') && css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null) {
|
||||||
|
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
|
||||||
|
if (resolvedFile) {
|
||||||
|
if (resolvedFile.url) {
|
||||||
|
css.setAttribute('href', resolvedFile.url);
|
||||||
|
} else {
|
||||||
|
const style = sketchDoc.createElement('style');
|
||||||
|
style.innerHTML = `\n${resolvedFile.content}`;
|
||||||
|
sketchDoc.getElementsByTagName("head")[0].appendChild(style);
|
||||||
|
css.parentNode.removeChild(css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
35
static/hijackConsole.js
Normal file
35
static/hijackConsole.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
var iframeWindow = window;
|
||||||
|
var originalConsole = iframeWindow.console;
|
||||||
|
iframeWindow.console = {};
|
||||||
|
|
||||||
|
var methods = [
|
||||||
|
'debug', 'clear', 'error', 'info', 'log', 'warn'
|
||||||
|
];
|
||||||
|
|
||||||
|
var consoleBuffer = [];
|
||||||
|
var LOGWAIT = 500;
|
||||||
|
|
||||||
|
methods.forEach( function(method) {
|
||||||
|
iframeWindow.console[method] = function() {
|
||||||
|
originalConsole[method].apply(originalConsole, arguments);
|
||||||
|
|
||||||
|
var args = Array.from(arguments);
|
||||||
|
args = args.map(function(i) {
|
||||||
|
// catch objects
|
||||||
|
return (typeof i === 'string') ? i : JSON.stringify(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
consoleBuffer.push({
|
||||||
|
method: method,
|
||||||
|
arguments: args,
|
||||||
|
source: 'sketch'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
if (consoleBuffer.length > 0) {
|
||||||
|
window.parent.postMessage(consoleBuffer, '*');
|
||||||
|
consoleBuffer.length = 0;
|
||||||
|
}
|
||||||
|
}, LOGWAIT);
|
|
@ -22,7 +22,7 @@ module.exports = {
|
||||||
extensions: ['', '.js', '.jsx'],
|
extensions: ['', '.js', '.jsx'],
|
||||||
modules: [
|
modules: [
|
||||||
'client',
|
'client',
|
||||||
'node_modules',
|
'node_modules'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in a new issue