removed content from master, moved to gh-pages

This commit is contained in:
Dany Shaanan 2014-11-17 14:58:15 +02:00
parent 91ab1b156a
commit 5f3abe3a4d
34 changed files with 1 additions and 60216 deletions

155
.eslintrc
View File

@ -1,155 +0,0 @@
{
"rules": {
"no-array-constructor": 2,
"no-catch-shadow": 2,
"no-comma-dangle": 2,
"no-cond-assign": 2,
"no-constant-condition": 2,
"no-control-regex": 2,
"no-div-regex": 1,
"no-else-return": 2,
"no-empty-class": 2,
"no-empty-label": 2,
"no-eq-null": 2,
"no-extend-native": 2,
"no-extra-boolean-cast": 2,
"no-extra-strict": 2,
"global-strict": [2, "always"],
"no-inner-declarations": [2, "functions"],
"no-iterator": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-mixed-requires": [0, false],
"no-negated-in-lhs": 2,
"no-nested-ternary": 2,
"no-new-require": 0,
"no-octal-escape": 2,
"no-path-concat": 1,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 1,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow": 1,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-space-before-semi": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-undef": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-wrap-func": 2,
"yoda": 2,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"consistent-return": 2,
"consistent-this": [0, "self", "TODO: add this options, remove this value in array"],
"curly": [2, "all"],
"default-case": 0,
"func-names": 0,
"func-style": [0, "declaration"],
"max-depth": [0, 4],
"max-len": [0, 80, 4],
"max-nested-callbacks": [0, 2],
"handle-callback-err": 0,
"one-var": 0,
"sort-vars": 0,
"space-after-keywords": [2, "always"],
"space-in-brackets": [0, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-word-ops": 2,
"strict": 2,
"valid-typeof": 2,
"wrap-regex": 0,
"no-alert": 2,
"no-caller": 2,
"no-bitwise": 0,
"no-console": 0,
"no-underscore-dangle": 0,
"no-debugger": 2,
"no-dupe-keys": 2,
"no-empty": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-floating-decimal": 1,
"no-func-assign": 2,
"no-invalid-regexp": 2,
"no-implied-eval": 2,
"no-with": 2,
"no-fallthrough": 2,
"no-unreachable": 2,
"no-undef-init": 2,
"no-octal": 2,
"no-obj-calls": 2,
"no-new-wrappers": 2,
"no-new": 2,
"no-new-func": 2,
"no-native-reassign": 2,
"no-plusplus": 0,
"no-delete-var": 2,
"no-return-assign": 2,
"no-new-object": 2,
"no-label-var": 2,
"no-ternary": 0,
"no-self-compare": 2,
"no-use-before-define": 0,
"valid-jsdoc": 0,
"eol-last": 0,
"no-trailing-spaces": 0,
"no-extra-bind": 2,
"camelcase": 0,
"dot-notation": 2,
"eqeqeq": 2,
"new-parens": 2,
"guard-for-in": 2,
"radix": 0,
"new-cap": 2,
"semi": 2,
"use-isnan": 2,
"quotes": [1, "single", "avoid-escape"],
"max-params": [0, 3],
"max-statements": [0, 10],
"complexity": [0, 11],
"wrap-iife": 2,
"no-multi-str": 2,
"quote-props": [1, "as-needed"],
"no-multi-spaces": 1,
"key-spacing": [1, { "beforeColon": false, "afterColon": true }],
"comma-spacing": 1
},
"env": {
"browser": false,
"node": true,
"amd": true
},
"globals": {
"requirejs": true,
"xdescribe": false,
"describe": false,
"it": false,
"xit": false,
"beforeEach": false,
"afterEach": false,
"jasmine": false,
"expect": false,
"waitsFor": false,
"waits": false,
"runs": false,
"any": false,
"spyOn": false,
"createSpy": false
}
}

View File

@ -1,21 +0,0 @@
### Operating systems ###
.DS_Store
.AppleDouble
.LSOverride
._*
.Spotlight-V100
.Trashes
### Regular dev ###
node_modules
npm-debug.log
.idea
*.iml
### bower ###
/bower_components/*
### tests ###
/test

22
LICENSE
View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Wix.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

268
README.md
View File

@ -1,269 +1,3 @@
# react-templates
Light weight templates for react. Reasons that we love it:
* No runtime libraries. No magic. Just simple pre-compilation to a clear code
* Super easy to write panels. By panels we mean components that have a lot of HTML code and non-reusable logic
* Very good separation of presentation and logic. Almost no HTML within the component file
* Declerative coding for presentation. HTML that you write and inspect look similar
* Easy syntax. Similar to HTML. All IDEs recognize this format
## How does it work
React templates compiles a *.rt file (react template file - extended HTML format) into a javascript file. Currently, this file supports requirejs format, that will return a function. This function, when applied, will return a virtual react DOM (based on React.DOM elements and user custom components). A common use case would be that a React component would require a JS file generated by a template, and then call `func.apply(this)`, causing the template to have the component as its context.
###### Basic concepts for react templates
* Any valid HTML is a template (and comments)
* {} to identify JS expression
* rt-if
* rt-repeat
* rt-scope
* rt-class
* style
* event handlers
* doctype rt, require dependencies, and calling other components
###### Why not use JSX?
Some love jsx, some don't. We don't. Well, not that we don't like it, but it seems to fit only components with very little html inside, which could be done by creating elements in code. Also, we like to separate code and html. It just feels right.
## Usage
**src/cli.js < filename.rt >**
*(you might need to provide execution permission to the file)*
Note that in most cases, this package will be wrapped in a grunt task, so the cli will not be used directly
TODO: pointer to the grunt repository
# Template directives and syntax
## Any valid HTML is a template
Writing any html is a valid template. This does not apply to event handlers ("on" methods). See the section about event handlers
## {} to identify JS expression
In html attributes and text, you can replace context by a javascript expression. You do this by wrapping it in {}. If this is inside an attribute, it still needs to be wrapped by quotes. In text, you can just use it.
###### Sample:
```html
<a href="{this.state.linkRef}">{this.state.linkText}</a>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.a({ 'href': this.state.linkRef }, this.state.linkText);
};
});
```
*Note: within the special **"rt-"** directives, simple strings don't make sense, as all those directives apply on some execution context. Therefore, in these directives, there won't be a need to add the {} to identify execution context*
## rt-if
This gives you the ability to add conditions to a sub-tree of html. If the condition is evaluated to true, the subree will be returned, otherwise, it will not be calculated. It is implemented by a trinary expression
###### Sample:
```html
<div rt-if="this.state.resultCode === 200">Success!</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return this.state.resultCode === 200 ? React.DOM.div({}, 'Success!') : null;
};
});
```
## rt-repeat
Repeats a node with its subtree for each item in an array. This is implemented by creating a method that is passed to a map call as a callback. It creates a real context for the iterated variable. The syntax is `rt-repeat="itemVar in arrayExpr"`. Within the scope of the element, `itemVar` will be available in javascript context, and also an `itemVarIndex` will be created to represent the index of the item. If the definition is `myNum in this.getMyNumbers()`, than there will be 2 variables in the scope: `myNum` and `myNumIndex`. This naming is used to allow nesting of repeat expression with access to all levels.
###### Sample:
```html
<div rt-repeat="myNum in this.getMyNumbers()">{myNumIndex}. myNum</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function repeatMyNum1(myNum, myNumIndex) {
return React.DOM.div({}, myNumIndex + '. myNum');
}
return function () {
return _.map(this.getMyNumbers(), repeatMyNum1.bind(this));
};
});
```
## rt-scope
This directive creates a new javascript scope. It actually creates a new method for this scope, and calls it with its current context. The syntax is `rt-scope="expr1 as var1; expr2 as var2`. This gives a convenience method to shorten stuff up in a scope and make the code more readable. It also helps to execute an expression only once in a scope instead of every chunk that needs it.
###### Sample:
```html
<div rt-repeat="rpt in array">
<div rt-scope="')' as separator; rpt.val as val">{rptIndex}{separator} {val}</div>
<div>'rpt' exists here, but not 'separator' and 'val'</div>
</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function scopeSeparatorVal1(rpt, rptIndex, separator, val) {
return React.DOM.div({}, rptIndex + separator + ' ' + val);
}
function repeatRpt2(rpt, rptIndex) {
return React.DOM.div({}, scopeSeparatorVal1.apply(this, [
rpt,
rptIndex,
')',
rpt.val
]), React.DOM.div({}, '\'rpt\' exists here, but not \'separator\' and \'val\''));
}
return function () {
return _.map(array, repeatRpt2.bind(this));
};
});
```
## rt-class
In order to reduce the boiler-plate code when programatically setting class names, you can use the rt-class directive. It expectes to get a JSON object with keys as class names, and a value of true or false as the value. If the value is true, the class name will be included. Please note the following:
1. In react templates, you can use the "class" attribute the same as you'd do in html. If you like, you can even have execution context within
2. You cannot use class and rt-class on the same html element
###### Sample:
```html
<div rt-scope="{blue: true, selected: this.isSelected()} as classes">
These are logically equivalent
<div rt-class="classes">Reference</div>
<div rt-class="{blue: true, selected: this.isSelected()}">Inline</div>
<div class="blue{this.isSelected() ? ' selected' : ''}">Using the class attribute</div>
</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function scopeClasses1(classes) {
return React.DOM.div({}, 'These are logically equivalent', React.DOM.div({ 'className': React.addons.classSet(classes) }, 'Reference'), React.DOM.div({
'className': React.addons.classSet({
blue: true,
selected: this.isSelected()
})
}, 'Inline'), React.DOM.div({ 'className': 'blue' + this.isSelected() ? ' selected' : '' }, 'Using the class attribute'));
}
return function () {
return scopeClasses1.apply(this, [{
blue: true,
selected: this.isSelected()
}]);
};
});
```
## style
In order to make it closer to html, we allow the settings of inline styles. In addition, this will take care of changing the styles from hyphen-style to camelCase style. If you'd like, you can still return an object from evaluation context. Please note that if you do it inline, you'll need to open single curly braces for the js context, and another for the object. Also, you'll need to use camelCase if using it that way
###### Sample:
```html
<div>
These are really equivalent
<div style="color:white; line-height:{this.state.lineHeight}px">Inline</div>
<div style="{{'color': 'white', 'lineHeight': this.state.lineHeight + 'px'}}">Inline</div>
</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.div({}, 'These are really equivalent', React.DOM.div({
'style': {
color: 'white',
lineHeight: this.state.lineHeight + 'px'
}
}, 'Inline'), React.DOM.div({
'style': {
'color': 'white',
'lineHeight': this.state.lineHeight + 'px'
}
}, 'Inline'));
};
});
```
## event handlers
React event handlers accept function pointers. Therefore, when using event, you can just open an execution context and provide a pointer to a method. This would look like `onClick="{this.myClickHandler}"`. However, sometimes there's very little to do on click, or we just want to call a method with bound parameters. In that case, you can use a lambda notation, which will result in creating a react template creating a method for the handler. It does not have a performance impact, as the method is created once, and just bound to the context instead of created again. The lambda notation will look like this `onClick="(evt) => console.log(evt)"`. In this example, **evt** was the name you choose for the first argument that will be passed into your inline method. With browser events, this will most likely be the react synthetic event. However, if you expect a property that starts with **on**Something, then react-templates will treat it as an event handler. So if you have an event handler called **onBoxSelected** that will trigger an event with a row and column params, you can write `onBoxSelected="(row, col)=>this.doSomething(row,col)"`. You can use a no-param version as well `onClick="()=>console.log('just wanted to know it clicked')"`
###### Sample:
```html
<div rt-repeat="item in items">
<div onClick="()=>this.itemSelected(item)" onMouseDown="{this.mouseDownHandler}">
</div>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function onClick1(item, itemIndex) {
this.itemSelected(item);
}
function repeatItem2(item, itemIndex) {
return React.DOM.div({}, React.DOM.div({
'onClick': onClick1.bind(this, item, itemIndex),
'onMouseDown': this.mouseDownHandler
}));
}
return function () {
return _.map(items, repeatItem2.bind(this));
};
});
```
## doctype rt, require dependencies, and calling other components
In many cases, you'd like to use either library code, or other components within your template. In order to do so, you can define a doctype and indicate dependencies. You do so by `<!doctype rt depVarName="depVarPath">`. After that, you will have **depVarName** in your scope. You can import react components and use them afterwords in the template as tag names. For example `<MySlider prop1="val1" onMyChange="{this.onSliderMoved}">`. This will also support nesting `<MyContainer><div>child</div><div>another</div></MyContainer>`. You will then be able to find the children in **this.props.children**.
###### Sample:
```html
<!doctype rt MyComp="comps/myComp" utils="utils/utils">
<MyComp rt-repeat="item in items">
<div>{utils.toLower(item.name)}</div>
</MyComp>
```
###### Compiled:
```javascript
define([
'react',
'lodash',
'comps/myComp',
'utils/utils'
], function (React, _, MyComp, utils) {
'use strict';
function repeatItem1(item, itemIndex) {
return MyComp({}, React.DOM.div({}, utils.toLower(item.name)));
}
return function () {
return _.map(items, repeatItem1.bind(this));
};
});
```
Moved to gh-pages branch.

View File

@ -1,16 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React templates - Image Search Sample</title>
<link rel="stylesheet" href="playground/playground.css"/>
<link rel="stylesheet" href="playground/codemirror.css">
<link rel="stylesheet" href="playground/solarized.css">
</head>
<body>
<div id="playground">
</div>
<script src="playground/main.browser.js"></script>
</body>
</html>

View File

@ -1,33 +0,0 @@
{
"name": "react-templates",
"version": "0.0.1",
"description": "Light weight templates for react -> write html get valid react code",
"main": "./src/reactTemplates.js",
"bin": {
"rt": "./src/cli.js"
},
"scripts": {
"test": "node test/src/test.js"
},
"repository": {
"type": "git",
"url": "git://github.com/wix/react-templates.git"
},
"author": "Avi Marcus",
"license": "MIT",
"bugs": {
"url": "https://github.com/wix/react-templates/issues"
},
"homepage": "https://github.com/wix/react-templates",
"dependencies": {
"chalk": "^0.5.1",
"cheerio": "^0.18.0",
"escodegen": "^1.4.1",
"esprima": "^1.2.2",
"lodash": "^2.4.1",
"react": "^0.12.0"
},
"devDependencies": {
"tape": "^3.0.2"
}
}

View File

@ -1,309 +0,0 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
}
.CodeMirror-scroll {
/* Set scrolling behaviour here */
overflow: auto;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
@-moz-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@-webkit-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
/* Can style cursor different in overwrite (non-insert) mode */
div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
line-height: 1;
position: relative;
overflow: hidden;
background: white;
color: black;
}
.CodeMirror-scroll {
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
padding-bottom: 30px;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
-moz-box-sizing: content-box;
box-sizing: content-box;
padding-bottom: 30px;
margin-bottom: -32px;
display: inline-block;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor {
position: absolute;
border-right: none;
width: 0;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because one or more lines are too long

View File

@ -1,103 +0,0 @@
var reactTemplates = require('../src/reactTemplates');
var playgroundTemplate = require('./playground.rt.js');
var htmlMode = require('codemirror/mode/htmlmixed/htmlmixed');
var javascriptMode = require('codemirror/mode/javascript/javascript');
var xmlMode = require('codemirror/mode/xml/xml');
var cssMode = require('codemirror/mode/css/css');
var vbScriptMode = require('codemirror/mode/vbscript/vbscript');
var React = require('react/addons');
var _ = require('lodash');
var html = "<div>hello</div>";
var res = reactTemplates.convertTemplateToReact(html.trim());
console.log(res);
function emptyFunc() {
return null;
}
function generateTemplateFunction(html) {
try {
var code = reactTemplates.convertTemplateToReact(html.trim().replace(/\r/g,""));
var defineMap = {"react":React,"lodash":_};
var define = function (requirementsNames,content) {
var requirements = _.map(requirementsNames,function (reqName) {
return defineMap[reqName];
});
return content.apply(this,requirements);
};
var res = eval(code);
return res;
} catch (e) {
return emptyFunc
}
}
function generateRenderFunc(renderFunc) {
return function() {
var res = null;
try {
res = renderFunc.apply(this)
} catch (e) {
res = React.DOM.div.apply(this,[{style:{color:"red"}},"Exception:"+e.message]);
}
return React.DOM.div.apply(this, _.flatten([
{key:"result"},
res
]));
}
}
var z = {getInitialState: function() {return {name:"reactTemplates"}}};
var templateHTML = "<div>\n Have {_.filter(this.state.todos, {done:true}).length} todos done,\n and {_.filter(this.state.todos, {done:false}).length} not done\n <br/>\n <div rt-repeat=\"todo in this.state.todos\" key=\"{todo.key}\">\n <button onClick=\"(e)=>e.preventDefault(); this.remove(todo)\">x</button>\n <input type=\"checkbox\" checked=\"{todo.done}\" onChange=\"()=>this.toggleChecked(todoIndex)\"/>\n <span style=\"{todo.done ? {'text-decoration':'line-through'} : {} }\">{todo.value}</span>\n </div>\n <input key=\"myinput\" type=\"text\" onKeyDown=\"(e) => if (e.keyCode == 13) { this.add(); }\" valueLink=\"{this.linkState('edited')}\"/>\n <button onClick=\"(e)=>e.preventDefault(); this.add()\" >Add</button><br/>\n <button onClick=\"(e)=>e.preventDefault(); this.clearDone()\">Clear done</button>\n</div>";
var templateProps = "{\n mixins: [React.addons.LinkedStateMixin],\n getInitialState: function () {\n return {edited: '', todos: [], counter: 0};\n },\n add: function () {\n if (this.state.edited.trim().length === 0) {\n return;\n }\n var newTodo = {value: this.state.edited, done: false, key: this.state.counter};\n this.setState({todos: this.state.todos.concat(newTodo), edited: '', counter: this.state.counter + 1});\n },\n remove: function (todo) {\n this.setState({todos: _.reject(this.state.todos, todo)});\n },\n toggleChecked: function (index) {\n var todos = _.cloneDeep(this.state.todos);\n todos[index].done = !todos[index].done;\n this.setState({todos: todos});\n },\n clearDone: function () {\n this.setState({todos: _.filter(this.state.todos, {done: false})});\n }\n}";
var Playground = React.createClass({
displayName: 'Playground',
mixins: [React.addons.LinkedStateMixin],
updateSample: function (state) {
this.sampleFunc = generateTemplateFunction(state.templateHTML);
this.validHTML = this.sampleFunc !== emptyFunc;
this.sampleRender = generateRenderFunc(this.sampleFunc);
var classBase = {};
try {
this.validProps = true;
console.log(state.templateProps);
classBase = eval("("+state.templateProps+")");
if (!_.isObject(classBase)) {
throw "failed to eval";
}
} catch (e) {
classBase = {};
this.validProps = false;
}
classBase.render = this.sampleRender;
console.log(classBase);
this.sample = React.createFactory(React.createClass(classBase));
},
getInitialState: function () {
var currentState = {
templateHTML:templateHTML,
templateProps: templateProps
};
this.updateSample(currentState);
return currentState;
},
componentWillUpdate: function (nextProps,nextState) {
if (nextState.templateHTML !== this.state.templateHTML || nextState.templateProps !== this.state.templateProps) {
this.updateSample(nextState);
}
},
render: function () {
return playgroundTemplate.apply(this);
}
});
React.render(Playground(),document.getElementById('playground'));

View File

@ -1,20 +0,0 @@
.hidden {
display:none;
}
.large-text-area {
width:600px;
height:300px;
}
.code-area {
width:620px;
height:620px;
float:left;
}
.result-area {
width: 400px;
height: 600px;
float:left;
}

View File

@ -1,30 +0,0 @@
<!DOCTYPE rt CodeEditor="react-code-mirror">
<div>
<div class="code-area">
<form>
<CodeEditor class="large-text-area" style="border: {this.validHTML? '1px solid black':'2px solid red'};"
value="{this.state.templateHTML}"
mode="htmlmixed"
smartIndent="{true}"
lineNumbers="{true}"
onChange="(evt) => this.setState({'templateHTML':evt.target.value})"
/>
<br/>
<CodeEditor class="large-text-area" style="border: {this.validProps? '1px solid black':'2px solid red'};"
value="{this.state.templateProps}"
mode="javascript"
theme="solarized"
smartIndent="{true}"
lineNumbers="{true}"
onChange="(evt) => this.setState({'templateProps':evt.target.value})"
/>
</form>
</div>
<div class="result-area">
<this.sample>
</this.sample>
</div>
</div>

View File

@ -1,30 +0,0 @@
var React = require('react');
var _ = require('lodash');
var CodeEditor = require('react-code-mirror');
'use strict';
function onChange1(evt) {
this.setState({ 'templateHTML': evt.target.value });
}
function onChange2(evt) {
this.setState({ 'templateProps': evt.target.value });
}
module.exports = function () {
return React.DOM.div({}, React.DOM.div({ 'className': 'code-area' }, React.DOM.form({}, CodeEditor({
'className': 'large-text-area',
'style': { border: this.validHTML ? '1px solid black' : '2px solid red' },
'value': this.state.templateHTML,
'mode': 'htmlmixed',
'smartIndent': true,
'lineNumbers': true,
'onChange': onChange1.bind(this)
}), React.DOM.br({}), CodeEditor({
'className': 'large-text-area',
'style': { border: this.validProps ? '1px solid black' : '2px solid red' },
'value': this.state.templateProps,
'mode': 'javascript',
'theme': 'solarized',
'smartIndent': true,
'lineNumbers': true,
'onChange': onChange2.bind(this)
}))), React.DOM.div({ 'className': 'result-area' }, this.sample({})));
};

View File

@ -1,170 +0,0 @@
/*
Solarized theme for code-mirror
http://ethanschoonover.com/solarized
*/
/*
Solarized color pallet
http://ethanschoonover.com/solarized/img/solarized-palette.png
*/
.solarized.base03 { color: #002b36; }
.solarized.base02 { color: #073642; }
.solarized.base01 { color: #586e75; }
.solarized.base00 { color: #657b83; }
.solarized.base0 { color: #839496; }
.solarized.base1 { color: #93a1a1; }
.solarized.base2 { color: #eee8d5; }
.solarized.base3 { color: #fdf6e3; }
.solarized.solar-yellow { color: #b58900; }
.solarized.solar-orange { color: #cb4b16; }
.solarized.solar-red { color: #dc322f; }
.solarized.solar-magenta { color: #d33682; }
.solarized.solar-violet { color: #6c71c4; }
.solarized.solar-blue { color: #268bd2; }
.solarized.solar-cyan { color: #2aa198; }
.solarized.solar-green { color: #859900; }
/* Color scheme for code-mirror */
.cm-s-solarized {
line-height: 1.45em;
color-profile: sRGB;
rendering-intent: auto;
}
.cm-s-solarized.cm-s-dark {
color: #839496;
background-color: #002b36;
text-shadow: #002b36 0 1px;
}
.cm-s-solarized.cm-s-light {
background-color: #fdf6e3;
color: #657b83;
text-shadow: #eee8d5 0 1px;
}
.cm-s-solarized .CodeMirror-widget {
text-shadow: none;
}
.cm-s-solarized .cm-keyword { color: #cb4b16 }
.cm-s-solarized .cm-atom { color: #d33682; }
.cm-s-solarized .cm-number { color: #d33682; }
.cm-s-solarized .cm-def { color: #2aa198; }
.cm-s-solarized .cm-variable { color: #268bd2; }
.cm-s-solarized .cm-variable-2 { color: #b58900; }
.cm-s-solarized .cm-variable-3 { color: #6c71c4; }
.cm-s-solarized .cm-property { color: #2aa198; }
.cm-s-solarized .cm-operator {color: #6c71c4;}
.cm-s-solarized .cm-comment { color: #586e75; font-style:italic; }
.cm-s-solarized .cm-string { color: #859900; }
.cm-s-solarized .cm-string-2 { color: #b58900; }
.cm-s-solarized .cm-meta { color: #859900; }
.cm-s-solarized .cm-qualifier { color: #b58900; }
.cm-s-solarized .cm-builtin { color: #d33682; }
.cm-s-solarized .cm-bracket { color: #cb4b16; }
.cm-s-solarized .CodeMirror-matchingbracket { color: #859900; }
.cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; }
.cm-s-solarized .cm-tag { color: #93a1a1 }
.cm-s-solarized .cm-attribute { color: #2aa198; }
.cm-s-solarized .cm-header { color: #586e75; }
.cm-s-solarized .cm-quote { color: #93a1a1; }
.cm-s-solarized .cm-hr {
color: transparent;
border-top: 1px solid #586e75;
display: block;
}
.cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; }
.cm-s-solarized .cm-special { color: #6c71c4; }
.cm-s-solarized .cm-em {
color: #999;
text-decoration: underline;
text-decoration-style: dotted;
}
.cm-s-solarized .cm-strong { color: #eee; }
.cm-s-solarized .cm-tab:before {
content: "➤"; /*visualize tab character*/
color: #586e75;
position:absolute;
}
.cm-s-solarized .cm-error,
.cm-s-solarized .cm-invalidchar {
color: #586e75;
border-bottom: 1px dotted #dc322f;
}
.cm-s-solarized.cm-s-dark .CodeMirror-selected {
background: #073642;
}
.cm-s-solarized.cm-s-light .CodeMirror-selected {
background: #eee8d5;
}
/* Editor styling */
/* Little shadow on the view-port of the buffer view */
.cm-s-solarized.CodeMirror {
-moz-box-shadow: inset 7px 0 12px -6px #000;
-webkit-box-shadow: inset 7px 0 12px -6px #000;
box-shadow: inset 7px 0 12px -6px #000;
}
/* Gutter border and some shadow from it */
.cm-s-solarized .CodeMirror-gutters {
border-right: 1px solid;
}
/* Gutter colors and line number styling based of color scheme (dark / light) */
/* Dark */
.cm-s-solarized.cm-s-dark .CodeMirror-gutters {
background-color: #002b36;
border-color: #00232c;
}
.cm-s-solarized.cm-s-dark .CodeMirror-linenumber {
text-shadow: #021014 0 -1px;
}
/* Light */
.cm-s-solarized.cm-s-light .CodeMirror-gutters {
background-color: #fdf6e3;
border-color: #eee8d5;
}
/* Common */
.cm-s-solarized .CodeMirror-linenumber {
color: #586e75;
padding: 0 5px;
}
.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; }
.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; }
.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; }
.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text {
color: #586e75;
}
.cm-s-solarized .CodeMirror-lines .CodeMirror-cursor {
border-left: 1px solid #819090;
}
/*
Active line. Negative margin compensates left padding of the text in the
view-port
*/
.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background {
background: rgba(255, 255, 255, 0.10);
}
.cm-s-solarized.cm-s-light .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.10);
}

View File

@ -1,96 +0,0 @@
define([
'lodash',
'jquery',
'react',
'ImageSearch.rt'
], function (_, $, React, template) {
'use strict';
var ImageSearch = React.createClass({
displayName: 'ImageSearch',
mixins: [React.addons.LinkedStateMixin],
seq: 0,
total: 0,
hasMore: true,
heights: [0, 0, 0],
realTerm: 'cats',
getInitialState: function () {
setTimeout(this.search, 0);
return {
searchTerm: this.realTerm,
items: [[], [], []]
};
},
search: function() {
this.state.items = [[], [], []];
this.total = 0;
this.heights = [0, 0, 0];
this.hasMore = true;
this.realTerm = this.state.searchTerm;
this.loadMore();
},
indexOfMin: function(array) {
var indexAndMin = _.reduce(array, function(accum, height, index) {
return (height < accum.min) ? { i: index, min: height } : accum;
}, {i: -1, min: Number.MAX_VALUE});
return indexAndMin.i;
},
loadMore: function(done) {
done = done || function() {};
if (!this.hasMore) {
done();
return;
}
var url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&start=' + this.total + '&q=' + this.realTerm + "&callback=?";
var self = this;
$.ajax({url: url, dataType: 'jsonp'})
.done(function(data){
if (!data.responseData) {
self.hasMore = false;
done();
return;
}
var results = data.responseData.results;
var items = _.cloneDeep(self.state.items);
for (var i = 0; i < results.length; i++) {
var result = data.responseData.results[i];
var minHeightIndex = self.indexOfMin(self.heights);
items[minHeightIndex].push({
id: self.seq + 1,
title: result.titleNoFormatting,
url: result.url,
ratio: result.width / result.height,
originalContext: result.originalContextUrl
});
var relativeHeight = result.height / result.width;
self.heights[minHeightIndex] = self.heights[minHeightIndex] + relativeHeight;
self.total++;
self.seq++;
}
self.setState({items: items});
done();
});
},
shouldComponentUpdate: function(nextProps, nextState) {
return !_.isEqual(this.state, nextState);
},
render: function () {
return template.apply(this);
}
});
return ImageSearch;
});

View File

@ -1,18 +0,0 @@
<!doctype jsx InfiniteScroll="InfiniteScroll">
<div className="innerContainer">
<div className="searchbox">
<input type="text" valueLink="{this.linkState('searchTerm')}" onKeyDown="(e) => if (e.keyCode == 13) { this.search(); return false; }"></input>
<button onClick="() => this.search(); return false;">Search</button>
</div>
<InfiniteScroll className="fixed" onLoadMore="{this.loadMore}" threshold="{150}">
<div rt-repeat="row in [0, 1, 2]" key="{row}">
<a rt-repeat="i in this.state.items[row]" href="{i.originalContext}" target="blank" className="container fadeInDown" key="{i.id}">
<div style="padding-top: {Math.floor(100/i.ratio)}%; background-color: grey"></div>
<div className="imgContainer">
<img width="100%" src="{i.url}"/>
<div className="title">{i.title}</div>
</div>
</a>
</div>
</InfiniteScroll>
</div>

View File

@ -1,57 +0,0 @@
define([
'react',
'lodash',
'InfiniteScroll'
], function (React, _, InfiniteScroll) {
'use strict';
function onKeyDown1(e) {
if (e.keyCode == 13) {
this.search();
return false;
}
}
function onClick2() {
this.search();
return false;
}
function repeatI3(row, rowIndex, i, iIndex) {
return React.DOM.a({
'href': i.originalContext,
'target': 'blank',
'className': 'container fadeInDown',
'key': i.id
}, React.DOM.div({
'style': {
paddingTop: Math.floor(100 / i.ratio) + '%',
backgroundColor: 'grey'
}
}), React.DOM.div({ 'className': 'imgContainer' }, React.DOM.img({
'width': '100%',
'src': i.url
}), React.DOM.div({ 'className': 'title' }, i.title)));
}
function repeatRow4(row, rowIndex) {
return React.DOM.div.apply(this, _.flatten([
{ 'key': row },
_.map(this.state.items[row], repeatI3.bind(this, row, rowIndex))
]));
}
return function () {
return React.DOM.div({ 'className': 'innerContainer' }, React.DOM.div({ 'className': 'searchbox' }, React.DOM.input({
'type': 'text',
'valueLink': this.linkState('searchTerm'),
'onKeyDown': onKeyDown1.bind(this)
}), React.DOM.button({ 'onClick': onClick2.bind(this) }, 'Search')), InfiniteScroll.apply(this, _.flatten([
{
'className': 'fixed',
'onLoadMore': this.loadMore,
'threshold': 150
},
_.map([
0,
1,
2
], repeatRow4.bind(this))
])));
};
});

View File

@ -1,37 +0,0 @@
define([
'lodash',
'jquery',
'react'
], function (_, $, React) {
'use strict';
var InfiniteScroll = React.createClass({
displayName: 'InfiniteScroll',
gettingMore: false,
onLoadMoreFinished: function() {
this.gettingMore = false;
},
onScroll: function(evt) {
if (!this.props.onLoadMore || this.gettingMore) {
return;
}
var threshold = this.props.threshold || 0;
var raw = evt.target;
if (raw.scrollTop + raw.offsetHeight + threshold >= raw.scrollHeight) {
this.gettingMore = true;
this.props.onLoadMore(this.onLoadMoreFinished);
}
},
render: function () {
var passedProps = _.omit(this.props, ['onLoadMore', 'threshold', 'children', 'onScroll']);
passedProps.onScroll = this.onScroll;
return React.DOM.div(passedProps, this.props.children);
}
});
return InfiniteScroll;
});

View File

@ -1,16 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React templates - Image Search Sample</title>
<link rel="stylesheet" href="sample.css"/>
</head>
<body>
<form id="main" style="padding:20px 150px;">
<!-- the lists panel -->
<div id="preloader" style="text-align: center;">Loading...</div>
</form>
<script data-main="main.js" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.15/require.js"></script>
</body>
</html>

View File

@ -1,20 +0,0 @@
'use strict';
requirejs.config({
// baseUrl: '/',
paths: {
lodash: 'http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash',
jquery: 'http://code.jquery.com/jquery-1.11.0.min',
react: 'http://fb.me/react-with-addons-0.12.0'
},
shim: {
lodash: { exports: '_' },
jquery: { exports: '$' },
react: { exports: 'React' }
}
});
requirejs(['jquery', 'react', 'ImageSearch'], function ($, React, ImageSearch) {
React.renderComponent(ImageSearch(), $('#main').get(0));
});

View File

@ -1,83 +0,0 @@
.imgContainer {
position:absolute;
top:0;
bottom:0;
left:0;
right:0;
}
.fixed {
height: calc(553px - 53px);
overflow-y: auto;
overflow-x: hidden;
}
.fixed > div {
width: 33%;
display: inline-block;
vertical-align: top;
}
.searchbox {
text-align:center;
height: 53px;
}
.container {
display: block;
background-color: gray;
position: relative;
margin: 0 10px 10px 0;
}
@-webkit-keyframes fadeInDown{
0%{
opacity:0;
-webkit-transform:translate3d(0,-50%,0);
transform:translate3d(0,-50%,0)
}
100%{
opacity:1;
-webkit-transform:none;
transform:none
}
}
@keyframes fadeInDown{
0%{
opacity:0;
-webkit-transform:translate3d(0,-50%,0);
-ms-transform:translate3d(0,-50%,0);
transform:translate3d(0,-50%,0)
}
100%{
opacity:1;
-webkit-transform:none;
-ms-transform:none;
transform:none
}
}
.fadeInDown{
-webkit-animation:fadeInDown 1s;
animation:fadeInDown 1s;
}
.title {
color: white;
background-color: black;
font-family: Arial, Helvetica, sans-serif;
font-size: 9pt;
opacity: 0;
position: absolute;
bottom:0;
}
.container:hover .title {
opacity: 1;
transition: opacity 0.75s;
}

View File

@ -1,56 +0,0 @@
#!/usr/bin/env node
/**
* Created by idok on 11/10/14.
*/
'use strict';
//var fs = require('fs');
var _ = require('lodash');
var path = require('path');
var reactTemplates = require('./reactTemplates');
var pkg = require('../package.json');
var options = {commonJS: false};
if (process.argv.length > 2) {
var files = [];
_.forEach(process.argv.slice(2),function (param) {
if (param === '-v' || param === '--version') {
console.log(pkg.version);
} else if (param === '-h' || param === '--help') {
printHelp();
} else if (param === '--common') {
options.commonJS = true;
} else {
files.push(param);
}
});
_.forEach(files,handleSingleFile);
} else {
printHelp();
}
function printHelp() {
console.log(pkg.description);
console.log('');
console.log('Usage:');
console.log(' $ node reactTemplates.js <filename>');
}
function handleSingleFile(filename) {
if (path.extname(filename) !== '.rt') {
console.log('invalid file, only handle rt files');
return;// only handle html files
}
// var html = fs.readFileSync(filename).toString();
// if (!html.match(/\<\!doctype jsx/)) {
// console.log('invalid file, missing header');
// return;
// }
// var js = reactTemplates.convertTemplateToReact(html);
// fs.writeFileSync(filename + '.js', js);
try {
reactTemplates.convertFile(filename, filename + '.js', options);
} catch (e) {
console.log('Error processing file: ' + filename + ', ' + e.description);
}
}

View File

@ -1,317 +0,0 @@
/**
* Created by avim on 11/9/2014.
*/
'use strict';
var cheerio = require('cheerio');
var _ = require('lodash');
var esprima = require('esprima');
var escodegen = require('escodegen');
var React = require('react');
var fs = require('fs');
var chalk = require('chalk');
var repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.bind(<%= repeatBinds %>))');
var ifTemplate = _.template('((<%= condition %>)?(<%= body %>):null)');
var classSetTemplate = _.template('React.addons.classSet(<%= classSet %>)');
var simpleTagTemplate = _.template('<%= name %>(<%= props %><%= children %>)');
var tagTemplate = _.template('<%= name %>.apply(this,_.flatten([<%= props %><%= children %>]))');
var commentTemplate = _.template(' /* <%= data %> */ ');
var templateAMDTemplate = _.template("define([<%= requirePaths %>], function (<%= requireNames %>) {\n'use strict';\n <%= injectedFunctions %>\nreturn function(){ return <%= body %>};\n});");
var templateCommonJSTemplate = _.template("<%= vars %>\n\n'use strict';\n <%= injectedFunctions %>\nmodule.exports = function(){ return <%= body %>};\n");
var templateProp = 'rt-repeat';
var ifProp = 'rt-if';
var classSetProp = 'rt-class';
var scopeProp = 'rt-scope';
var reactSupportedAttributes = ['accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'checked', 'classID', 'className', 'cols', 'colSpan', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'defer', 'dir', 'disabled', 'download', 'draggable', 'encType', 'form', 'formNoValidate', 'frameBorder', 'height', 'hidden', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'label', 'lang', 'list', 'loop', 'manifest', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'multiple', 'muted', 'name', 'noValidate', 'open', 'pattern', 'placeholder', 'poster', 'preload', 'radioGroup', 'readOnly', 'rel', 'required', 'role', 'rows', 'rowSpan', 'sandbox', 'scope', 'scrolling', 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcSet', 'start', 'step', 'style', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode'];
var attributesMapping = {'class': 'className', 'rt-class': 'className'};
_.forEach(reactSupportedAttributes,function (attributeReactName) {
if (attributeReactName !== attributeReactName.toLowerCase()) {
attributesMapping[attributeReactName.toLowerCase()] = attributeReactName;
}
});
function concatChildren(children) {
var res = '';
_.forEach(children, function (child) {
if (child.indexOf(' /*') !== 0 && child) {
res += ',' + child;
} else {
res += child;
}
}, this);
return res;
}
function convertToCamelCase(str) {
return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}
var curlyMap = {'{': 1, '}': -1};
function convertText(txt) {
txt = txt.trim();
var res = '';
var first = true;
while (txt.indexOf('{') !== -1) {
var start = txt.indexOf('{');
var pre = txt.substr(0,start);
if (pre) {
res += (first ? '' : '+') + JSON.stringify(pre);
first = false;
}
var curlyCounter = 1;
for (var end = start + 1; end < txt.length && curlyCounter > 0; end++) {
curlyCounter += curlyMap[txt.charAt(end)] || 0;
}
if (curlyCounter !== 0) {
throw 'Failed to parse text';
} else {
res += (first ? '' : '+') + txt.substr(start + 1, end - start - 2);
first = false;
txt = txt.substr(end);
}
}
if (txt) {
res += (first ? '' : '+') + JSON.stringify(txt);
}
if (res === '') {
res = 'true';
}
return res;
}
function isStringOnlyCode(txt) {
txt = txt.trim();
return txt.length && txt.charAt(0) === '{' && txt.charAt(txt.length - 1) === '}';
}
function generateInjectedFunc(context, namePrefix, body, params) {
params = params || context.boundParams;
var generatedFuncName = namePrefix.replace(',','') + (context.injectedFunctions.length + 1);
var funcText = 'function ' + generatedFuncName + '(' + params.join(',');
funcText += ') {\n' + body + '\n}\n';
context.injectedFunctions.push(funcText);
return generatedFuncName;
}
function generateProps(node, context) {
var props = {};
_.forOwn(node.attribs, function (val, key) {
var propKey = attributesMapping[key.toLowerCase()] || key;
if (props.hasOwnProperty(propKey)) {
throw 'duplicate definition of ' + propKey + ' ' + JSON.stringify(node.attribs);
}
if (key.indexOf('on') === 0 && !isStringOnlyCode(val)) {
var funcParts = val.split('=>');
if (funcParts.length !== 2) {
throw 'when using "on" events, use lambda "(p1,p2)=>body" notation or use {} to return a callback function. error: [' + key + '="' + val + '"]';
}
var evtParams = funcParts[0].replace('(', '').replace(')', '').trim();
var funcBody = funcParts[1].trim();
var params = context.boundParams;
if (evtParams.trim() !== '') {
params = params.concat([evtParams.trim()]);
}
var generatedFuncName = generateInjectedFunc(context, key, funcBody, params);
props[propKey] = generatedFuncName + '.bind(' + (['this'].concat(context.boundParams)).join(',') + ')';
} else if (key === 'style' && !isStringOnlyCode(val)) {
var styleParts = val.trim().split(';');
styleParts = _.compact(_.map(styleParts, function (str) {
str = str.trim();
if (!str || str.indexOf(':') === -1) {
return null;
}
var res = str.split(':');
res[0] = res[0].trim();
res[1] = res.slice(1).join(':').trim();
return res;
}));
var styleArray = [];
_.forEach(styleParts, function (stylePart) {
styleArray.push(convertToCamelCase(stylePart[0]) + ' : ' + convertText(stylePart[1]));
});
props[propKey] = '{' + styleArray.join(',') + '}';
} else if (key === classSetProp) {
props[propKey] = classSetTemplate({classSet: val});
} else if (key.indexOf('rt-') !== 0) {
props[propKey] = convertText(val);
}
});
return '{' + _.map(props, function (val, key) {
return JSON.stringify(key) + ' : ' + val;
}).join(',') + '}';
}
function convertTagNameToConstructor(tagName) {
return React.DOM.hasOwnProperty(tagName) ? 'React.DOM.' + tagName : tagName;
}
function defaultContext() {
return {
boundParams: [],
injectedFunctions: []
};
}
function addIfNotThere(array, obj) {
if (!_.contains(array, obj)) {
array.push(obj);
}
}
function hasNonSimpleChildren(node) {
return _.any(node.children, function (child) {
return child.type === 'tag' && child.attribs[templateProp];
});
}
function convertHtmlToReact(node, context) {
if (node.type === 'tag') {
context = {
boundParams: _.clone(context.boundParams),
injectedFunctions: context.injectedFunctions
};
var data = {name: convertTagNameToConstructor(node.name)};
if (node.attribs[scopeProp]) {
data.scopeMapping = {};
data.scopeName = '';
_.each(context.boundParams, function (boundParam) {
data.scopeMapping[boundParam] = boundParam;
});
_.each(node.attribs[scopeProp].split(';'), function (scopePart) {
var scopeSubParts = scopePart.split(' as ');
var scopeName = scopeSubParts[1].trim();
addIfNotThere(context.boundParams, scopeName);
data.scopeName += capitalize(scopeName);
data.scopeMapping[scopeName] = scopeSubParts[0].trim();
});
}
if (node.attribs[templateProp]) {
data.item = node.attribs[templateProp].split(' in ')[0].trim();
data.collection = node.attribs[templateProp].split(' in ')[1].trim();
addIfNotThere(context.boundParams, data.item);
addIfNotThere(context.boundParams, data.item + 'Index');
}
data.props = generateProps(node, context);
if (node.attribs[ifProp]) {
data.condition = node.attribs[ifProp].trim();
}
data.children = concatChildren(_.map(node.children, function (child) {
return convertHtmlToReact(child, context);
}));
if (hasNonSimpleChildren(node)) {
data.body = tagTemplate(data);
} else {
data.body = simpleTagTemplate(data);
}
if (node.attribs[templateProp]) {
data.repeatFunction = generateInjectedFunc(context, 'repeat' + capitalize(data.item), 'return ' + data.body);
data.repeatBinds = ['this'].concat(_.reject(context.boundParams, function (param) {
return (param === data.item || param === data.item + 'Index');
}));
data.body = repeatTemplate(data);
}
if (node.attribs[ifProp]) {
data.body = ifTemplate(data);
}
if (node.attribs[scopeProp]) {
var generatedFuncName = generateInjectedFunc(context, 'scope' + data.scopeName, 'return ' + data.body, _.keys(data.scopeMapping));
data.body = generatedFuncName + '.apply(this, [' + _.values(data.scopeMapping).join(',') + '])';
}
return data.body;
} else if (node.type === 'comment') {
return (commentTemplate(node));
} else if (node.type === 'text') {
if (node.data.trim()) {
return convertText(node.data.trim());
}
return '';
}
}
function extractDefinesFromJSXTag(html, defines) {
html = html.replace(/\<\!doctype rt\s*(.*?)\s*\>/i, function(full, reqStr) {
var match = true;
while (match) {
match = false;
reqStr = reqStr.replace(/\s*(\w+)\s*\=\s*\"([^\"]*)\"\s*/, function(full, varName, reqPath) {
defines[reqPath] = varName;
match = true;
return '';
});
}
return '';
});
return html;
}
/**
* @param {string} html
* @return {string}
*/
function convertTemplateToReact(html,options) {
// var x = cheerio.load(html);
options = options || {};
var defines = {react: 'React', lodash: '_'};
html = extractDefinesFromJSXTag(html, defines);
var rootNode = cheerio.load(html.trim(), {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true});
var context = defaultContext();
var body = convertHtmlToReact(rootNode.root()[0].children[0], context);
var requirePaths = _(defines).keys().map(function (reqName) { return '"' + reqName + '"'; }).value().join(',');
var requireVars = _(defines).values().value().join(',');
var vars = _(defines).map(function (reqVar,reqPath) {return "var "+reqVar+" = require('"+reqPath+"');"}).join("\n");
var data = {body: body, injectedFunctions: '', requireNames: requireVars, requirePaths: requirePaths, vars:vars};
data.injectedFunctions = context.injectedFunctions.join('\n');
var code = options.commonJS ? templateCommonJSTemplate(data) : templateAMDTemplate(data);
try {
var tree = esprima.parse(code, {range: true, tokens: true, comment: true});
tree = escodegen.attachComments(tree, tree.comments, tree.tokens);
code = escodegen.generate(tree, {comment: true});
} catch (e) {
// TODO error handling
console.log(e);
}
return code;
}
/**
* @param {string} source
* @param {string} target
*/
function convertFile(source, target, options) {
// if (path.extname(filename) !== ".html") {
// console.log('invalid file, only handle html files');
// return;// only handle html files
// }
var util = require('./util');
if (!util.isStale(source, target)) {
console.log('target file ' + chalk.cyan(target) + ' is up to date, skipping');
// return;
}
var html = fs.readFileSync(source).toString();
if (!html.match(/\<\!doctype rt/i)) {
throw new Error('invalid file, missing header');
}
var js = convertTemplateToReact(html, options);
fs.writeFileSync(target, js);
}
module.exports = {
convertTemplateToReact: convertTemplateToReact,
convertFile: convertFile,
_test: {}
};

View File

@ -1,21 +0,0 @@
'use strict';
var fs = require('fs');
//var path = require('path');
/**
* @param {string} source
* @param {string} target
* @return {boolean}
*/
function isStale(source, target) {
if (!fs.existsSync(target)) {
return true;
}
var sourceTime = fs.statSync(source).mtime;
var targetTime = fs.statSync(target).mtime;
return sourceTime.getTime() > targetTime.getTime();
}
module.exports = {
isStale: isStale
};

View File

@ -1,3 +0,0 @@
<!doctype rt>
<div>
</div>

View File

@ -1,9 +0,0 @@
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.div({});
};
});

View File

@ -1,6 +0,0 @@
<!DOCTYPE rt>
<div rt-scope="{value:'event did not happen because onClick not called'} as data"
onMouseDown="(evt) => data.value = 'event did happen though it should not'"
onClick="() => data.value = 'event did happen though it should not'">
{data.value}
</div>

View File

@ -1,3 +0,0 @@
<div>
event did not happen because onClick not called
</div>

View File

@ -1,8 +0,0 @@
<!DOCTYPE rt>
<p>
<div rt-repeat="items in this.props.things">
<span style="width:auto;line-height: 5px;"
onClick="(evt)=>this.happend(evt);return false;"
onMouseDown="()=>this.happend();return false;">Mock</span>
</div>
</p>

View File

@ -1,30 +0,0 @@
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function onClick1(items, itemsIndex, evt) {
this.happend(evt);
return false;
}
function onMouseDown2(items, itemsIndex) {
this.happend();
return false;
}
function repeatItems3(items, itemsIndex) {
return React.DOM.div({}, React.DOM.span({
'style': {
width: 'auto',
lineHeight: '5px'
},
'onClick': onClick1.bind(this, items, itemsIndex),
'onMouseDown': onMouseDown2.bind(this, items, itemsIndex)
}, 'Mock'));
}
return function () {
return React.DOM.p.apply(this, _.flatten([
{},
_.map(this.props.things, repeatItems3.bind(this))
]));
};
});

View File

@ -1,9 +0,0 @@
<!DOCTYPE rt>
<div>
<div rt-scope="['a','b','c'] as items">
<span rt-repeat="item in items">Item:#{itemIndex} = {item}</span>
</div>
<span>{typeof (items) == 'undefined'?'items not in scope':'items in scope'}</span>
<span>{typeof (item) == 'undefined'?'item not in scope':'item in scope'}</span>
</div>

View File

@ -1,9 +0,0 @@
<div>
<div>
<span>Item:#0 = a</span>
<span>Item:#1 = b</span>
<span>Item:#2 = c</span>
</div>
<span>items not in scope</span>
<span>item not in scope</span>
</div>

View File

@ -1,16 +0,0 @@
<!doctype rt>
<div>
<div style= "position: relative; textAlign: center;
top: {this.props.config.previewTop};
height: {this.props.config.previewHeight}">
<div style="margin: auto; height: 100%; width: {this.props.config.previewWidth || '100%'}">
<iframe id="preview" src="http://localhost/sites/412?ds=true" style="width: 100%; height: 100%; border: 0"/>
</div>
</div>
<div>editor
<div rt-if="!this.props.editorState.previewMode">left bar</div>
</div>
</div>

View File

@ -1,30 +0,0 @@
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.div({}, React.DOM.div({
'style': {
position: 'relative',
textAlign: 'center',
top: this.props.config.previewTop,
height: this.props.config.previewHeight
}
}, React.DOM.div({
'style': {
margin: 'auto',
height: '100%',
width: this.props.config.previewWidth || '100%'
}
}, React.DOM.iframe({
'id': 'preview',
'src': 'http://localhost/sites/412?ds=true',
'style': {
width: '100%',
height: '100%',
border: '0'
}
}))), React.DOM.div({}, 'editor', !this.props.editorState.previewMode ? React.DOM.div({}, 'left bar') : null));
};
});

View File

@ -1,92 +0,0 @@
'use strict';
var test = require('tape');
var reactTemplates = require('../../src/reactTemplates');
var fs = require('fs');
var _ = require('lodash');
var path = require('path');
var React = require('react');
var cheerio = require('cheerio');
var dataPath = path.resolve(__dirname, '..', 'data');
test('conversion test', function (t) {
var files = ['div.rt', 'test.rt', 'repeat.rt'];
t.plan(files.length);
files.forEach(check);
function check(testFile) {
var filename = path.join(dataPath, testFile);
var html = fs.readFileSync(filename).toString();
var expected = fs.readFileSync(filename + '.js').toString().replace(/\r/g, '').trim();
// var expected = fs.readFileSync(filename.replace(".html", ".js")).toString();
var actual = reactTemplates.convertTemplateToReact(html).replace(/\r/g, '').trim();
t.equal(actual, expected);
if (actual !== expected) {
fs.writeFileSync(filename + '.actual.js', actual);
}
}
});
function normalizeHtml(html) {
return cheerio.load(html, {normalizeWhitespace: true}).html()
.replace(/\>\s+/mg, '>')
.replace(/\s+\</mg, '<')
.replace(/\>\s+\</mg, '><');
}
test('html tests', function (t) {
var files = ['scope.rt', 'lambda.rt'];
t.plan(files.length);
files.forEach(check);
function check(testFile) {
var filename = path.join(dataPath, testFile);
var html = fs.readFileSync(filename).toString();
var expected = fs.readFileSync(filename + '.html').toString().replace(/\r/g, '');
// var expected = fs.readFileSync(filename.replace(".html", ".js")).toString();
var code = reactTemplates.convertTemplateToReact(html).replace(/\r/g, '');
var defineMap = {react: React, lodash: _};
var define = function (requirementsNames, content) {
var requirements = _.map(requirementsNames,function (reqName) {
return defineMap[reqName];
});
return content.apply(this,requirements);
};
var comp = React.createFactory(React.createClass({
render: eval(code)
}));
var actual = React.renderToStaticMarkup(comp());
actual = normalizeHtml(actual);
expected = normalizeHtml(expected);
t.equal(actual, expected);
if (actual !== expected) {
fs.writeFileSync(filename + '.actual.html', actual);
}
}
});
test('util.isStale', function (t) {
t.plan(2);
var a = path.join(dataPath, 'a.tmp');
var b = path.join(dataPath, 'b.tmp');
fs.writeFileSync(a, 'actual');
fs.writeFileSync(b, 'actual');
var mtime1 = new Date(1995, 11, 17, 3, 24, 0);
fs.utimesSync(a, mtime1, mtime1);
var mtime2 = new Date(1995, 11, 17, 3, 24, 1);
fs.utimesSync(b, mtime2, mtime2);
var util = require('../../src/util');
var actual = util.isStale(a, b);
t.equal(actual, false);
actual = util.isStale(b, a);
t.equal(actual, true);
fs.unlinkSync(a);
fs.unlinkSync(b);
});