├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── react-timesheet.js └── react-timesheet.min.js ├── example ├── dist │ ├── JSXTransformer.js │ ├── app.js │ ├── bundle.js │ ├── codemirror.css │ ├── codemirror.js │ ├── common.js │ ├── index.html │ ├── javascript.js │ ├── timesheet-white.css │ └── timesheet.css └── src │ ├── app.js │ └── index.html ├── gulpfile.js ├── package.json └── src ├── TimeSheet.js ├── timesheet-white.css ├── timesheet-white.sass ├── timesheet.css └── timesheet.sass /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage tools 11 | lib-cov 12 | coverage 13 | 14 | # Compiled binary addons (http://nodejs.org/api/addons.html) 15 | build/Release 16 | 17 | # Dependency directory 18 | node_modules 19 | .idea 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yuanyan Cao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Time Sheet 2 | ================= 3 | 4 | Time Sheet Component for React. Modified from [timesheet.js](https://github.com/sbstjn/timesheet.js) 5 | 6 | ## Demo & Examples 7 | 8 | Live demo: [yuanyan.github.io/react-timesheet](http://yuanyan.github.io/react-timesheet/) 9 | 10 | To build the examples locally, run: 11 | 12 | ``` 13 | npm install 14 | gulp dev 15 | ``` 16 | 17 | Then open [`localhost:9999`](http://localhost:9999) in a browser. 18 | 19 | ## Installation 20 | 21 | The easiest way to use `react-timesheet` is to install it from NPM and include it in your own React build process (using [Browserify](http://browserify.org), etc). 22 | 23 | You can also use the standalone build by including `dist/react-timesheet.js` in your page. If you use this, make sure you have already included React, and it is available as a global variable. 24 | 25 | ``` 26 | npm install react-timesheet --save 27 | ``` 28 | 29 | ## Usage 30 | 31 | ``` 32 | var React = require('react'); 33 | var TimeSheet = require('react-timesheet'); 34 | var Example1 = React.createClass({ 35 | data: [ 36 | ['2002', '09/2002', 'A freaking awesome time', 'lorem'], 37 | ['06/2002', '09/2003', 'Some great memories', 'ipsum'], 38 | ['2003', 'Had very bad luck'], 39 | ['10/2003', '2006', 'At least had fun', 'dolor'], 40 | ['02/2005', '05/2006', 'Enjoyed those times as well', 'ipsum'], 41 | ['07/2005', '09/2005', 'Bad luck again', 'default'], 42 | ['10/2005', '2008', 'For a long time nothing happened', 'dolor'], 43 | ['01/2008', '05/2009', 'LOST Season #4', 'lorem'], 44 | ['01/2009', '05/2009', 'LOST Season #4', 'lorem'], 45 | ['02/2010', '05/2010', 'LOST Season #5', 'lorem'], 46 | ['09/2008', '06/2010', 'FRINGE #1 & #2', 'ipsum'] 47 | ], 48 | render: function() { 49 | return ( 50 | 51 | ); 52 | } 53 | }); 54 | ``` 55 | 56 | ## Properties 57 | 58 | * `data`: Your configure data. 59 | * `min`: Min year value. 60 | * `max`: Max year data. 61 | * `theme`: Custom theme name. 62 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-timesheet", 3 | "main": "dist/react-timesheet.min.js", 4 | "version": "0.1.0", 5 | "homepage": "https://github.com/yuanyan/react-timesheet", 6 | "authors": [ 7 | "Yuanyan Cao" 8 | ], 9 | "description": "Time Sheet Component for React", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "react", 17 | "react-component", 18 | "timesheet" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | ".editorconfig", 23 | ".gitignore", 24 | "package.json", 25 | "src", 26 | "node_modules", 27 | "example", 28 | "test" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /dist/react-timesheet.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.TimeSheet=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 10 ? num : '0' + num; 22 | }; 23 | 24 | /** 25 | * Calculate starting offset for bubble 26 | */ 27 | Bubble.prototype.getStartOffset = function() { 28 | return (this.widthMonth/12) * (12 * (this.start.getFullYear() - this.min) + this.start.getMonth()); 29 | }; 30 | 31 | /** 32 | * Get count of full years from start to end 33 | */ 34 | Bubble.prototype.getFullYears = function() { 35 | return ((this.end && this.end.getFullYear()) || this.start.getFullYear()) - this.start.getFullYear(); 36 | }; 37 | 38 | /** 39 | * Get count of all months in Timesheet Bubble 40 | */ 41 | Bubble.prototype.getMonths = function() { 42 | var fullYears = this.getFullYears(); 43 | var months = 0; 44 | 45 | if (!this.end) { 46 | months += !this.start.hasMonth ? 12 : 1; 47 | } else { 48 | if (!this.end.hasMonth) { 49 | months += 12 - (this.start.hasMonth ? this.start.getMonth() : 0); 50 | months += 12 * (fullYears-1 > 0 ? fullYears-1 : 0); 51 | } else { 52 | months += this.end.getMonth() + 1; 53 | months += 12 - (this.start.hasMonth ? this.start.getMonth() : 0); 54 | months += 12 * (fullYears-1); 55 | } 56 | } 57 | 58 | return months; 59 | }; 60 | 61 | /** 62 | * Get bubble's width in pixel 63 | */ 64 | Bubble.prototype.getWidth = function() { 65 | return (this.widthMonth/12) * this.getMonths(); 66 | }; 67 | 68 | /** 69 | * Get the bubble's label 70 | */ 71 | Bubble.prototype.getDateLabel = function() { 72 | return [ 73 | (this.start.hasMonth ? this.formatMonth(this.start.getMonth() + 1) + '/' : '' ) + this.start.getFullYear(), 74 | (this.end ? '-' + ((this.end.hasMonth ? this.formatMonth(this.end.getMonth() + 1) + '/' : '' ) + this.end.getFullYear()) : '') 75 | ].join(''); 76 | }; 77 | 78 | /** 79 | * Parse data string 80 | */ 81 | function parseDate(date) { 82 | if (date.indexOf('/') === -1) { 83 | date = new Date(parseInt(date, 10), 0, 1); 84 | date.hasMonth = false; 85 | } else { 86 | date = date.split('/'); 87 | date = new Date(parseInt(date[1], 10), parseInt(date[0], 10)-1, 1); 88 | date.hasMonth = true; 89 | } 90 | 91 | return date; 92 | }; 93 | 94 | 95 | var TimeSheet = React.createClass({displayName: "TimeSheet", 96 | propTypes: { 97 | data: React.PropTypes.array.isRequired, 98 | min: React.PropTypes.number, 99 | max: React.PropTypes.number, 100 | theme: React.PropTypes.string 101 | }, 102 | 103 | getDefaultProps: function(){ 104 | return { 105 | className: 'timesheet' 106 | } 107 | }, 108 | 109 | parse: function(data, min, max) { 110 | var ret = []; 111 | 112 | for (var n = 0, m = data.length; n max) { 128 | max = end.getFullYear(); 129 | } else if (beg.getFullYear() > max) { 130 | max = beg.getFullYear(); 131 | } 132 | 133 | ret.push({start: beg, end: end, label: lbl, type: cat}); 134 | } 135 | 136 | return { 137 | data: ret, 138 | year: { 139 | min: min, 140 | max: max 141 | } 142 | }; 143 | }, 144 | 145 | componentDidMount: function() { 146 | var widthMonth = this.refs.year.getDOMNode().offsetWidth; 147 | 148 | this.setState({ 149 | widthMonth: widthMonth 150 | }); 151 | }, 152 | 153 | getLists: function(data, year){ 154 | 155 | var lists = []; 156 | var widthMonth = this.state && this.state.widthMonth; 157 | if(!widthMonth) return lists; 158 | 159 | for (var i = 0, l = data.length; i < l; i++) { 160 | var cur = data[i]; 161 | var bubble = new Bubble(widthMonth, year.min, cur.start, cur.end); 162 | 163 | var style = { 164 | marginLeft: bubble.getStartOffset() + 'px', 165 | width: bubble.getWidth() + 'px' 166 | }; 167 | 168 | var className = 'bubble bubble-' + (cur.type || 'default'); 169 | var duration = cur.end ? Math.round((cur.end-cur.start)/1000/60/60/24/39) : ''; 170 | var date = bubble.getDateLabel(); 171 | var label = cur.label; 172 | 173 | var line = [ 174 | React.createElement("span", {style: style, className: className, "data-duration": duration}), 175 | React.createElement("span", {className: "date"}, date), 176 | React.createElement("span", {className: "label"}, label) 177 | ]; 178 | 179 | lists.push(React.createElement("li", null, line)); 180 | } 181 | 182 | return lists; 183 | }, 184 | 185 | render: function() { 186 | 187 | var result = this.parse(this.props.data, this.props.min, this.props.max); 188 | var data = result.data; 189 | var year = result.year; 190 | 191 | var sections = []; 192 | for (var c = year.min; c <= year.max; c++) { 193 | sections.push(React.createElement("section", {ref: "year"}, c)); 194 | } 195 | 196 | var lists = this.getLists(data, year); 197 | var className = this.props.className + " " + (this.props.theme || ''); 198 | 199 | return ( 200 | React.createElement("div", {className: className}, 201 | React.createElement("div", {className: "scale"}, 202 | sections 203 | ), 204 | React.createElement("ul", {className: "data"}, 205 | lists 206 | ) 207 | ) 208 | ); 209 | } 210 | }); 211 | 212 | module.exports = TimeSheet; 213 | 214 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 215 | },{}]},{},[1])(1) 216 | }); -------------------------------------------------------------------------------- /dist/react-timesheet.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.TimeSheet=t()}}(function(){return function t(e,n,r){function a(i,o){if(!n[i]){if(!e[i]){var h="function"==typeof require&&require;if(!o&&h)return h(i,!0);if(s)return s(i,!0);var l=new Error("Cannot find module '"+i+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[i]={exports:{}};e[i][0].call(u.exports,function(t){var n=e[i][1][t];return a(n?n:t)},u,u.exports,t,e,n,r)}return n[i].exports}for(var s="function"==typeof require&&require,i=0;i=10?t:"0"+t},a.prototype.getStartOffset=function(){return this.widthMonth/12*(12*(this.start.getFullYear()-this.min)+this.start.getMonth())},a.prototype.getFullYears=function(){return(this.end&&this.end.getFullYear()||this.start.getFullYear())-this.start.getFullYear()},a.prototype.getMonths=function(){var t=this.getFullYears(),e=0;return this.end?this.end.hasMonth?(e+=this.end.getMonth()+1,e+=12-(this.start.hasMonth?this.start.getMonth():0),e+=12*(t-1)):(e+=12-(this.start.hasMonth?this.start.getMonth():0),e+=12*(t-1>0?t-1:0)):e+=this.start.hasMonth?1:12,e},a.prototype.getWidth=function(){return this.widthMonth/12*this.getMonths()},a.prototype.getDateLabel=function(){return[(this.start.hasMonth?this.formatMonth(this.start.getMonth()+1)+"/":"")+this.start.getFullYear(),this.end?"-"+((this.end.hasMonth?this.formatMonth(this.end.getMonth()+1)+"/":"")+this.end.getFullYear()):""].join("")};var s=r.createClass({displayName:"TimeSheet",propTypes:{data:r.PropTypes.array.isRequired,min:r.PropTypes.number,max:r.PropTypes.number,theme:r.PropTypes.string},getDefaultProps:function(){return{className:"timesheet"}},parse:function(t,e,r){for(var a=[],s=0,i=t.length;i>s;s++){var o=n(t[s][0]),h=4===t[s].length?n(t[s][1]):null,l=4===t[s].length?t[s][2]:t[s][1],u=t[s][3]||"default";e||(e=o.getFullYear(),r=e),o.getFullYear()r?r=h.getFullYear():o.getFullYear()>r&&(r=o.getFullYear()),a.push({start:o,end:h,label:l,type:u})}return{data:a,year:{min:e,max:r}}},componentDidMount:function(){var t=this.refs.year.getDOMNode().offsetWidth;this.setState({widthMonth:t})},getLists:function(t,e){var n=[],s=this.state&&this.state.widthMonth;if(!s)return n;for(var i=0,o=t.length;o>i;i++){var h=t[i],l=new a(s,e.min,h.start,h.end),u={marginLeft:l.getStartOffset()+"px",width:l.getWidth()+"px"},f="bubble bubble-"+(h.type||"default"),d=h.end?Math.round((h.end-h.start)/1e3/60/60/24/39):"",p=l.getDateLabel(),c=h.label,g=[r.createElement("span",{style:u,className:f,"data-duration":d}),r.createElement("span",{className:"date"},p),r.createElement("span",{className:"label"},c)];n.push(r.createElement("li",null,g))}return n},render:function(){for(var t=this.parse(this.props.data,this.props.min,this.props.max),e=t.data,n=t.year,a=[],s=n.min;s<=n.max;s++)a.push(r.createElement("section",{ref:"year"},s));var i=this.getLists(e,n),o=this.props.className+" "+(this.props.theme||"");return r.createElement("div",{className:o},r.createElement("div",{className:"scale"},a),r.createElement("ul",{className:"data"},i))}});e.exports=s}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)}); -------------------------------------------------------------------------------- /example/dist/app.js: -------------------------------------------------------------------------------- 1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 10 ? num : '0' + num; 21 | }; 22 | 23 | /** 24 | * Calculate starting offset for bubble 25 | */ 26 | Bubble.prototype.getStartOffset = function() { 27 | return (this.widthMonth/12) * (12 * (this.start.getFullYear() - this.min) + this.start.getMonth()); 28 | }; 29 | 30 | /** 31 | * Get count of full years from start to end 32 | */ 33 | Bubble.prototype.getFullYears = function() { 34 | return ((this.end && this.end.getFullYear()) || this.start.getFullYear()) - this.start.getFullYear(); 35 | }; 36 | 37 | /** 38 | * Get count of all months in Timesheet Bubble 39 | */ 40 | Bubble.prototype.getMonths = function() { 41 | var fullYears = this.getFullYears(); 42 | var months = 0; 43 | 44 | if (!this.end) { 45 | months += !this.start.hasMonth ? 12 : 1; 46 | } else { 47 | if (!this.end.hasMonth) { 48 | months += 12 - (this.start.hasMonth ? this.start.getMonth() : 0); 49 | months += 12 * (fullYears-1 > 0 ? fullYears-1 : 0); 50 | } else { 51 | months += this.end.getMonth() + 1; 52 | months += 12 - (this.start.hasMonth ? this.start.getMonth() : 0); 53 | months += 12 * (fullYears-1); 54 | } 55 | } 56 | 57 | return months; 58 | }; 59 | 60 | /** 61 | * Get bubble's width in pixel 62 | */ 63 | Bubble.prototype.getWidth = function() { 64 | return (this.widthMonth/12) * this.getMonths(); 65 | }; 66 | 67 | /** 68 | * Get the bubble's label 69 | */ 70 | Bubble.prototype.getDateLabel = function() { 71 | return [ 72 | (this.start.hasMonth ? this.formatMonth(this.start.getMonth() + 1) + '/' : '' ) + this.start.getFullYear(), 73 | (this.end ? '-' + ((this.end.hasMonth ? this.formatMonth(this.end.getMonth() + 1) + '/' : '' ) + this.end.getFullYear()) : '') 74 | ].join(''); 75 | }; 76 | 77 | /** 78 | * Parse data string 79 | */ 80 | function parseDate(date) { 81 | if (date.indexOf('/') === -1) { 82 | date = new Date(parseInt(date, 10), 0, 1); 83 | date.hasMonth = false; 84 | } else { 85 | date = date.split('/'); 86 | date = new Date(parseInt(date[1], 10), parseInt(date[0], 10)-1, 1); 87 | date.hasMonth = true; 88 | } 89 | 90 | return date; 91 | }; 92 | 93 | 94 | var TimeSheet = React.createClass({displayName: "TimeSheet", 95 | propTypes: { 96 | data: React.PropTypes.array.isRequired, 97 | min: React.PropTypes.number, 98 | max: React.PropTypes.number, 99 | theme: React.PropTypes.string 100 | }, 101 | 102 | getDefaultProps: function(){ 103 | return { 104 | className: 'timesheet' 105 | } 106 | }, 107 | 108 | parse: function(data, min, max) { 109 | var ret = []; 110 | 111 | for (var n = 0, m = data.length; n max) { 127 | max = end.getFullYear(); 128 | } else if (beg.getFullYear() > max) { 129 | max = beg.getFullYear(); 130 | } 131 | 132 | ret.push({start: beg, end: end, label: lbl, type: cat}); 133 | } 134 | 135 | return { 136 | data: ret, 137 | year: { 138 | min: min, 139 | max: max 140 | } 141 | }; 142 | }, 143 | 144 | componentDidMount: function() { 145 | var widthMonth = this.refs.year.getDOMNode().offsetWidth; 146 | 147 | this.setState({ 148 | widthMonth: widthMonth 149 | }); 150 | }, 151 | 152 | getLists: function(data, year){ 153 | 154 | var lists = []; 155 | var widthMonth = this.state && this.state.widthMonth; 156 | if(!widthMonth) return lists; 157 | 158 | for (var i = 0, l = data.length; i < l; i++) { 159 | var cur = data[i]; 160 | var bubble = new Bubble(widthMonth, year.min, cur.start, cur.end); 161 | 162 | var style = { 163 | marginLeft: bubble.getStartOffset() + 'px', 164 | width: bubble.getWidth() + 'px' 165 | }; 166 | 167 | var className = 'bubble bubble-' + (cur.type || 'default'); 168 | var duration = cur.end ? Math.round((cur.end-cur.start)/1000/60/60/24/39) : ''; 169 | var date = bubble.getDateLabel(); 170 | var label = cur.label; 171 | 172 | var line = [ 173 | React.createElement("span", {style: style, className: className, "data-duration": duration}), 174 | React.createElement("span", {className: "date"}, date), 175 | React.createElement("span", {className: "label"}, label) 176 | ]; 177 | 178 | lists.push(React.createElement("li", null, line)); 179 | } 180 | 181 | return lists; 182 | }, 183 | 184 | render: function() { 185 | 186 | var result = this.parse(this.props.data, this.props.min, this.props.max); 187 | var data = result.data; 188 | var year = result.year; 189 | 190 | var sections = []; 191 | for (var c = year.min; c <= year.max; c++) { 192 | sections.push(React.createElement("section", {ref: "year"}, c)); 193 | } 194 | 195 | var lists = this.getLists(data, year); 196 | var className = this.props.className + " " + (this.props.theme || ''); 197 | 198 | return ( 199 | React.createElement("div", {className: className}, 200 | React.createElement("div", {className: "scale"}, 201 | sections 202 | ), 203 | React.createElement("ul", {className: "data"}, 204 | lists 205 | ) 206 | ) 207 | ); 208 | } 209 | }); 210 | 211 | module.exports = TimeSheet; 212 | 213 | },{"react":undefined}]},{},[]); 214 | -------------------------------------------------------------------------------- /example/dist/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | -moz-box-sizing: content-box; 37 | box-sizing: content-box; 38 | } 39 | 40 | .CodeMirror-guttermarker { color: black; } 41 | .CodeMirror-guttermarker-subtle { color: #999; } 42 | 43 | /* CURSOR */ 44 | 45 | .CodeMirror div.CodeMirror-cursor { 46 | border-left: 1px solid black; 47 | } 48 | /* Shown when moving in bi-directional text */ 49 | .CodeMirror div.CodeMirror-secondarycursor { 50 | border-left: 1px solid silver; 51 | } 52 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 53 | width: auto; 54 | border: 0; 55 | background: #7e7; 56 | } 57 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 58 | z-index: 1; 59 | } 60 | 61 | .cm-animate-fat-cursor { 62 | width: auto; 63 | border: 0; 64 | -webkit-animation: blink 1.06s steps(1) infinite; 65 | -moz-animation: blink 1.06s steps(1) infinite; 66 | animation: blink 1.06s steps(1) infinite; 67 | } 68 | @-moz-keyframes blink { 69 | 0% { background: #7e7; } 70 | 50% { background: none; } 71 | 100% { background: #7e7; } 72 | } 73 | @-webkit-keyframes blink { 74 | 0% { background: #7e7; } 75 | 50% { background: none; } 76 | 100% { background: #7e7; } 77 | } 78 | @keyframes blink { 79 | 0% { background: #7e7; } 80 | 50% { background: none; } 81 | 100% { background: #7e7; } 82 | } 83 | 84 | /* Can style cursor different in overwrite (non-insert) mode */ 85 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 86 | 87 | .cm-tab { display: inline-block; text-decoration: inherit; } 88 | 89 | .CodeMirror-ruler { 90 | border-left: 1px solid #ccc; 91 | position: absolute; 92 | } 93 | 94 | /* DEFAULT THEME */ 95 | 96 | .cm-s-default .cm-keyword {color: #708;} 97 | .cm-s-default .cm-atom {color: #219;} 98 | .cm-s-default .cm-number {color: #164;} 99 | .cm-s-default .cm-def {color: #00f;} 100 | .cm-s-default .cm-variable, 101 | .cm-s-default .cm-punctuation, 102 | .cm-s-default .cm-property, 103 | .cm-s-default .cm-operator {} 104 | .cm-s-default .cm-variable-2 {color: #05a;} 105 | .cm-s-default .cm-variable-3 {color: #085;} 106 | .cm-s-default .cm-comment {color: #a50;} 107 | .cm-s-default .cm-string {color: #a11;} 108 | .cm-s-default .cm-string-2 {color: #f50;} 109 | .cm-s-default .cm-meta {color: #555;} 110 | .cm-s-default .cm-qualifier {color: #555;} 111 | .cm-s-default .cm-builtin {color: #30a;} 112 | .cm-s-default .cm-bracket {color: #997;} 113 | .cm-s-default .cm-tag {color: #170;} 114 | .cm-s-default .cm-attribute {color: #00c;} 115 | .cm-s-default .cm-header {color: blue;} 116 | .cm-s-default .cm-quote {color: #090;} 117 | .cm-s-default .cm-hr {color: #999;} 118 | .cm-s-default .cm-link {color: #00c;} 119 | 120 | .cm-negative {color: #d44;} 121 | .cm-positive {color: #292;} 122 | .cm-header, .cm-strong {font-weight: bold;} 123 | .cm-em {font-style: italic;} 124 | .cm-link {text-decoration: underline;} 125 | .cm-strikethrough {text-decoration: line-through;} 126 | 127 | .cm-s-default .cm-error {color: #f00;} 128 | .cm-invalidchar {color: #f00;} 129 | 130 | /* Default styles for common addons */ 131 | 132 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 133 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 134 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 135 | .CodeMirror-activeline-background {background: #e8f2ff;} 136 | 137 | /* STOP */ 138 | 139 | /* The rest of this file contains styles related to the mechanics of 140 | the editor. You probably shouldn't touch them. */ 141 | 142 | .CodeMirror { 143 | position: relative; 144 | overflow: hidden; 145 | background: white; 146 | } 147 | 148 | .CodeMirror-scroll { 149 | overflow: scroll !important; /* Things will break if this is overridden */ 150 | /* 30px is the magic margin used to hide the element's real scrollbars */ 151 | /* See overflow: hidden in .CodeMirror */ 152 | margin-bottom: -30px; margin-right: -30px; 153 | padding-bottom: 30px; 154 | height: 100%; 155 | outline: none; /* Prevent dragging from highlighting the element */ 156 | position: relative; 157 | -moz-box-sizing: content-box; 158 | box-sizing: content-box; 159 | } 160 | .CodeMirror-sizer { 161 | position: relative; 162 | border-right: 30px solid transparent; 163 | -moz-box-sizing: content-box; 164 | box-sizing: content-box; 165 | } 166 | 167 | /* The fake, visible scrollbars. Used to force redraw during scrolling 168 | before actuall scrolling happens, thus preventing shaking and 169 | flickering artifacts. */ 170 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 171 | position: absolute; 172 | z-index: 6; 173 | display: none; 174 | } 175 | .CodeMirror-vscrollbar { 176 | right: 0; top: 0; 177 | overflow-x: hidden; 178 | overflow-y: scroll; 179 | } 180 | .CodeMirror-hscrollbar { 181 | bottom: 0; left: 0; 182 | overflow-y: hidden; 183 | overflow-x: scroll; 184 | } 185 | .CodeMirror-scrollbar-filler { 186 | right: 0; bottom: 0; 187 | } 188 | .CodeMirror-gutter-filler { 189 | left: 0; bottom: 0; 190 | } 191 | 192 | .CodeMirror-gutters { 193 | position: absolute; left: 0; top: 0; 194 | z-index: 3; 195 | } 196 | .CodeMirror-gutter { 197 | white-space: normal; 198 | height: 100%; 199 | -moz-box-sizing: content-box; 200 | box-sizing: content-box; 201 | display: inline-block; 202 | margin-bottom: -30px; 203 | /* Hack to make IE7 behave */ 204 | *zoom:1; 205 | *display:inline; 206 | } 207 | .CodeMirror-gutter-wrapper { 208 | position: absolute; 209 | z-index: 4; 210 | height: 100%; 211 | } 212 | .CodeMirror-gutter-elt { 213 | position: absolute; 214 | cursor: default; 215 | z-index: 4; 216 | } 217 | .CodeMirror-gutter-wrapper { 218 | -webkit-user-select: none; 219 | -moz-user-select: none; 220 | user-select: none; 221 | } 222 | 223 | .CodeMirror-lines { 224 | cursor: text; 225 | min-height: 1px; /* prevents collapsing before first draw */ 226 | } 227 | .CodeMirror pre { 228 | /* Reset some styles that the rest of the page might have set */ 229 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 230 | border-width: 0; 231 | background: transparent; 232 | font-family: inherit; 233 | font-size: inherit; 234 | margin: 0; 235 | white-space: pre; 236 | word-wrap: normal; 237 | line-height: inherit; 238 | color: inherit; 239 | z-index: 2; 240 | position: relative; 241 | overflow: visible; 242 | -webkit-tap-highlight-color: transparent; 243 | } 244 | .CodeMirror-wrap pre { 245 | word-wrap: break-word; 246 | white-space: pre-wrap; 247 | word-break: normal; 248 | } 249 | 250 | .CodeMirror-linebackground { 251 | position: absolute; 252 | left: 0; right: 0; top: 0; bottom: 0; 253 | z-index: 0; 254 | } 255 | 256 | .CodeMirror-linewidget { 257 | position: relative; 258 | z-index: 2; 259 | overflow: auto; 260 | } 261 | 262 | .CodeMirror-widget {} 263 | 264 | .CodeMirror-code { 265 | outline: none; 266 | } 267 | 268 | .CodeMirror-measure { 269 | position: absolute; 270 | width: 100%; 271 | height: 0; 272 | overflow: hidden; 273 | visibility: hidden; 274 | } 275 | .CodeMirror-measure pre { position: static; } 276 | 277 | .CodeMirror div.CodeMirror-cursor { 278 | position: absolute; 279 | border-right: none; 280 | width: 0; 281 | } 282 | 283 | div.CodeMirror-cursors { 284 | visibility: hidden; 285 | position: relative; 286 | z-index: 3; 287 | } 288 | .CodeMirror-focused div.CodeMirror-cursors { 289 | visibility: visible; 290 | } 291 | 292 | .CodeMirror-selected { background: #d9d9d9; } 293 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 294 | .CodeMirror-crosshair { cursor: crosshair; } 295 | .CodeMirror ::selection { background: #d7d4f0; } 296 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 297 | 298 | .cm-searching { 299 | background: #ffa; 300 | background: rgba(255, 255, 0, .4); 301 | } 302 | 303 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 304 | .CodeMirror span { *vertical-align: text-bottom; } 305 | 306 | /* Used to force a border model for a node */ 307 | .cm-force-border { padding-right: .1px; } 308 | 309 | @media print { 310 | /* Hide the cursor when printing */ 311 | .CodeMirror div.CodeMirror-cursors { 312 | visibility: hidden; 313 | } 314 | } 315 | 316 | /* See issue #2901 */ 317 | .cm-tab-wrap-hack:after { content: ''; } 318 | 319 | /* Help users use markselection to safely style text background */ 320 | span.CodeMirror-selectedtext { background: none; } 321 | -------------------------------------------------------------------------------- /example/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 35 | 61 | 107 | 108 | 109 |

