diff --git a/webmaker/src/CodeMirror.js b/webmaker/src/CodeMirror.js new file mode 100644 index 0000000..df244d1 --- /dev/null +++ b/webmaker/src/CodeMirror.js @@ -0,0 +1,60 @@ +// Most of the code from this file comes from: +// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js +import CodeMirror from 'codemirror'; + +// Make CodeMirror available globally so the modes' can register themselves. +window.CodeMirror = CodeMirror + +if (!CodeMirror.modeURL) CodeMirror.modeURL = 'lib/codemirror/mode/%N/%N.js'; + +var loading = {} + +function splitCallback(cont, n) { + var countDown = n + return function () { + if (--countDown === 0) cont() + } +} + +function ensureDeps(mode, cont) { + var deps = CodeMirror.modes[mode].dependencies + if (!deps) return cont() + var missing = [] + for (var i = 0; i < deps.length; ++i) { + if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i]) + } + if (!missing.length) return cont() + var split = splitCallback(cont, missing.length) + for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split) +} + +CodeMirror.requireMode = function (mode, cont) { + if (typeof mode !== 'string') mode = mode.name + if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont) + if (loading.hasOwnProperty(mode)) return loading[mode].push(cont) + + var file = CodeMirror.modeURL.replace(/%N/g, mode) + + var script = document.createElement('script') + script.src = file + var others = document.getElementsByTagName('script')[0] + var list = loading[mode] = [cont] + + CodeMirror.on(script, 'load', function () { + ensureDeps(mode, function () { + for (var i = 0; i < list.length; ++i) list[i]() + }) + }) + + others.parentNode.insertBefore(script, others) +} + +CodeMirror.autoLoadMode = function (instance, mode) { + if (CodeMirror.modes.hasOwnProperty(mode)) return + + CodeMirror.requireMode(mode, function () { + instance.setOption('mode', instance.getOption('mode')) + }) +} + +export default CodeMirror diff --git a/webmaker/src/analytics.js b/webmaker/src/analytics.js index 1616a3b..889eb76 100644 --- a/webmaker/src/analytics.js +++ b/webmaker/src/analytics.js @@ -16,7 +16,7 @@ export function trackEvent(category, action, label, value) { }; // if online, load after sometime -if (navigator.onLine && !window.DEBUG) { +if (false && navigator.onLine && !window.DEBUG) { /* eslint-disable */ // prettier-ignore diff --git a/webmaker/src/components/ContentWrap.jsx b/webmaker/src/components/ContentWrap.jsx index be1cfb2..fc15dd1 100644 --- a/webmaker/src/components/ContentWrap.jsx +++ b/webmaker/src/components/ContentWrap.jsx @@ -1,10 +1,12 @@ import { h, Component } from 'preact'; import UserCodeMirror from './UserCodeMirror.jsx'; import { computeHtml, computeCss, computeJs } from '../computes'; -import { HtmlModes, CssModes, JsModes } from '../codeModes'; -import { log, writeFile } from '../utils'; +import { modes, HtmlModes, CssModes, JsModes } from '../codeModes'; +import { log, writeFile, loadJS } from '../utils'; import { SplitPane } from './SplitPane.jsx'; import { trackEvent } from '../analytics'; +import CodeMirror from '../CodeMirror'; +import { deferred } from '../deferred'; const BASE_PATH = chrome.extension || window.DEBUG ? '/' : '/app'; const minCodeWrapSize = 33; @@ -217,7 +219,9 @@ export default class ContentWrap extends Component { currentCode.html === this.codeInPreview.html && currentCode.js === this.codeInPreview.js ) { - computeCss(currentCode.css, this.cssMode).then(function(css) { + computeCss(currentCode.css, this.props.currentItem.cssMode).then(function( + css + ) { if (targetFrame.contentDocument.querySelector('#webmakerstyle')) { targetFrame.contentDocument.querySelector( '#webmakerstyle' @@ -225,9 +229,15 @@ export default class ContentWrap extends Component { } }); } else { - var htmlPromise = computeHtml(currentCode.html, this.htmlMode); - var cssPromise = computeCss(currentCode.css, this.cssMode); - var jsPromise = computeJs(currentCode.js, this.jsMode); + var htmlPromise = computeHtml( + currentCode.html, + this.props.currentItem.htmlMode + ); + var cssPromise = computeCss( + currentCode.css, + this.props.currentItem.cssMode + ); + var jsPromise = computeJs(currentCode.js, this.props.currentItem.jsMode); Promise.all([htmlPromise, cssPromise, jsPromise]).then(result => { this.createPreviewFile(result[0], result[1], result[2]); }); @@ -237,10 +247,15 @@ export default class ContentWrap extends Component { this.codeInPreview.css = currentCode.css; this.codeInPreview.js = currentCode.js; } + isValidItem(item) { + return !!item.title; + } componentDidUpdate() { - this.refreshEditor(); - // this.setPreviewContent(true); - // console.log('componentdidupdate', this.props.currentItem); + log('🚀', 'didupdate', this.props.currentItem); + + // if (this.isValidItem(this.props.currentItem)) { + // this.refreshEditor(); + // } } componentDidMount() { this.props.onRef(this); @@ -257,7 +272,13 @@ export default class ContentWrap extends Component { this.cm.css.refresh(); this.cm.js.refresh(); - this.setPreviewContent(true); + // Set preview only when all modes are updated so that preview doesn't generate on partially + // correct modes and also doesn't happen 3 times. + Promise.all([ + this.updateHtmlMode(this.props.currentItem.htmlMode), + this.updateCssMode(this.props.currentItem.cssMode), + this.updateJsMode(this.props.currentItem.jsMode) + ]).then(() => this.setPreviewContent(true)); } applyCodemirrorSettings(prefs) { if (!this.cm) { @@ -404,7 +425,116 @@ export default class ContentWrap extends Component { this.updateCodeWrapCollapseStates(); document.body.classList.remove('is-dragging'); } + /** + * Loaded the code comiler based on the mode selected + */ + handleModeRequirements(mode) { + const baseTranspilerPath = 'lib/transpilers'; + // Exit if already loaded + var d = deferred(); + if (modes[mode].hasLoaded) { + d.resolve(); + return d.promise; + } + function setLoadedFlag() { + modes[mode].hasLoaded = true; + d.resolve(); + } + + if (mode === HtmlModes.JADE) { + loadJS(`${baseTranspilerPath}/jade.js`).then(setLoadedFlag); + } else if (mode === HtmlModes.MARKDOWN) { + loadJS(`${baseTranspilerPath}/marked.js`).then(setLoadedFlag); + } else if (mode === CssModes.LESS) { + loadJS(`${baseTranspilerPath}/less.min.js`).then(setLoadedFlag); + } else if (mode === CssModes.SCSS || mode === CssModes.SASS) { + loadJS(`${baseTranspilerPath}/sass.js`).then(function() { + window.sass = new Sass(`${baseTranspilerPath}/sass.worker.js`); + setLoadedFlag(); + }); + } else if (mode === CssModes.STYLUS) { + loadJS(`${baseTranspilerPath}/stylus.min.js`).then(setLoadedFlag); + } else if (mode === CssModes.ACSS) { + loadJS(`${baseTranspilerPath}/atomizer.browser.js`).then(setLoadedFlag); + } else if (mode === JsModes.COFFEESCRIPT) { + loadJS(`${baseTranspilerPath}/coffee-script.js`).then(setLoadedFlag); + } else if (mode === JsModes.ES6) { + loadJS(`${baseTranspilerPath}/babel.min.js`).then(setLoadedFlag); + } else if (mode === JsModes.TS) { + loadJS(`${baseTranspilerPath}/typescript.js`).then(setLoadedFlag); + } else { + d.resolve(); + } + + return d.promise; + } + + updateHtmlMode(value) { + this.props.onCodeModeChange('html', value); + this.props.currentItem.htmlMode = value; + const htmlModeLabel = $('#js-html-mode-label'); + + htmlModeLabel.textContent = modes[value].label; + // FIXME - use a better selector for the mode selectbox + htmlModeLabel.parentElement.querySelector('select').value = value; + this.cm.html.setOption('mode', modes[value].cmMode); + CodeMirror.autoLoadMode( + this.cm.html, + modes[value].cmPath || modes[value].cmMode + ); + return this.handleModeRequirements(value); + } + updateCssMode(value) { + this.props.onCodeModeChange('css', value); + this.props.currentItem.cssMode = value; + const cssModeLabel = $('#js-css-mode-label'); + cssModeLabel.textContent = modes[value].label; + // FIXME - use a better selector for the mode selectbox + cssModeLabel.parentElement.querySelector('select').value = value; + this.cm.css.setOption('mode', modes[value].cmMode); + this.cm.css.setOption('readOnly', modes[value].cmDisable); + // cssSettingsBtn.classList[modes[value].hasSettings ? 'remove' : 'add']( + // 'hide' + // ); + CodeMirror.autoLoadMode( + this.cm.css, + modes[value].cmPath || modes[value].cmMode + ); + return this.handleModeRequirements(value); + } + updateJsMode(value) { + this.props.onCodeModeChange('js', value); + this.props.currentItem.jsMode = value; + const jsModeLabel = $('#js-js-mode-label'); + + jsModeLabel.textContent = modes[value].label; + // FIXME - use a better selector for the mode selectbox + jsModeLabel.parentElement.querySelector('select').value = value; + this.cm.js.setOption('mode', modes[value].cmMode); + CodeMirror.autoLoadMode( + this.cm.js, + modes[value].cmPath || modes[value].cmMode + ); + return this.handleModeRequirements(value); + } + codeModeChangeHandler(e) { + var mode = e.target.value; + var type = e.target.dataset.type; + var currentMode = this.props.currentItem[ + type === 'html' ? 'htmlMode' : type === 'css' ? 'cssMode' : 'jsMode' + ]; + if (currentMode !== mode) { + if (type === 'html') { + this.updateHtmlMode(mode).then(() => this.setPreviewContent(true)); + } else if (type === 'js') { + this.updateJsMode(mode).then(() => this.setPreviewContent(true)); + } else if (type === 'css') { + this.updateCssMode(mode).then(() => this.setPreviewContent(true)); + } + trackEvent('ui', 'updateCodeMode', mode); + } + } render() { return ( @@ -494,7 +624,7 @@ export default class ContentWrap extends Component { +