├── src ├── components │ ├── Nav │ │ ├── index.js │ │ ├── Tab.js │ │ ├── Nav.scss │ │ ├── Nav.js │ │ └── __tests__ │ │ │ ├── Nav.spec.js │ │ │ └── Tab.spec.js │ ├── Result │ │ ├── index.js │ │ ├── Result.scss │ │ ├── generateHTML.js │ │ ├── __tests__ │ │ │ ├── generateHTML.spec.js │ │ │ └── Result.spec.js │ │ └── Result.js │ ├── CodeEditor │ │ ├── index.js │ │ ├── CodeEditor.js │ │ └── __tests__ │ │ │ └── CodeEditor.spec.js │ ├── global.scss │ └── Playground │ │ ├── Playground.scss │ │ ├── index.js │ │ ├── __tests__ │ │ └── Playground.spec.js │ │ └── Playground.js ├── utils │ ├── index.js │ ├── isEmpty.js │ ├── __tests__ │ │ ├── isEmpty.spec.js │ │ └── getCodeMirrorMode.spec.js │ └── getCodeMirrorMode.js ├── __tests__ │ ├── utils │ │ └── beautifyHTML.js │ └── framework.spec.js ├── redux │ ├── rootReducer.js │ ├── store.js │ └── modules │ │ ├── activeTab.js │ │ ├── compiling.js │ │ ├── __tests__ │ │ ├── compiling.spec.js │ │ ├── code.reducer.spec.js │ │ └── code.actions.spec.js │ │ └── code.js ├── index.js └── plugins │ └── parsers │ ├── sass.js │ └── __tests__ │ └── sass.spec.js ├── bin ├── karma.js ├── build.js ├── webpack.js ├── deploy.js ├── concatStyleSheet.js └── develop.js ├── .gitignore ├── .eslintignore ├── tests.webpack.js ├── .eslintrc ├── demo ├── examples │ ├── slack-logo │ │ ├── html.example │ │ └── sass.example │ └── editr.js │ │ ├── html.example │ │ └── css.example ├── develop.html ├── styles.scss ├── gh-pages.html └── app.js ├── CHANGELOG.md ├── .babelrc ├── LICENSE ├── .travis.yml ├── config └── index.js ├── README.md └── package.json /src/components/Nav/index.js: -------------------------------------------------------------------------------- 1 | export default from './Nav' 2 | -------------------------------------------------------------------------------- /src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | export default from './Result' 2 | -------------------------------------------------------------------------------- /bin/karma.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../build/karma.conf').default 2 | -------------------------------------------------------------------------------- /src/components/CodeEditor/index.js: -------------------------------------------------------------------------------- 1 | export default from './CodeEditor' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | webpack 4 | coverage 5 | 6 | node_modules 7 | *.log 8 | -------------------------------------------------------------------------------- /src/components/Result/Result.scss: -------------------------------------------------------------------------------- 1 | .iframe { 2 | width: 100%; 3 | height: 100%; 4 | border: none; 5 | position: absolute; 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | lib/** 5 | webpack/** 6 | src/**/*.spec.js 7 | src/index.html 8 | demo/** 9 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as isEmpty } from './isEmpty' 2 | export { default as getCodeMirrorMode } from './getCodeMirrorMode' 3 | -------------------------------------------------------------------------------- /tests.webpack.js: -------------------------------------------------------------------------------- 1 | // Bundle all file in src into test 2 | const componentsContext = require.context('./src', true, /\.js$/) 3 | componentsContext.keys().forEach(componentsContext) 4 | -------------------------------------------------------------------------------- /src/__tests__/utils/beautifyHTML.js: -------------------------------------------------------------------------------- 1 | import { 2 | html as beautifyHTML, 3 | default_options as defaultOptions 4 | } from 'js-beautify' 5 | 6 | export default (html) => ( 7 | beautifyHTML(html, {...defaultOptions, indent_size: 2}) 8 | ) 9 | -------------------------------------------------------------------------------- /src/components/global.scss: -------------------------------------------------------------------------------- 1 | $color-white: #fff; 2 | 3 | /* 4 | * App colors 5 | */ 6 | $color-primary: #555; 7 | $color-accent: red; 8 | $color-text: #555; 9 | $color-background: #f0f1f4; 10 | $color-divider: rgba($color-primary, .4); 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "env" : { 8 | "browser" : true 9 | }, 10 | "globals" : { 11 | "__DEV__" : false, 12 | "__PROD__" : false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/examples/slack-logo/html.example: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | -------------------------------------------------------------------------------- /src/redux/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import activeTab from './modules/activeTab' 3 | import compiling from './modules/compiling' 4 | import code from './modules/code' 5 | 6 | export default combineReducers({ 7 | activeTab, 8 | code, 9 | compiling 10 | }) 11 | -------------------------------------------------------------------------------- /src/components/Playground/Playground.scss: -------------------------------------------------------------------------------- 1 | @import "../global"; 2 | 3 | .main { 4 | height: 100%; 5 | position: relative; 6 | overflow: hidden; 7 | border: 1px solid $color-divider; 8 | font-family: sans-serif; 9 | } 10 | 11 | :global .ReactCodeMirror { 12 | height: 100%; 13 | position: relative; 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - Added Plugin interface [pull/15](https://github.com/thangngoc89/react-code-playground/pull/15) 2 | - Added Karma for testing [pull/16](https://github.com/thangngoc89/react-code-playground/pull/16) 3 | - Added use tab name from ``parsers`` 4 | [pull/13](https://github.com/thangngoc89/react-code-playground/pull/13) 5 | 6 | # 0.1.2 - 2016-28-01 7 | 8 | ✨ Initial release 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-1"], 3 | "env": { 4 | "development": { 5 | "presets": [ 6 | "react-hmre" 7 | ] 8 | }, 9 | "lib": { 10 | "plugins": [ 11 | [ 12 | "babel-plugin-webpack-loaders", 13 | { 14 | "config": "./build/webpack.config.lib.js", 15 | "verbose": false, 16 | } 17 | ] 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import rootReducer from './rootReducer' 4 | 5 | const store = (initialState = {}) => { 6 | const finalCreateStore = compose( 7 | applyMiddleware(thunk), 8 | window.devToolsExtension ? window.devToolsExtension() : f => f 9 | )(createStore) 10 | 11 | return finalCreateStore(rootReducer, initialState) 12 | } 13 | 14 | export default store() 15 | -------------------------------------------------------------------------------- /demo/develop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React code playground demo 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/Result/generateHTML.js: -------------------------------------------------------------------------------- 1 | export default function ({html, css, javascript}) { 2 | let result = ` 3 | 4 | 5 | 6 | ` 7 | 8 | if (html) { 9 | result = result.replace('', `${html}`) 10 | } 11 | if (css) { 12 | result = result.replace('', ``) 13 | } 14 | if (javascript) { 15 | result = result.replace('', ``) 16 | } 17 | return result 18 | } 19 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | import webpack from './webpack' 2 | import wpConfig from '../build/webpack.config' 3 | import fs from 'fs-extra' 4 | import config from '../config' 5 | 6 | const debug = require('debug')('playground:bin:build') 7 | 8 | const paths = config.utils_paths 9 | 10 | debug('Start build') 11 | fs.emptyDirSync(paths.dist()) 12 | 13 | webpack(wpConfig, () => { 14 | debug('Webpack build success') 15 | debug('Copy static files') 16 | fs.copySync(paths.demo('vendor'), paths.dist('vendor')) 17 | debug('Done') 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/Nav/Tab.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './Nav.scss' 3 | const cx = require('classnames/bind').bind(styles) 4 | 5 | const Tab = ({ 6 | type, 7 | displayName, 8 | activeTab, 9 | onTabClick 10 | }) => { 11 | const className = cx('tab', { 12 | tabActive: activeTab === type 13 | }) 14 | 15 | return ( 16 |
  • onTabClick(type)} 19 | > 20 | {displayName} 21 |
  • 22 | ) 23 | } 24 | 25 | export default Tab 26 | -------------------------------------------------------------------------------- /src/utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * isEmpty function 3 | * @param {any} t [variable for checking] 4 | * @return {boolean} [is variable empty or not] 5 | */ 6 | export default function (t) { 7 | if (t === null) { 8 | return true 9 | } 10 | 11 | if (Array.isArray(t)) { 12 | return (t.length === 0) 13 | } 14 | 15 | if (typeof t === 'object') { 16 | let k = Object.keys(t) 17 | if (k !== undefined) { 18 | return (Object.keys(t).length === 0) 19 | } 20 | } 21 | 22 | return (t !== undefined) || 23 | (t !== '') 24 | } 25 | -------------------------------------------------------------------------------- /bin/webpack.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | const debug = require('debug')('playground:bin:webpack') 3 | 4 | export default (webpackConfig, cb) => { 5 | webpack(webpackConfig, (err, stats) => { 6 | if (err) { 7 | throw err 8 | } 9 | 10 | if (stats.hasErrors()) { 11 | stats.compilation.errors.forEach( 12 | item => { 13 | item.stack.split('\n').forEach(line => debug(line)) 14 | } 15 | ) 16 | 17 | throw new Error('webpack build failed with errors') 18 | } 19 | cb(stats) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /bin/deploy.js: -------------------------------------------------------------------------------- 1 | const ghpages = require('gh-pages') 2 | const debug = require('debug')('playground:bin:deploy') 3 | const path = require('path') 4 | 5 | debug('Start deployment process.') 6 | 7 | debug('Clean up temp folder') 8 | ghpages.clean() 9 | 10 | debug('Deploy with github token') 11 | ghpages.publish(path.join(__dirname, '../dist'), { 12 | repo: 'https://' + process.env.GH_TOKEN + '@github.com/thangngoc89/react-code-playground.git', 13 | silent: true 14 | }, callback) 15 | 16 | var callback = function (err) { 17 | if (err) { 18 | debug(err) 19 | return 20 | } 21 | debug('Finish') 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/__tests__/isEmpty.spec.js: -------------------------------------------------------------------------------- 1 | import { isEmpty } from '../' 2 | 3 | describe('utils', () => { 4 | describe('isEmpty function lists these at true', () => { 5 | it('empty object', () => { 6 | expect(isEmpty({})).to.be.true 7 | }) 8 | it('empty array', () => { 9 | expect(isEmpty([])).to.be.true 10 | }) 11 | it('empty string', () => { 12 | expect(isEmpty('')).to.be.true 13 | }) 14 | it('undefined', () => { 15 | expect(isEmpty(undefined)).to.be.true 16 | }) 17 | it('null', () => { 18 | expect(isEmpty(null)).to.be.true 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/__tests__/framework.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | 3 | describe('(Framework) Karma Plugins', function () { 4 | it('Should expose "expect" globally.', function () { 5 | assert.ok(expect) 6 | }) 7 | 8 | it('Should expose "should" globally.', function () { 9 | assert.ok(should) 10 | }) 11 | 12 | it('Should have chai-as-promised helpers.', function () { 13 | const pass = new Promise(res => res('test')) 14 | const fail = new Promise((res, rej) => rej()) 15 | 16 | return Promise.all([ 17 | expect(pass).to.be.fulfilled, 18 | expect(fail).to.not.be.fulfilled 19 | ]) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Playground from './components/Playground' 3 | import store from './redux/store' 4 | import { codeRefresh } from './redux/modules/code' 5 | 6 | class CodePlayground extends Component { 7 | /** 8 | * Set isSynced to false when receive new props 9 | */ 10 | componentWillReceiveProps (nextProps) { 11 | if (nextProps !== this.props) { 12 | store.dispatch(codeRefresh()) 13 | } 14 | } 15 | 16 | render () { 17 | return ( 18 | 22 | ) 23 | } 24 | } 25 | 26 | export default CodePlayground 27 | -------------------------------------------------------------------------------- /src/redux/modules/activeTab.js: -------------------------------------------------------------------------------- 1 | import { createAction, handleActions } from 'redux-actions' 2 | // ------------------------------------ 3 | // Constants 4 | // ------------------------------------ 5 | export const TAB_SET = 'playground/tab/SET' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export const tabSet = createAction(TAB_SET) 11 | 12 | export const actions = { 13 | tabSet 14 | } 15 | 16 | // ------------------------------------ 17 | // Reducer 18 | // ------------------------------------ 19 | const initialState = 'result' 20 | 21 | export default handleActions({ 22 | [TAB_SET]: (state, { payload }) => payload 23 | }, initialState) 24 | -------------------------------------------------------------------------------- /src/components/Result/__tests__/generateHTML.spec.js: -------------------------------------------------------------------------------- 1 | import generateHTML from '../generateHTML.js' 2 | import beautifyHTML from '../../../__tests__/utils/beautifyHTML' 3 | 4 | describe('(component) Result', () => { 5 | describe('generateHTML', () => { 6 | const html = '

    foo

    ' 7 | const javascript = `console.log('foo')` 8 | const css = `.foo {border: 0}` 9 | 10 | it('generate expected code', () => { 11 | const result = generateHTML({javascript, css, html}) 12 | const expectedResult = 13 | ` 14 | 15 | 16 | 17 | 18 | ${html} 19 | 20 | 21 | 22 | ` 23 | expect(beautifyHTML(result)).to.equal(beautifyHTML(expectedResult)) 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/plugins/parsers/sass.js: -------------------------------------------------------------------------------- 1 | const parser = { 2 | type: 'parser', 3 | name: 'SASS', 4 | codeType: 'css', 5 | codeMirrorMode: 'sass', 6 | parse: function (code, cb) { 7 | if ( 8 | typeof window !== 'undefined' && 9 | typeof window.Sass !== 'object' 10 | ) { 11 | throw new Error( 12 | 'Sass.js is not available in global scope ' + 13 | 'Please download and embed it into your website' + 14 | 'from https://github.com/medialize/sass.js' 15 | ) 16 | } 17 | window.Sass.compile(code, result => { 18 | if (result.status === 0) { 19 | cb(result.text, 'css') 20 | } else { 21 | // TODO: Handle me 22 | console.error(result) 23 | } 24 | }) 25 | } 26 | } 27 | 28 | export default parser 29 | -------------------------------------------------------------------------------- /src/components/Nav/Nav.scss: -------------------------------------------------------------------------------- 1 | @import "../global.scss"; 2 | 3 | .nav { 4 | display: block; 5 | overflow: hidden; 6 | height: 2.6rem; 7 | margin: 0; 8 | padding: 0; 9 | background: $color-background; 10 | border-style: solid; 11 | border-color: rgba($color-primary, .4); 12 | border-width: 0 0 1px 0; 13 | list-style-type: none; 14 | } 15 | 16 | .tab { 17 | float: left; 18 | padding: .8rem; 19 | background: $color-background; 20 | color: $color-text; 21 | cursor: pointer; 22 | 23 | &:hover, &:focus { 24 | background: rgba($color-text, .7); 25 | color: $color-background; 26 | } 27 | 28 | &:active { 29 | background: rgba($color-primary, 0.12); 30 | } 31 | } 32 | 33 | .tabActive { 34 | background: $color-text; 35 | color: $color-background; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Result/__tests__/Result.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'teaspoon' 3 | import Result from '../Result' 4 | import styles from '../Result.scss' 5 | 6 | const render = props => $().render() 7 | const shallowRender = props => $().shallowRender() 8 | 9 | describe('(component) Nav', () => { 10 | describe('Result', () => { 11 | let $element, _props 12 | 13 | beforeEach(() => { 14 | _props = { 15 | code: { 16 | html: 'Foo', 17 | css: '.foo {border: 0}', 18 | javascript: 'var a = 1' 19 | } 20 | } 21 | }) 22 | 23 | it('render an