Live Demo

110 |

Default Theme Time Sheet

111 |
112 |

White Theme Time Sheet

113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /example/dist/javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // TODO actually recognize syntax of TypeScript constructs 5 | 6 | (function(mod) { 7 | if (typeof exports == "object" && typeof module == "object") // CommonJS 8 | mod(require("../../lib/codemirror")); 9 | else if (typeof define == "function" && define.amd) // AMD 10 | define(["../../lib/codemirror"], mod); 11 | else // Plain browser env 12 | mod(CodeMirror); 13 | })(function(CodeMirror) { 14 | "use strict"; 15 | 16 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 17 | var indentUnit = config.indentUnit; 18 | var statementIndent = parserConfig.statementIndent; 19 | var jsonldMode = parserConfig.jsonld; 20 | var jsonMode = parserConfig.json || jsonldMode; 21 | var isTS = parserConfig.typescript; 22 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 23 | 24 | // Tokenizer 25 | 26 | var keywords = function(){ 27 | function kw(type) {return {type: type, style: "keyword"};} 28 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 29 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 30 | 31 | var jsKeywords = { 32 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 33 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C, 34 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 35 | "function": kw("function"), "catch": kw("catch"), 36 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 37 | "in": operator, "typeof": operator, "instanceof": operator, 38 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 39 | "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"), 40 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C 41 | }; 42 | 43 | // Extend the 'normal' keywords with the TypeScript language extensions 44 | if (isTS) { 45 | var type = {type: "variable", style: "variable-3"}; 46 | var tsKeywords = { 47 | // object-like things 48 | "interface": kw("interface"), 49 | "extends": kw("extends"), 50 | "constructor": kw("constructor"), 51 | 52 | // scope modifiers 53 | "public": kw("public"), 54 | "private": kw("private"), 55 | "protected": kw("protected"), 56 | "static": kw("static"), 57 | 58 | // types 59 | "string": type, "number": type, "bool": type, "any": type 60 | }; 61 | 62 | for (var attr in tsKeywords) { 63 | jsKeywords[attr] = tsKeywords[attr]; 64 | } 65 | } 66 | 67 | return jsKeywords; 68 | }(); 69 | 70 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 71 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 72 | 73 | function readRegexp(stream) { 74 | var escaped = false, next, inSet = false; 75 | while ((next = stream.next()) != null) { 76 | if (!escaped) { 77 | if (next == "/" && !inSet) return; 78 | if (next == "[") inSet = true; 79 | else if (inSet && next == "]") inSet = false; 80 | } 81 | escaped = !escaped && next == "\\"; 82 | } 83 | } 84 | 85 | // Used as scratch variables to communicate multiple values without 86 | // consing up tons of objects. 87 | var type, content; 88 | function ret(tp, style, cont) { 89 | type = tp; content = cont; 90 | return style; 91 | } 92 | function tokenBase(stream, state) { 93 | var ch = stream.next(); 94 | if (ch == '"' || ch == "'") { 95 | state.tokenize = tokenString(ch); 96 | return state.tokenize(stream, state); 97 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 98 | return ret("number", "number"); 99 | } else if (ch == "." && stream.match("..")) { 100 | return ret("spread", "meta"); 101 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 102 | return ret(ch); 103 | } else if (ch == "=" && stream.eat(">")) { 104 | return ret("=>", "operator"); 105 | } else if (ch == "0" && stream.eat(/x/i)) { 106 | stream.eatWhile(/[\da-f]/i); 107 | return ret("number", "number"); 108 | } else if (/\d/.test(ch)) { 109 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 110 | return ret("number", "number"); 111 | } else if (ch == "/") { 112 | if (stream.eat("*")) { 113 | state.tokenize = tokenComment; 114 | return tokenComment(stream, state); 115 | } else if (stream.eat("/")) { 116 | stream.skipToEnd(); 117 | return ret("comment", "comment"); 118 | } else if (state.lastType == "operator" || state.lastType == "keyword c" || 119 | state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { 120 | readRegexp(stream); 121 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 122 | return ret("regexp", "string-2"); 123 | } else { 124 | stream.eatWhile(isOperatorChar); 125 | return ret("operator", "operator", stream.current()); 126 | } 127 | } else if (ch == "`") { 128 | state.tokenize = tokenQuasi; 129 | return tokenQuasi(stream, state); 130 | } else if (ch == "#") { 131 | stream.skipToEnd(); 132 | return ret("error", "error"); 133 | } else if (isOperatorChar.test(ch)) { 134 | stream.eatWhile(isOperatorChar); 135 | return ret("operator", "operator", stream.current()); 136 | } else if (wordRE.test(ch)) { 137 | stream.eatWhile(wordRE); 138 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 139 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 140 | ret("variable", "variable", word); 141 | } 142 | } 143 | 144 | function tokenString(quote) { 145 | return function(stream, state) { 146 | var escaped = false, next; 147 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 148 | state.tokenize = tokenBase; 149 | return ret("jsonld-keyword", "meta"); 150 | } 151 | while ((next = stream.next()) != null) { 152 | if (next == quote && !escaped) break; 153 | escaped = !escaped && next == "\\"; 154 | } 155 | if (!escaped) state.tokenize = tokenBase; 156 | return ret("string", "string"); 157 | }; 158 | } 159 | 160 | function tokenComment(stream, state) { 161 | var maybeEnd = false, ch; 162 | while (ch = stream.next()) { 163 | if (ch == "/" && maybeEnd) { 164 | state.tokenize = tokenBase; 165 | break; 166 | } 167 | maybeEnd = (ch == "*"); 168 | } 169 | return ret("comment", "comment"); 170 | } 171 | 172 | function tokenQuasi(stream, state) { 173 | var escaped = false, next; 174 | while ((next = stream.next()) != null) { 175 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 176 | state.tokenize = tokenBase; 177 | break; 178 | } 179 | escaped = !escaped && next == "\\"; 180 | } 181 | return ret("quasi", "string-2", stream.current()); 182 | } 183 | 184 | var brackets = "([{}])"; 185 | // This is a crude lookahead trick to try and notice that we're 186 | // parsing the argument patterns for a fat-arrow function before we 187 | // actually hit the arrow token. It only works if the arrow is on 188 | // the same line as the arguments and there's no strange noise 189 | // (comments) in between. Fallback is to only notice when we hit the 190 | // arrow, and not declare the arguments as locals for the arrow 191 | // body. 192 | function findFatArrow(stream, state) { 193 | if (state.fatArrowAt) state.fatArrowAt = null; 194 | var arrow = stream.string.indexOf("=>", stream.start); 195 | if (arrow < 0) return; 196 | 197 | var depth = 0, sawSomething = false; 198 | for (var pos = arrow - 1; pos >= 0; --pos) { 199 | var ch = stream.string.charAt(pos); 200 | var bracket = brackets.indexOf(ch); 201 | if (bracket >= 0 && bracket < 3) { 202 | if (!depth) { ++pos; break; } 203 | if (--depth == 0) break; 204 | } else if (bracket >= 3 && bracket < 6) { 205 | ++depth; 206 | } else if (wordRE.test(ch)) { 207 | sawSomething = true; 208 | } else if (/["'\/]/.test(ch)) { 209 | return; 210 | } else if (sawSomething && !depth) { 211 | ++pos; 212 | break; 213 | } 214 | } 215 | if (sawSomething && !depth) state.fatArrowAt = pos; 216 | } 217 | 218 | // Parser 219 | 220 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 221 | 222 | function JSLexical(indented, column, type, align, prev, info) { 223 | this.indented = indented; 224 | this.column = column; 225 | this.type = type; 226 | this.prev = prev; 227 | this.info = info; 228 | if (align != null) this.align = align; 229 | } 230 | 231 | function inScope(state, varname) { 232 | for (var v = state.localVars; v; v = v.next) 233 | if (v.name == varname) return true; 234 | for (var cx = state.context; cx; cx = cx.prev) { 235 | for (var v = cx.vars; v; v = v.next) 236 | if (v.name == varname) return true; 237 | } 238 | } 239 | 240 | function parseJS(state, style, type, content, stream) { 241 | var cc = state.cc; 242 | // Communicate our context to the combinators. 243 | // (Less wasteful than consing up a hundred closures on every call.) 244 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 245 | 246 | if (!state.lexical.hasOwnProperty("align")) 247 | state.lexical.align = true; 248 | 249 | while(true) { 250 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 251 | if (combinator(type, content)) { 252 | while(cc.length && cc[cc.length - 1].lex) 253 | cc.pop()(); 254 | if (cx.marked) return cx.marked; 255 | if (type == "variable" && inScope(state, content)) return "variable-2"; 256 | return style; 257 | } 258 | } 259 | } 260 | 261 | // Combinator utils 262 | 263 | var cx = {state: null, column: null, marked: null, cc: null}; 264 | function pass() { 265 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 266 | } 267 | function cont() { 268 | pass.apply(null, arguments); 269 | return true; 270 | } 271 | function register(varname) { 272 | function inList(list) { 273 | for (var v = list; v; v = v.next) 274 | if (v.name == varname) return true; 275 | return false; 276 | } 277 | var state = cx.state; 278 | if (state.context) { 279 | cx.marked = "def"; 280 | if (inList(state.localVars)) return; 281 | state.localVars = {name: varname, next: state.localVars}; 282 | } else { 283 | if (inList(state.globalVars)) return; 284 | if (parserConfig.globalVars) 285 | state.globalVars = {name: varname, next: state.globalVars}; 286 | } 287 | } 288 | 289 | // Combinators 290 | 291 | var defaultVars = {name: "this", next: {name: "arguments"}}; 292 | function pushcontext() { 293 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 294 | cx.state.localVars = defaultVars; 295 | } 296 | function popcontext() { 297 | cx.state.localVars = cx.state.context.vars; 298 | cx.state.context = cx.state.context.prev; 299 | } 300 | function pushlex(type, info) { 301 | var result = function() { 302 | var state = cx.state, indent = state.indented; 303 | if (state.lexical.type == "stat") indent = state.lexical.indented; 304 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 305 | indent = outer.indented; 306 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 307 | }; 308 | result.lex = true; 309 | return result; 310 | } 311 | function poplex() { 312 | var state = cx.state; 313 | if (state.lexical.prev) { 314 | if (state.lexical.type == ")") 315 | state.indented = state.lexical.indented; 316 | state.lexical = state.lexical.prev; 317 | } 318 | } 319 | poplex.lex = true; 320 | 321 | function expect(wanted) { 322 | function exp(type) { 323 | if (type == wanted) return cont(); 324 | else if (wanted == ";") return pass(); 325 | else return cont(exp); 326 | }; 327 | return exp; 328 | } 329 | 330 | function statement(type, value) { 331 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 332 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 333 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 334 | if (type == "{") return cont(pushlex("}"), block, poplex); 335 | if (type == ";") return cont(); 336 | if (type == "if") { 337 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 338 | cx.state.cc.pop()(); 339 | return cont(pushlex("form"), expression, statement, poplex, maybeelse); 340 | } 341 | if (type == "function") return cont(functiondef); 342 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 343 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 344 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 345 | block, poplex, poplex); 346 | if (type == "case") return cont(expression, expect(":")); 347 | if (type == "default") return cont(expect(":")); 348 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 349 | statement, poplex, popcontext); 350 | if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex); 351 | if (type == "class") return cont(pushlex("form"), className, poplex); 352 | if (type == "export") return cont(pushlex("form"), afterExport, poplex); 353 | if (type == "import") return cont(pushlex("form"), afterImport, poplex); 354 | return pass(pushlex("stat"), expression, expect(";"), poplex); 355 | } 356 | function expression(type) { 357 | return expressionInner(type, false); 358 | } 359 | function expressionNoComma(type) { 360 | return expressionInner(type, true); 361 | } 362 | function expressionInner(type, noComma) { 363 | if (cx.state.fatArrowAt == cx.stream.start) { 364 | var body = noComma ? arrowBodyNoComma : arrowBody; 365 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); 366 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 367 | } 368 | 369 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 370 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 371 | if (type == "function") return cont(functiondef, maybeop); 372 | if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); 373 | if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); 374 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 375 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 376 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 377 | if (type == "quasi") { return pass(quasi, maybeop); } 378 | return cont(); 379 | } 380 | function maybeexpression(type) { 381 | if (type.match(/[;\}\)\],]/)) return pass(); 382 | return pass(expression); 383 | } 384 | function maybeexpressionNoComma(type) { 385 | if (type.match(/[;\}\)\],]/)) return pass(); 386 | return pass(expressionNoComma); 387 | } 388 | 389 | function maybeoperatorComma(type, value) { 390 | if (type == ",") return cont(expression); 391 | return maybeoperatorNoComma(type, value, false); 392 | } 393 | function maybeoperatorNoComma(type, value, noComma) { 394 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 395 | var expr = noComma == false ? expression : expressionNoComma; 396 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 397 | if (type == "operator") { 398 | if (/\+\+|--/.test(value)) return cont(me); 399 | if (value == "?") return cont(expression, expect(":"), expr); 400 | return cont(expr); 401 | } 402 | if (type == "quasi") { return pass(quasi, me); } 403 | if (type == ";") return; 404 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 405 | if (type == ".") return cont(property, me); 406 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 407 | } 408 | function quasi(type, value) { 409 | if (type != "quasi") return pass(); 410 | if (value.slice(value.length - 2) != "${") return cont(quasi); 411 | return cont(expression, continueQuasi); 412 | } 413 | function continueQuasi(type) { 414 | if (type == "}") { 415 | cx.marked = "string-2"; 416 | cx.state.tokenize = tokenQuasi; 417 | return cont(quasi); 418 | } 419 | } 420 | function arrowBody(type) { 421 | findFatArrow(cx.stream, cx.state); 422 | return pass(type == "{" ? statement : expression); 423 | } 424 | function arrowBodyNoComma(type) { 425 | findFatArrow(cx.stream, cx.state); 426 | return pass(type == "{" ? statement : expressionNoComma); 427 | } 428 | function maybelabel(type) { 429 | if (type == ":") return cont(poplex, statement); 430 | return pass(maybeoperatorComma, expect(";"), poplex); 431 | } 432 | function property(type) { 433 | if (type == "variable") {cx.marked = "property"; return cont();} 434 | } 435 | function objprop(type, value) { 436 | if (type == "variable" || cx.style == "keyword") { 437 | cx.marked = "property"; 438 | if (value == "get" || value == "set") return cont(getterSetter); 439 | return cont(afterprop); 440 | } else if (type == "number" || type == "string") { 441 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 442 | return cont(afterprop); 443 | } else if (type == "jsonld-keyword") { 444 | return cont(afterprop); 445 | } else if (type == "[") { 446 | return cont(expression, expect("]"), afterprop); 447 | } 448 | } 449 | function getterSetter(type) { 450 | if (type != "variable") return pass(afterprop); 451 | cx.marked = "property"; 452 | return cont(functiondef); 453 | } 454 | function afterprop(type) { 455 | if (type == ":") return cont(expressionNoComma); 456 | if (type == "(") return pass(functiondef); 457 | } 458 | function commasep(what, end) { 459 | function proceed(type) { 460 | if (type == ",") { 461 | var lex = cx.state.lexical; 462 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 463 | return cont(what, proceed); 464 | } 465 | if (type == end) return cont(); 466 | return cont(expect(end)); 467 | } 468 | return function(type) { 469 | if (type == end) return cont(); 470 | return pass(what, proceed); 471 | }; 472 | } 473 | function contCommasep(what, end, info) { 474 | for (var i = 3; i < arguments.length; i++) 475 | cx.cc.push(arguments[i]); 476 | return cont(pushlex(end, info), commasep(what, end), poplex); 477 | } 478 | function block(type) { 479 | if (type == "}") return cont(); 480 | return pass(statement, block); 481 | } 482 | function maybetype(type) { 483 | if (isTS && type == ":") return cont(typedef); 484 | } 485 | function typedef(type) { 486 | if (type == "variable"){cx.marked = "variable-3"; return cont();} 487 | } 488 | function vardef() { 489 | return pass(pattern, maybetype, maybeAssign, vardefCont); 490 | } 491 | function pattern(type, value) { 492 | if (type == "variable") { register(value); return cont(); } 493 | if (type == "[") return contCommasep(pattern, "]"); 494 | if (type == "{") return contCommasep(proppattern, "}"); 495 | } 496 | function proppattern(type, value) { 497 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 498 | register(value); 499 | return cont(maybeAssign); 500 | } 501 | if (type == "variable") cx.marked = "property"; 502 | return cont(expect(":"), pattern, maybeAssign); 503 | } 504 | function maybeAssign(_type, value) { 505 | if (value == "=") return cont(expressionNoComma); 506 | } 507 | function vardefCont(type) { 508 | if (type == ",") return cont(vardef); 509 | } 510 | function maybeelse(type, value) { 511 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 512 | } 513 | function forspec(type) { 514 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 515 | } 516 | function forspec1(type) { 517 | if (type == "var") return cont(vardef, expect(";"), forspec2); 518 | if (type == ";") return cont(forspec2); 519 | if (type == "variable") return cont(formaybeinof); 520 | return pass(expression, expect(";"), forspec2); 521 | } 522 | function formaybeinof(_type, value) { 523 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 524 | return cont(maybeoperatorComma, forspec2); 525 | } 526 | function forspec2(type, value) { 527 | if (type == ";") return cont(forspec3); 528 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 529 | return pass(expression, expect(";"), forspec3); 530 | } 531 | function forspec3(type) { 532 | if (type != ")") cont(expression); 533 | } 534 | function functiondef(type, value) { 535 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 536 | if (type == "variable") {register(value); return cont(functiondef);} 537 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); 538 | } 539 | function funarg(type) { 540 | if (type == "spread") return cont(funarg); 541 | return pass(pattern, maybetype); 542 | } 543 | function className(type, value) { 544 | if (type == "variable") {register(value); return cont(classNameAfter);} 545 | } 546 | function classNameAfter(type, value) { 547 | if (value == "extends") return cont(expression, classNameAfter); 548 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 549 | } 550 | function classBody(type, value) { 551 | if (type == "variable" || cx.style == "keyword") { 552 | cx.marked = "property"; 553 | if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); 554 | return cont(functiondef, classBody); 555 | } 556 | if (value == "*") { 557 | cx.marked = "keyword"; 558 | return cont(classBody); 559 | } 560 | if (type == ";") return cont(classBody); 561 | if (type == "}") return cont(); 562 | } 563 | function classGetterSetter(type) { 564 | if (type != "variable") return pass(); 565 | cx.marked = "property"; 566 | return cont(); 567 | } 568 | function afterModule(type, value) { 569 | if (type == "string") return cont(statement); 570 | if (type == "variable") { register(value); return cont(maybeFrom); } 571 | } 572 | function afterExport(_type, value) { 573 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 574 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 575 | return pass(statement); 576 | } 577 | function afterImport(type) { 578 | if (type == "string") return cont(); 579 | return pass(importSpec, maybeFrom); 580 | } 581 | function importSpec(type, value) { 582 | if (type == "{") return contCommasep(importSpec, "}"); 583 | if (type == "variable") register(value); 584 | return cont(); 585 | } 586 | function maybeFrom(_type, value) { 587 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 588 | } 589 | function arrayLiteral(type) { 590 | if (type == "]") return cont(); 591 | return pass(expressionNoComma, maybeArrayComprehension); 592 | } 593 | function maybeArrayComprehension(type) { 594 | if (type == "for") return pass(comprehension, expect("]")); 595 | if (type == ",") return cont(commasep(maybeexpressionNoComma, "]")); 596 | return pass(commasep(expressionNoComma, "]")); 597 | } 598 | function comprehension(type) { 599 | if (type == "for") return cont(forspec, comprehension); 600 | if (type == "if") return cont(expression, comprehension); 601 | } 602 | 603 | function isContinuedStatement(state, textAfter) { 604 | return state.lastType == "operator" || state.lastType == "," || 605 | isOperatorChar.test(textAfter.charAt(0)) || 606 | /[,.]/.test(textAfter.charAt(0)); 607 | } 608 | 609 | // Interface 610 | 611 | return { 612 | startState: function(basecolumn) { 613 | var state = { 614 | tokenize: tokenBase, 615 | lastType: "sof", 616 | cc: [], 617 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 618 | localVars: parserConfig.localVars, 619 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 620 | indented: 0 621 | }; 622 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 623 | state.globalVars = parserConfig.globalVars; 624 | return state; 625 | }, 626 | 627 | token: function(stream, state) { 628 | if (stream.sol()) { 629 | if (!state.lexical.hasOwnProperty("align")) 630 | state.lexical.align = false; 631 | state.indented = stream.indentation(); 632 | findFatArrow(stream, state); 633 | } 634 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 635 | var style = state.tokenize(stream, state); 636 | if (type == "comment") return style; 637 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 638 | return parseJS(state, style, type, content, stream); 639 | }, 640 | 641 | indent: function(state, textAfter) { 642 | if (state.tokenize == tokenComment) return CodeMirror.Pass; 643 | if (state.tokenize != tokenBase) return 0; 644 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 645 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 646 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 647 | var c = state.cc[i]; 648 | if (c == poplex) lexical = lexical.prev; 649 | else if (c != maybeelse) break; 650 | } 651 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 652 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 653 | lexical = lexical.prev; 654 | var type = lexical.type, closing = firstChar == type; 655 | 656 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 657 | else if (type == "form" && firstChar == "{") return lexical.indented; 658 | else if (type == "form") return lexical.indented + indentUnit; 659 | else if (type == "stat") 660 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 661 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 662 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 663 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 664 | else return lexical.indented + (closing ? 0 : indentUnit); 665 | }, 666 | 667 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 668 | blockCommentStart: jsonMode ? null : "/*", 669 | blockCommentEnd: jsonMode ? null : "*/", 670 | lineComment: jsonMode ? null : "//", 671 | fold: "brace", 672 | 673 | helperType: jsonMode ? "json" : "javascript", 674 | jsonldMode: jsonldMode, 675 | jsonMode: jsonMode 676 | }; 677 | }); 678 | 679 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 680 | 681 | CodeMirror.defineMIME("text/javascript", "javascript"); 682 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 683 | CodeMirror.defineMIME("application/javascript", "javascript"); 684 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 685 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 686 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 687 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 688 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 689 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 690 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 691 | 692 | }); 693 | -------------------------------------------------------------------------------- /example/dist/timesheet-white.css: -------------------------------------------------------------------------------- 1 | .timesheet.white { 2 | border-top: 1px solid rgba(60, 60, 60, 0.3); 3 | background-color: #fbfbfb; 4 | position: relative; } 5 | .timesheet.white .scale { 6 | height: 100%; 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | float: left; } 11 | .timesheet.white .scale section { 12 | float: left; 13 | width: 59px; 14 | text-align: center; 15 | color: rgba(50, 50, 50, 0.8); 16 | font-family: "Signika Negative"; 17 | font-size: 13px; 18 | line-height: 24px; 19 | font-weight: 300; 20 | border-left: 1px dashed rgba(50, 50, 50, 0.1); 21 | height: 100%; } 22 | .timesheet.white .data { 23 | margin: 28px 0 0 0; 24 | padding: 0; 25 | text-align: left; 26 | list-style-type: none; 27 | color: rgba(250, 250, 250, 0.8); 28 | font-family: "Signika Negative"; 29 | font-size: 13px; 30 | overflow: hidden; } 31 | .timesheet.white .data li { 32 | margin: 0 0 3px 0; 33 | line-height: 22px; 34 | height: 21px; 35 | display: block; 36 | cursor: pointer; 37 | clear: both; 38 | position: relative; 39 | white-space: nowrap; } 40 | .timesheet.white .data li:hover .bubble { 41 | opacity: 1; } 42 | .timesheet.white .data li .date { 43 | color: #797979; 44 | font-size: 14px; } 45 | .timesheet.white .data li .label { 46 | font-weight: lighter; 47 | font-size: 14px; 48 | padding-left: 5px; 49 | line-height: 21px; 50 | color: #333332; 51 | white-space: nowrap; } 52 | .timesheet.white .data li .bubble { 53 | width: 24px; 54 | height: 7px; 55 | display: block; 56 | float: left; 57 | position: relative; 58 | top: 7px; 59 | border-radius: 4px; 60 | margin: 0 10px 0 0; 61 | opacity: 0.7; } 62 | .timesheet.white .data li .bubble-default { 63 | background-color: #fc464a; } 64 | .timesheet.white .data li .bubble-lorem { 65 | background-color: #9aca27; } 66 | .timesheet.white .data li .bubble-ipsum { 67 | background-color: #3cb6e3; } 68 | .timesheet.white .data li .bubble-dolor { 69 | background-color: #f4cf30; } 70 | .timesheet.white .data li .bubble-sit { 71 | background-color: #a969ca; } 72 | -------------------------------------------------------------------------------- /example/dist/timesheet.css: -------------------------------------------------------------------------------- 1 | .timesheet { 2 | border-top: 1px solid rgba(250, 250, 250, 0.5); 3 | background-color: #333333; 4 | position: relative; } 5 | .timesheet .scale { 6 | height: 100%; 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | float: left; } 11 | .timesheet .scale section { 12 | float: left; 13 | width: 59px; 14 | text-align: center; 15 | color: rgba(250, 250, 250, 0.8); 16 | font-family: "Signika Negative"; 17 | font-size: 13px; 18 | line-height: 24px; 19 | font-weight: lighter; 20 | border-left: 1px dashed rgba(250, 250, 250, 0.2); 21 | height: 100%; } 22 | .timesheet .data { 23 | margin: 28px 0 0 0; 24 | padding: 0; 25 | text-align: left; 26 | list-style-type: none; 27 | color: rgba(250, 250, 250, 0.8); 28 | font-family: "Signika Negative"; 29 | font-size: 13px; 30 | overflow: hidden; } 31 | .timesheet .data li { 32 | margin: 0 0 3px 0; 33 | line-height: 22px; 34 | height: 21px; 35 | display: block; 36 | cursor: pointer; 37 | clear: both; 38 | position: relative; 39 | white-space: nowrap; } 40 | .timesheet .data li:hover .bubble { 41 | opacity: 1; } 42 | .timesheet .data li .date { 43 | color: #b5b5b5; 44 | font-size: 14px; } 45 | .timesheet .data li .label { 46 | font-weight: lighter; 47 | font-size: 14px; 48 | padding-left: 5px; 49 | line-height: 21px; 50 | color: #979796; 51 | white-space: nowrap; } 52 | .timesheet .data li .bubble { 53 | width: 24px; 54 | height: 7px; 55 | display: block; 56 | float: left; 57 | position: relative; 58 | top: 7px; 59 | border-radius: 4px; 60 | margin: 0 10px 0 0; 61 | opacity: 0.7; } 62 | .timesheet .data li .bubble-default { 63 | background-color: #fc464a; } 64 | .timesheet .data li .bubble-lorem { 65 | background-color: #9aca27; } 66 | .timesheet .data li .bubble-ipsum { 67 | background-color: #3cb6e3; } 68 | .timesheet .data li .bubble-dolor { 69 | background-color: #f4cf30; } 70 | .timesheet .data li .bubble-sit { 71 | background-color: #a969ca; } 72 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var selfCleaningTimeout = { 4 | componentDidUpdate: function() { 5 | clearTimeout(this.timeoutID); 6 | }, 7 | 8 | setTimeout: function() { 9 | clearTimeout(this.timeoutID); 10 | this.timeoutID = setTimeout.apply(null, arguments); 11 | } 12 | }; 13 | 14 | var ComponentPreview = React.createClass({ 15 | propTypes: { 16 | code: React.PropTypes.string.isRequired 17 | }, 18 | 19 | mixins: [selfCleaningTimeout], 20 | 21 | render: function() { 22 | return
; 23 | }, 24 | 25 | componentDidMount: function() { 26 | this.executeCode(); 27 | }, 28 | 29 | componentDidUpdate: function(prevProps) { 30 | // execute code only when the state's not being updated by switching tab 31 | // this avoids re-displaying the error, which comes after a certain delay 32 | if (this.props.code !== prevProps.code) { 33 | this.executeCode(); 34 | } 35 | }, 36 | 37 | compileCode: function() { 38 | return JSXTransformer.transform( 39 | '(function() {' + 40 | this.props.code + 41 | '\n})();', 42 | { harmony: true } 43 | ).code; 44 | }, 45 | 46 | executeCode: function() { 47 | var mountNode = this.refs.mount.getDOMNode(); 48 | 49 | try { 50 | React.unmountComponentAtNode(mountNode); 51 | } catch (e) { } 52 | 53 | try { 54 | var compiledCode = this.compileCode(); 55 | React.render(eval(compiledCode), mountNode); 56 | } catch (err) { 57 | this.setTimeout(function() { 58 | React.render( 59 |
{err.toString()}
, 60 | mountNode 61 | ); 62 | }, 500); 63 | } 64 | } 65 | }); 66 | 67 | var IS_MOBILE = ( 68 | navigator.userAgent.match(/Android/i) 69 | || navigator.userAgent.match(/webOS/i) 70 | || navigator.userAgent.match(/iPhone/i) 71 | || navigator.userAgent.match(/iPad/i) 72 | || navigator.userAgent.match(/iPod/i) 73 | || navigator.userAgent.match(/BlackBerry/i) 74 | || navigator.userAgent.match(/Windows Phone/i) 75 | ); 76 | 77 | var CodeMirrorEditor = React.createClass({ 78 | componentDidMount: function() { 79 | if (IS_MOBILE) return; 80 | 81 | this.editor = CodeMirror.fromTextArea(this.refs.editor.getDOMNode(), { 82 | mode: 'javascript', 83 | lineNumbers: true, 84 | lineWrapping: true, 85 | smartIndent: false, // javascript mode does bad things with jsx indents 86 | matchBrackets: true, 87 | readOnly: this.props.readOnly 88 | }); 89 | this.editor.on('change', this.handleChange); 90 | }, 91 | 92 | componentDidUpdate: function() { 93 | if (this.props.readOnly) { 94 | this.editor.setValue(this.props.codeText); 95 | } 96 | }, 97 | 98 | handleChange: function() { 99 | if (!this.props.readOnly && this.props.onChange) { 100 | this.props.onChange(this.editor.getValue()); 101 | } 102 | }, 103 | 104 | render: function() { 105 | // wrap in a div to fully contain CodeMirror 106 | var editor; 107 | 108 | if (IS_MOBILE) { 109 | editor =
{this.props.codeText}
; 110 | } else { 111 | editor =