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
						5e0216f2ec
					
				
					 15 changed files with 384 additions and 245 deletions
				
			
		|  | @ -1,4 +1,5 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| function LoginForm(props) { | ||||
|   const { fields: { email, password }, handleSubmit, submitting, pristine } = props; | ||||
|  | @ -10,7 +11,7 @@ function LoginForm(props) { | |||
|           aria-label="email" | ||||
|           type="text" | ||||
|           placeholder="Email" | ||||
|           {...email} | ||||
|           {...domOnlyProps(email)} | ||||
|         /> | ||||
|         {email.touched && email.error && <span className="form-error">{email.error}</span>} | ||||
|       </p> | ||||
|  | @ -20,7 +21,7 @@ function LoginForm(props) { | |||
|           aria-label="password" | ||||
|           type="password" | ||||
|           placeholder="Password" | ||||
|           {...password} | ||||
|           {...domOnlyProps(password)} | ||||
|         /> | ||||
|         {password.touched && password.error && <span className="form-error">{password.error}</span>} | ||||
|       </p> | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| class NewFileForm extends React.Component { | ||||
|   constructor(props) { | ||||
|  | @ -21,7 +22,7 @@ class NewFileForm extends React.Component { | |||
|           type="text" | ||||
|           placeholder="Name" | ||||
|           ref="fileName" | ||||
|           {...name} | ||||
|           {...domOnlyProps(name)} | ||||
|         /> | ||||
|         <input type="submit" value="Add File" aria-label="add file" /> | ||||
|         {name.touched && name.error && <span className="form-error">{name.error}</span>} | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| class NewFolderForm extends React.Component { | ||||
|   constructor(props) { | ||||
|  | @ -21,7 +22,7 @@ class NewFolderForm extends React.Component { | |||
|           type="text" | ||||
|           placeholder="Name" | ||||
|           ref="fileName" | ||||
|           {...name} | ||||
|           {...domOnlyProps(name)} | ||||
|         /> | ||||
|         <input type="submit" value="Add Folder" aria-label="add folder" /> | ||||
|       </form> | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| function NewPasswordForm(props) { | ||||
|   const { fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props; | ||||
|  | @ -10,7 +11,7 @@ function NewPasswordForm(props) { | |||
|           aria-label="password" | ||||
|           type="password" | ||||
|           placeholder="Password" | ||||
|           {...password} | ||||
|           {...domOnlyProps(password)} | ||||
|         /> | ||||
|         {password.touched && password.error && <span className="form-error">{password.error}</span>} | ||||
|       </p> | ||||
|  | @ -20,7 +21,7 @@ function NewPasswordForm(props) { | |||
|           type="password" | ||||
|           placeholder="Confirm Password" | ||||
|           aria-label="confirm password" | ||||
|           {...confirmPassword} | ||||
|           {...domOnlyProps(confirmPassword)} | ||||
|         /> | ||||
|         {confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>} | ||||
|       </p> | ||||
|  |  | |||
|  | @ -1,14 +1,19 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import escapeStringRegexp from 'escape-string-regexp'; | ||||
| // import escapeStringRegexp from 'escape-string-regexp'; | ||||
| import srcDoc from 'srcdoc-polyfill'; | ||||
| 
 | ||||
| import loopProtect from 'loop-protect'; | ||||
| import { getBlobUrl } from '../actions/files'; | ||||
| import { resolvePathToFile } from '../../../../server/utils/filePath'; | ||||
| 
 | ||||
| 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_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 NOT_EXTERNAL_LINK_REGEX = /^(?!(http:\/\/|https:\/\/))/; | ||||
| const EXTERNAL_LINK_REGEX = /^(http:\/\/|https:\/\/)/; | ||||
| 
 | ||||
| function getAllScriptOffsets(htmlFile) { | ||||
|   const offs = []; | ||||
|  | @ -33,50 +38,8 @@ function getAllScriptOffsets(htmlFile) { | |||
|   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) { | ||||
|   const s = `<script> | ||||
|   const s = ` | ||||
|     function getScriptOff(line) { | ||||
|       var offs = ${offs}; | ||||
|       var l = 0; | ||||
|  | @ -111,7 +74,7 @@ function hijackConsoleErrorsScript(offs) { | |||
|         }], '*'); | ||||
|       return false; | ||||
|     }; | ||||
|   </script>`; | ||||
|   `; | ||||
|   return s; | ||||
| } | ||||
| 
 | ||||
|  | @ -172,117 +135,178 @@ class PreviewFrame extends React.Component { | |||
|   } | ||||
| 
 | ||||
|   injectLocalFiles() { | ||||
|     let htmlFile = this.props.htmlFile.content; | ||||
|     const htmlFile = this.props.htmlFile.content; | ||||
|     let scriptOffs = []; | ||||
| 
 | ||||
|     // have to build the array manually because the spread operator is only | ||||
|     // one level down... | ||||
|     const resolvedFiles = this.resolveJSAndCSSLinks(this.props.files); | ||||
| 
 | ||||
|     htmlFile = hijackConsoleLogsScript() + htmlFile; | ||||
|     const mediaFiles = this.props.files.filter(file => file.url); | ||||
|     const textFiles = this.props.files.filter(file => file.name.match(/(.+\.json$|.+\.txt$|.+\.csv$)/i) && file.url === undefined); | ||||
|     const parser = new DOMParser(); | ||||
|     const sketchDoc = parser.parseFromString(htmlFile, 'text/html'); | ||||
| 
 | ||||
|     const jsFiles = []; | ||||
|     this.props.jsFiles.forEach(jsFile => { | ||||
|       const newJSFile = { ...jsFile }; | ||||
|       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); | ||||
|     }); | ||||
|     this.resolvePathsForElementsWithAttribute('src', sketchDoc, resolvedFiles); | ||||
|     this.resolvePathsForElementsWithAttribute('href', sketchDoc, resolvedFiles); | ||||
|     // should also include background, data, poster, but these are used way less often | ||||
| 
 | ||||
|     const cssFiles = []; | ||||
|     this.props.cssFiles.forEach(cssFile => { | ||||
|       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); | ||||
|     }); | ||||
|     this.resolveScripts(sketchDoc, resolvedFiles); | ||||
|     this.resolveStyles(sketchDoc, resolvedFiles); | ||||
| 
 | ||||
|     jsFiles.forEach(jsFile => { | ||||
|       const fileName = escapeStringRegexp(jsFile.name); | ||||
|       const fileRegex = new RegExp(`<script.*?src=('|")((\.\/)|\/)?${fileName}('|").*?>([\s\S]*?)<\/script>`, 'gmi'); | ||||
|       let replacementString; | ||||
|       if (jsFile.url) { | ||||
|         replacementString = `<script data-tag="${startTag}${jsFile.name}" src="${jsFile.url}"></script>`; | ||||
|       } else { | ||||
|         replacementString = `<script data-tag="${startTag}${jsFile.name}">\n${jsFile.content}\n</script>`; | ||||
|     let scriptsToInject = [ | ||||
|       '/loop-protect.min.js', | ||||
|       '/hijackConsole.js' | ||||
|     ]; | ||||
|     if (this.props.isTextOutputPlaying) { | ||||
|       let interceptorScripts = []; | ||||
|       if (this.props.textOutput === 1) { | ||||
|         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); | ||||
|     }); | ||||
| 
 | ||||
|     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'; | ||||
|       scriptsToInject = scriptsToInject.concat(interceptorScripts); | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|     htmlFile += hijackConsoleErrorsScript(JSON.stringify(scriptOffs)); | ||||
|     const sketchDocString = `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`; | ||||
|     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() { | ||||
|  | @ -331,8 +355,6 @@ PreviewFrame.propTypes = { | |||
|   htmlFile: PropTypes.shape({ | ||||
|     content: PropTypes.string.isRequired | ||||
|   }), | ||||
|   jsFiles: PropTypes.array.isRequired, | ||||
|   cssFiles: PropTypes.array.isRequired, | ||||
|   files: PropTypes.array.isRequired, | ||||
|   dispatchConsoleEvent: PropTypes.func, | ||||
|   children: PropTypes.element, | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React, { PropTypes } from 'react'; | ||||
| import { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| function ResetPasswordForm(props) { | ||||
|   const { fields: { email }, handleSubmit, submitting, invalid, pristine } = props; | ||||
|  | @ -10,7 +11,7 @@ function ResetPasswordForm(props) { | |||
|           aria-label="email" | ||||
|           type="text" | ||||
|           placeholder="Email used for registration" | ||||
|           {...email} | ||||
|           {...domOnlyProps(email)} | ||||
|         /> | ||||
|       </p> | ||||
|       <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 { domOnlyProps } from '../../../utils/reduxFormUtils'; | ||||
| 
 | ||||
| function SignupForm(props) { | ||||
|   const { fields: { username, email, password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props; | ||||
|  | @ -10,7 +11,7 @@ function SignupForm(props) { | |||
|           aria-label="username" | ||||
|           type="text" | ||||
|           placeholder="Username" | ||||
|           {...username} | ||||
|           {...domOnlyProps(username)} | ||||
|         /> | ||||
|         {username.touched && username.error && <span className="form-error">{username.error}</span>} | ||||
|       </p> | ||||
|  | @ -20,7 +21,7 @@ function SignupForm(props) { | |||
|           aria-label="email" | ||||
|           type="text" | ||||
|           placeholder="Email" | ||||
|           {...email} | ||||
|           {...domOnlyProps(email)} | ||||
|         /> | ||||
|         {email.touched && email.error && <span className="form-error">{email.error}</span>} | ||||
|       </p> | ||||
|  | @ -30,7 +31,7 @@ function SignupForm(props) { | |||
|           aria-label="password" | ||||
|           type="password" | ||||
|           placeholder="Password" | ||||
|           {...password} | ||||
|           {...domOnlyProps(password)} | ||||
|         /> | ||||
|         {password.touched && password.error && <span className="form-error">{password.error}</span>} | ||||
|       </p> | ||||
|  | @ -40,7 +41,7 @@ function SignupForm(props) { | |||
|           type="password" | ||||
|           placeholder="Confirm Password" | ||||
|           aria-label="confirm password" | ||||
|           {...confirmPassword} | ||||
|           {...domOnlyProps(confirmPassword)} | ||||
|         /> | ||||
|         {confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>} | ||||
|       </p> | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import * as EditorAccessibilityActions from '../actions/editorAccessibility'; | |||
| import * as PreferencesActions from '../actions/preferences'; | ||||
| import * as UserActions from '../../User/actions'; | ||||
| 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 Overlay from '../../App/components/Overlay'; | ||||
| import SketchList from '../components/SketchList'; | ||||
|  | @ -344,8 +344,6 @@ class IDEView extends React.Component { | |||
|                 </div> | ||||
|                 <PreviewFrame | ||||
|                   htmlFile={this.props.htmlFile} | ||||
|                   jsFiles={this.props.jsFiles} | ||||
|                   cssFiles={this.props.cssFiles} | ||||
|                   files={this.props.files} | ||||
|                   content={this.props.selectedFile.content} | ||||
|                   isPlaying={this.props.ide.isPlaying} | ||||
|  | @ -559,8 +557,6 @@ IDEView.propTypes = { | |||
|   }), | ||||
|   setSelectedFile: PropTypes.func.isRequired, | ||||
|   htmlFile: PropTypes.object.isRequired, | ||||
|   jsFiles: PropTypes.array.isRequired, | ||||
|   cssFiles: PropTypes.array.isRequired, | ||||
|   dispatchConsoleEvent: PropTypes.func.isRequired, | ||||
|   newFile: PropTypes.func.isRequired, | ||||
|   closeNewFileModal: PropTypes.func.isRequired, | ||||
|  | @ -617,8 +613,6 @@ function mapStateToProps(state) { | |||
|     files: state.files, | ||||
|     selectedFile: state.files.find(file => file.isSelectedFile), | ||||
|     htmlFile: getHTMLFile(state.files), | ||||
|     jsFiles: getJSFiles(state.files), | ||||
|     cssFiles: getCSSFiles(state.files), | ||||
|     ide: state.ide, | ||||
|     preferences: state.preferences, | ||||
|     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" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=4" | ||||
|     "node": ">=6" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "archiver": "^1.1.0", | ||||
|  | @ -82,6 +82,7 @@ | |||
|     "file-type": "^3.8.0", | ||||
|     "htmlhint": "^0.9.13", | ||||
|     "js-beautify": "^1.6.4", | ||||
|     "jsdom": "^9.8.3", | ||||
|     "jshint": "^2.9.2", | ||||
|     "lodash": "^4.16.4", | ||||
|     "loop-protect": "git+https://git@github.com/catarak/loop-protect.git", | ||||
|  | @ -93,11 +94,11 @@ | |||
|     "passport": "^0.3.2", | ||||
|     "passport-github": "^1.1.0", | ||||
|     "passport-local": "^1.0.0", | ||||
|     "react": "^15.0.2", | ||||
|     "react-dom": "^15.0.2", | ||||
|     "react": "^15.1.0", | ||||
|     "react-dom": "^15.1.0", | ||||
|     "react-inlinesvg": "^0.4.2", | ||||
|     "react-redux": "^4.4.5", | ||||
|     "react-router": "^2.4.1", | ||||
|     "react-router": "^2.6.0", | ||||
|     "react-split-pane": "^0.1.44", | ||||
|     "redux": "^3.5.2", | ||||
|     "redux-form": "^5.3.3", | ||||
|  |  | |||
|  | @ -2,35 +2,12 @@ import Project from '../models/project'; | |||
| import escapeStringRegexp from 'escape-string-regexp'; | ||||
| const startTag = '@fs-'; | ||||
| import { resolvePathToFile } from '../utils/filePath'; | ||||
| 
 | ||||
| function injectMediaUrls(filesToInject, allFiles, projectId) { | ||||
|   filesToInject.forEach(file => { | ||||
|     let fileStrings = file.content.match(/(['"])((\\\1|.)*?)\1/gm); | ||||
|     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, 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}`); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| import { | ||||
|   injectMediaUrls, | ||||
|   resolvePathsForElementsWithAttribute, | ||||
|   resolveScripts, | ||||
|   resolveStyles } from '../utils/previewGeneration'; | ||||
| import jsdom, { serializeDocument } from 'jsdom'; | ||||
| 
 | ||||
| export function serveProject(req, res) { | ||||
|   Project.findById(req.params.project_id) | ||||
|  | @ -38,25 +15,17 @@ export function serveProject(req, res) { | |||
|       //TODO this does not parse html
 | ||||
|       const files = project.files; | ||||
|       let htmlFile = files.find(file => file.name.match(/\.html$/i)).content; | ||||
|       const jsFiles = files.filter(file => file.name.match(/\.js$/i)); | ||||
|       const cssFiles = files.filter(file => file.name.match(/\.css$/i)); | ||||
|       const filesToInject = files.filter(file => file.name.match(/\.(js|css)$/i)); | ||||
|       injectMediaUrls(filesToInject, files, req.params.project_id); | ||||
| 
 | ||||
|       injectMediaUrls(jsFiles, files, req.params.project_id); | ||||
|       injectMediaUrls(cssFiles, files, req.params.project_id); | ||||
|       jsdom.env(htmlFile, (err, window) => { | ||||
|         const sketchDoc = window.document; | ||||
|         resolvePathsForElementsWithAttribute('src', sketchDoc, files); | ||||
|         resolvePathsForElementsWithAttribute('href', sketchDoc, files); | ||||
|         resolveScripts(sketchDoc, files); | ||||
|         resolveStyles(sketchDoc, files); | ||||
| 
 | ||||
|       jsFiles.forEach(jsFile => { | ||||
|         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); | ||||
|         res.send(serializeDocument(sketchDoc)); | ||||
|       }); | ||||
| 
 | ||||
|       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) { | ||||
|   const filePathArray = filePath.split('/'); | ||||
|   let resolvedFile; | ||||
|   let currentFile; | ||||
|   let currentFile = files.find(file => file.name === 'root'); | ||||
|   filePathArray.some((filePathSegment, index) => { | ||||
|     if (filePathSegment === "" || filePathSegment === ".") { | ||||
|     if (filePathSegment === '' || filePathSegment === '.') { | ||||
|       return false; | ||||
|     } else if (filePathSegment === "..") { | ||||
|     } else if (filePathSegment === '..') { | ||||
|       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; | ||||
| } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										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'], | ||||
|     modules: [ | ||||
|       'client', | ||||
|       'node_modules', | ||||
|       'node_modules' | ||||
|     ] | ||||
|   }, | ||||
| 	plugins: [ | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Cassie Tarakajian
						Cassie Tarakajian