├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── react-image.js └── react-image.min.js ├── example ├── dist │ ├── JSXTransformer.js │ ├── app.js │ ├── bundle.js │ ├── codemirror.css │ ├── codemirror.js │ ├── common.js │ ├── index.html │ └── javascript.js └── src │ ├── app.js │ └── index.html ├── gulpfile.js ├── package.json └── src └── Image.js /.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 | This project has been superseded by https://github.com/mbrevda/react-image. 2 | === 3 | and is no longer being activly devloped! 4 | 5 | 6 | React Image 7 | ================= 8 | 9 | Image Component for React. 10 | 11 | ## Demo & Examples 12 | 13 | Live demo: [yuanyan.github.io/react-image](http://yuanyan.github.io/react-image/) 14 | 15 | To build the examples locally, run: 16 | 17 | ``` 18 | npm install 19 | gulp dev 20 | ``` 21 | 22 | Then open [`localhost:9999`](http://localhost:9999) in a browser. 23 | 24 | ## Installation 25 | 26 | The easiest way to use `react-image` is to install it from NPM and include it in your own React build process (using [Browserify](http://browserify.org), etc). 27 | 28 | You can also use the standalone build by including `dist/react-image.js` in your page. If you use this, make sure you have already included React, and it is available as a global variable. 29 | 30 | ``` 31 | npm install react-image --save 32 | ``` 33 | 34 | ## Usage 35 | 36 | ``` 37 | var React = require('react'); 38 | var Img = require('react-image'); 39 | var App = React.createClass({ 40 | 41 | imgs: [ 42 | "//placebacon.net/200/150 400w", 43 | "//placebacon.net/300/300 600w", 44 | "//placebacon.net/400/400 800w", 45 | "//placebacon.net/800/800 1000w" 46 | ], 47 | 48 | render: function() { 49 | 50 | return ( 51 | Bacon... 52 | ); 53 | } 54 | 55 | }); 56 | ``` 57 | 58 | ## Properties 59 | 60 | * `url`: Image URL, this attribute is obligatory for the element. On browsers supporting srcset, src is ignored if this one is provided. 61 | * `srcSet`: A list of one or more strings separated by commas indicating a set of possible images for the user agent to use. Each string is composed of: 62 | 1. one URL to an image, 63 | 2. a width descriptor, that is a positive integer directly followed by 'w'. The default value, if missing, is the infinity. 64 | 3. a pixel density descriptor, that is a positive floating number directly followed by 'x'. The default value, if missing, is 1x. 65 | 66 | Each string in the list must have at least a width descriptor or a pixel density descriptor to be valid. Among the list, there must be only one string containing the same tuple of width descriptor and pixel density descriptor. 67 | The browser chooses the most adequate image to display at a given point of time. 68 | 69 | * `crossorigin`: This enumerated attribute indicates if the fetching of the related image must be done using CORS or not. CORS-enabled images can be reused in the element without being tainted. The allowed values are: 70 | * `anonymous`: A cross-origin request (i.e. with Origin: HTTP header) is performed. But no credential is sent (i.e. no cookie, no X.509 certificate and no HTTP Basic authentication is sent). If the server does not give credentials to the origin site (by not setting the Access-Control-Allow-Origin: HTTP header), the image will be tainted and its usage restricted.. 71 | * `use-credentials`: A cross-origin request (i.e. with Origin: HTTP header) performed with credential is sent (i.e. a cookie, a certificate and HTTP Basic authentication is performed). If the server does not give credentials to the origin site (through Access-Control-Allow-Credentials: HTTP header), the image will be tainted and its usage restricted. 72 | 73 | When not present, the resource is fetched without a CORS request (i.e. without sending the Origin: HTTP header), preventing its non-tainted usage in elements. If invalid, it is handled as if the enumerated keyword anonymous was used. See CORS settings attributes for additional information. 74 | 75 | * `width`: The width of the image in CSS pixels. 76 | * `height`: The height of the image in CSS pixels. 77 | * `alt`: This attribute defines the alternative text describing the image. Users will see this displayed if the image URL is wrong, the image is not in one of the supported formats, or until the image is downloaded. 78 | * `lazy`: Lazy load image when not in viewport, set `lazy=10` means load image when in 10px offset relative viewport. 79 | 80 | ## Events 81 | * `onLoad`: The callback when image success loaded. 82 | * `onError`: The callback when image error loaded. 83 | 84 | ## TODO 85 | 86 | * `sizes`: A list of one or more strings separated by commas indicating a set of source sizes. Each source size consists of: 87 | 1. an optional media condition, 88 | 2. a source size value. The default, if missing, is 100vw. 89 | 90 | Source sizes values are used to specify the intended size of the image, for the purpose of selecting a source from the list supplied by the srcset attribute. The selected size becomes the intrinsic size of the image (images inherent size if no explicit CSS styling is applied). If the srcset attribute is absent, or contains no values with a width descriptor, then the sizes attribute has no effect. 91 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-image", 3 | "main": "dist/react-image.min.js", 4 | "version": "0.1.0", 5 | "homepage": "https://github.com/yuanyan/react-image", 6 | "authors": [ 7 | "Yuanyan Cao" 8 | ], 9 | "description": "Image Component for React", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "react", 17 | "react-component", 18 | "image", 19 | "img" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | ".editorconfig", 24 | ".gitignore", 25 | "package.json", 26 | "src", 27 | "node_modules", 28 | "example", 29 | "test" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /dist/react-image.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.Image=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= 0)) // b is less than target but a is the same or better 49 | { 50 | return a; 51 | } 52 | 53 | if ((bDt === 0 && aDt !== 0) || // b perfectly matches target but a does not 54 | (aDt < 0 && bDt >= 0)) // a is less than target but b is the same or better 55 | { 56 | return b; 57 | } 58 | 59 | if (Math.abs(aDt) < Math.abs(bDt)) 60 | { 61 | return a; 62 | } 63 | 64 | if (Math.abs(bDt) < Math.abs(aDt)) 65 | { 66 | return b; 67 | } 68 | 69 | return a; 70 | } 71 | 72 | // document.body.scrollTop was working in Chrome but didn't work on Firefox, so had to resort to window.pageYOffset 73 | // but can't fallback to document.body.scrollTop as that doesn't work in IE with a doctype (?) so have to use document.documentElement.scrollTop 74 | function getPageOffset(){ 75 | return window.pageYOffset || document.documentElement.scrollTop; 76 | } 77 | 78 | function checkElementInViewport(element, viewportHeight, lazyOffset){ 79 | var elementOffsetTop = 0; 80 | var offset = getPageOffset() + lazyOffset; 81 | 82 | if (element.offsetParent) { 83 | do { 84 | elementOffsetTop += element.offsetTop; 85 | } 86 | while (element = element.offsetParent); 87 | } 88 | 89 | return elementOffsetTop < (viewportHeight + offset); 90 | } 91 | 92 | var Image = React.createClass({displayName: "Image", 93 | nativeSupport: false, 94 | propTypes: { 95 | src: React.PropTypes.string.isRequired, 96 | srcSet: React.PropTypes.string, 97 | sizes: React.PropTypes.string, 98 | alt: React.PropTypes.string, 99 | width: React.PropTypes.number, 100 | height: React.PropTypes.number, 101 | fade: React.PropTypes.bool, 102 | placeholderSrc: React.PropTypes.string, 103 | lazyOffset: React.PropTypes.number, 104 | crossorigin: React.PropTypes.oneOf(['anonymous', 'use-credentials']), 105 | onLoad: React.PropTypes.func, 106 | onError: React.PropTypes.func 107 | }, 108 | 109 | getDefaultProps: function() { 110 | return { 111 | onLoad: function(){}, 112 | onError: function(){}, 113 | lazyOffset: 0, 114 | placeholderSrc: 'data:image/gif;base64,R0lGODlhEAAJAIAAAP///wAAACH5BAEAAAAALAAAAAAQAAkAAAIKhI+py+0Po5yUFQA7' 115 | } 116 | }, 117 | 118 | getInitialState: function() { 119 | 120 | if (typeof document !== 'undefined') { 121 | var img = document.createElement('img'); 122 | this.nativeSupport = ('sizes' in img) && ('srcset' in img); 123 | } 124 | 125 | this.onViewportResize = debounce(this.onViewportResize, 150); 126 | this.onViewportScroll = debounce(this.onViewportScroll, 150); 127 | 128 | return { 129 | w: this.getViewportWidth(), 130 | h: this.getViewportHeight(), 131 | x: this.getDevicePixelRatio(), 132 | candidates: this.buildCandidates(this.props.srcSet) 133 | } 134 | }, 135 | 136 | componentWillReceiveProps: function(nextProps) { 137 | if (nextProps && nextProps.srcSet) { 138 | this.setState({candidates: this.buildCandidates(nextProps.srcSet)}); 139 | } 140 | }, 141 | 142 | componentDidMount: function() { 143 | 144 | if (typeof window !== "undefined") { 145 | 146 | if(this.props.lazy) { 147 | this.onViewportScroll(); 148 | addEvent(window, "scroll", this.onViewportScroll); 149 | } 150 | 151 | if (!this.nativeSupport) { 152 | addEvent(window, "resize", this.onViewportResize); 153 | } 154 | } 155 | }, 156 | 157 | componentWillUnmount: function() { 158 | 159 | if (typeof window !== "undefined") { 160 | 161 | if(this.props.lazy) { 162 | removeEvent(window, "scroll", this.onViewportScroll); 163 | } 164 | 165 | if (!this.nativeSupport) { 166 | removeEvent(window, "resize", this.onViewportResize) 167 | } 168 | } 169 | }, 170 | 171 | render: function() { 172 | 173 | if(this.props.lazy && !this.state.lazyloaded ) return this.renderPlaceholder(); 174 | 175 | if (this.nativeSupport) return this.renderNative(); 176 | return ( 177 | React.createElement("img", React.__spread({}, this.props, {onLoad: this.props.onLoad, onError: this.props.onError, src: this.matchImage()})) 178 | ); 179 | }, 180 | 181 | renderPlaceholder: function(){ 182 | return ( 183 | React.createElement("img", {width: this.props.width, height: this.props.height, ref: "placeholder", src: this.props.placeholderSrc}) 184 | ) 185 | }, 186 | 187 | renderNative: function() { 188 | return ( 189 | React.createElement("img", React.__spread({}, this.props, {onLoad: this.props.onLoad, onError: this.props.onError, src: this.state.candidates[0].url, srcSet: this.props.srcSet})) 190 | ); 191 | }, 192 | 193 | /** 194 | * Takes a srcSet in the form of url/ 195 | * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or 196 | * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or 197 | * "images/pic-small.png" 198 | * Get an array of image candidates in the form of 199 | * {url: "/foo/bar.png", x: 1, w:0, h: 0} 200 | * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value 201 | * If sizes is specified, resolution is calculated 202 | */ 203 | buildCandidates: function(srcSet) { 204 | return srcSet.split(',').map(function(srcImg) { 205 | var stringComponents = srcImg.trim().split(' '); 206 | var candidate = { 207 | url: stringComponents[0].trim(), 208 | w: 0, 209 | h: 0, 210 | x: 1.0 211 | }; 212 | 213 | for (var i = 1; i < stringComponents.length; i++) { 214 | var str = stringComponents[i].trim(); 215 | if (str.indexOf('w', str.length - 1) !== -1) { 216 | candidate.w = parseInt(str.substring(0,str.length-1)); 217 | } else if (str.indexOf('h', str.length-1) !== -1) { 218 | candidate.h = parseInt(str.substring(0,str.length-1)); 219 | } else if (str.indexOf('x', str.length-1) !== -1) { 220 | candidate.x = parseFloat(str.substring(0,str.length-1)); 221 | } else { 222 | console.warn('Invalid parameter passed to Image srcSet: [' + str + '] in ' + srcImg); 223 | } 224 | } 225 | 226 | return candidate; 227 | }); 228 | }, 229 | 230 | matchImage: function() { 231 | return this.state.candidates.reduce(function(a, b) { 232 | if (a.x === b.x) { 233 | // Both have the same density so attempt to find a better one using width 234 | if (a.w === b.w) { 235 | // Both have the same width so attempt to use height 236 | if (a.h === b.h) { 237 | return a; // hey, it came first! 238 | } else { 239 | return compare(a, b, this.state.h, function(img) { return img.h }); 240 | } 241 | } else { 242 | return compare(a, b, this.state.w, function(img) { return img.w }); 243 | } 244 | } else { 245 | return compare(a, b, this.state.x, function(img) { return img.x }); 246 | } 247 | }.bind(this)).url; 248 | }, 249 | 250 | onViewportResize: function() { 251 | // TODO: We need to time delay this, only update maybe once a second or 2 252 | this.setState({w: this.getViewportWidth(), h: this.getViewportHeight()}); 253 | }, 254 | 255 | onViewportScroll: function(){ 256 | if(this.refs.placeholder && checkElementInViewport(this.refs.placeholder.getDOMNode(), this.getViewportHeight(), this.props.lazyOffset) ){ 257 | this.setState({ 258 | lazyloaded: true 259 | }); 260 | } 261 | }, 262 | 263 | getViewportWidth: function() { 264 | if (typeof window !== 'undefined') { 265 | return window.innerWidth || document.documentElement.clientWidth; 266 | } else { 267 | return 0; 268 | } 269 | }, 270 | 271 | getViewportHeight: function() { 272 | if (typeof window !== 'undefined') { 273 | return window.innerHeight || document.documentElement.clientHeight; 274 | } else { 275 | return 0; 276 | } 277 | }, 278 | 279 | getDevicePixelRatio: function() { 280 | if (typeof window !== 'undefined') { 281 | return window.devicePixelRatio; 282 | } else { 283 | return 1; 284 | } 285 | } 286 | 287 | }); 288 | 289 | module.exports = Image; 290 | 291 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 292 | },{}]},{},[1])(1) 293 | }); -------------------------------------------------------------------------------- /dist/react-image.min.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 t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.Image=e()}}(function(){return function e(t,n,i){function r(s,d){if(!n[s]){if(!t[s]){var a="function"==typeof require&&require;if(!d&&a)return a(s,!0);if(o)return o(s,!0);var p=new Error("Cannot find module '"+s+"'");throw p.code="MODULE_NOT_FOUND",p}var u=n[s]={exports:{}};t[s][0].call(u.exports,function(e){var n=t[s][1][e];return r(n?n:e)},u,u.exports,e,t,n,i)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;so&&r>=0?e:0===o&&0!==r||0>r&&o>=0?t:Math.abs(r)i}var s="undefined"!=typeof window?window.React:"undefined"!=typeof e?e.React:null,d=function(){return document.addEventListener?function(e,t,n){return e.addEventListener(t,n,!1)}:function(e,t,n){return e.attachEvent("on"+t,n)}}(),a=function(){return document.addEventListener?function(e,t,n){return e.removeEventListener(t,n,!1)}:function(e,t,n){return e.detachEvent("on"+t,n)}}(),p=s.createClass({displayName:"Image",nativeSupport:!1,propTypes:{src:s.PropTypes.string.isRequired,srcSet:s.PropTypes.string,sizes:s.PropTypes.string,alt:s.PropTypes.string,width:s.PropTypes.number,height:s.PropTypes.number,fade:s.PropTypes.bool,placeholderSrc:s.PropTypes.string,lazyOffset:s.PropTypes.number,crossorigin:s.PropTypes.oneOf(["anonymous","use-credentials"]),onLoad:s.PropTypes.func,onError:s.PropTypes.func},getDefaultProps:function(){return{onLoad:function(){},onError:function(){},lazyOffset:0,placeholderSrc:"data:image/gif;base64,R0lGODlhEAAJAIAAAP///wAAACH5BAEAAAAALAAAAAAQAAkAAAIKhI+py+0Po5yUFQA7"}},getInitialState:function(){if("undefined"!=typeof document){var e=document.createElement("img");this.nativeSupport="sizes"in e&&"srcset"in e}return this.onViewportResize=n(this.onViewportResize,150),this.onViewportScroll=n(this.onViewportScroll,150),{w:this.getViewportWidth(),h:this.getViewportHeight(),x:this.getDevicePixelRatio(),candidates:this.buildCandidates(this.props.srcSet)}},componentWillReceiveProps:function(e){e&&e.srcSet&&this.setState({candidates:this.buildCandidates(e.srcSet)})},componentDidMount:function(){"undefined"!=typeof window&&(this.props.lazy&&(this.onViewportScroll(),d(window,"scroll",this.onViewportScroll)),this.nativeSupport||d(window,"resize",this.onViewportResize))},componentWillUnmount:function(){"undefined"!=typeof window&&(this.props.lazy&&a(window,"scroll",this.onViewportScroll),this.nativeSupport||a(window,"resize",this.onViewportResize))},render:function(){return this.props.lazy&&!this.state.lazyloaded?this.renderPlaceholder():this.nativeSupport?this.renderNative():s.createElement("img",s.__spread({},this.props,{onLoad:this.props.onLoad,onError:this.props.onError,src:this.matchImage()}))},renderPlaceholder:function(){return s.createElement("img",{width:this.props.width,height:this.props.height,ref:"placeholder",src:this.props.placeholderSrc})},renderNative:function(){return s.createElement("img",s.__spread({},this.props,{onLoad:this.props.onLoad,onError:this.props.onError,src:this.state.candidates[0].url,srcSet:this.props.srcSet}))},buildCandidates:function(e){return e.split(",").map(function(e){for(var t=e.trim().split(" "),n={url:t[0].trim(),w:0,h:0,x:1},i=1;i= match.index && 114 | cursor.ch < match.index + 3) { 115 | 116 | // TODO(joel) - figure out why this doesn't fold although it 117 | // seems like it should work. 118 | instance.foldCode(cursor, { widget: '...' }); 119 | } 120 | }); 121 | }, 122 | 123 | componentDidUpdate: function() { 124 | if (this.props.readOnly) { 125 | this.editor.setValue(this.props.codeText); 126 | } 127 | }, 128 | 129 | handleChange: function() { 130 | if (!this.props.readOnly && this.props.onChange) { 131 | this.props.onChange(this.editor.getValue()); 132 | } 133 | }, 134 | 135 | render: function() { 136 | // wrap in a div to fully contain CodeMirror 137 | var editor; 138 | 139 | if (IS_MOBILE) { 140 | editor = React.createElement("pre", {style: {overflow: 'scroll'}}, this.props.codeText); 141 | } else { 142 | editor = React.createElement("textarea", {ref: "editor", defaultValue: this.props.codeText}); 143 | } 144 | 145 | return ( 146 | React.createElement("div", {style: this.props.style, className: this.props.className}, 147 | editor 148 | ) 149 | ); 150 | } 151 | }); 152 | 153 | var ReactPlayground = React.createClass({displayName: "ReactPlayground", 154 | propTypes: { 155 | codeText: React.PropTypes.string.isRequired 156 | }, 157 | 158 | getInitialState: function() { 159 | return { 160 | code: this.props.codeText 161 | }; 162 | }, 163 | 164 | handleCodeChange: function(code) { 165 | this.setState({ 166 | code: code 167 | }); 168 | }, 169 | 170 | render: function() { 171 | return React.createElement("div", {className: "playground"}, 172 | React.createElement("div", {className: "playgroundCode"}, 173 | React.createElement(CodeMirrorEditor, {key: "jsx", 174 | onChange: this.handleCodeChange, 175 | className: "playgroundStage", 176 | codeText: this.state.code}) 177 | ), 178 | React.createElement("div", {className: "playgroundPreview"}, 179 | React.createElement(ComponentPreview, {code: this.state.code}) 180 | ) 181 | ); 182 | } 183 | }); 184 | 185 | React.render( 186 | React.createElement(ReactPlayground, {codeText: document.getElementById('code1').innerHTML}), 187 | document.getElementById('example1') 188 | ); 189 | 190 | React.render( 191 | React.createElement(ReactPlayground, {codeText: document.getElementById('code2').innerHTML}), 192 | document.getElementById('example2') 193 | ); 194 | 195 | React.render( 196 | React.createElement(ReactPlayground, {codeText: document.getElementById('code3').innerHTML}), 197 | document.getElementById('example3') 198 | ); 199 | 200 | },{"react":undefined,"react-image":undefined}]},{},[1]); 201 | -------------------------------------------------------------------------------- /example/dist/bundle.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= 0)) // b is less than target but a is the same or better 48 | { 49 | return a; 50 | } 51 | 52 | if ((bDt === 0 && aDt !== 0) || // b perfectly matches target but a does not 53 | (aDt < 0 && bDt >= 0)) // a is less than target but b is the same or better 54 | { 55 | return b; 56 | } 57 | 58 | if (Math.abs(aDt) < Math.abs(bDt)) 59 | { 60 | return a; 61 | } 62 | 63 | if (Math.abs(bDt) < Math.abs(aDt)) 64 | { 65 | return b; 66 | } 67 | 68 | return a; 69 | } 70 | 71 | // document.body.scrollTop was working in Chrome but didn't work on Firefox, so had to resort to window.pageYOffset 72 | // but can't fallback to document.body.scrollTop as that doesn't work in IE with a doctype (?) so have to use document.documentElement.scrollTop 73 | function getPageOffset(){ 74 | return window.pageYOffset || document.documentElement.scrollTop; 75 | } 76 | 77 | function checkElementInViewport(element, viewportHeight, lazyOffset){ 78 | var elementOffsetTop = 0; 79 | var offset = getPageOffset() + lazyOffset; 80 | 81 | if (element.offsetParent) { 82 | do { 83 | elementOffsetTop += element.offsetTop; 84 | } 85 | while (element = element.offsetParent); 86 | } 87 | 88 | return elementOffsetTop < (viewportHeight + offset); 89 | } 90 | 91 | var Image = React.createClass({displayName: "Image", 92 | nativeSupport: false, 93 | propTypes: { 94 | src: React.PropTypes.string.isRequired, 95 | srcSet: React.PropTypes.string, 96 | sizes: React.PropTypes.string, 97 | alt: React.PropTypes.string, 98 | width: React.PropTypes.number, 99 | height: React.PropTypes.number, 100 | fade: React.PropTypes.bool, 101 | placeholderSrc: React.PropTypes.string, 102 | lazyOffset: React.PropTypes.number, 103 | crossorigin: React.PropTypes.oneOf(['anonymous', 'use-credentials']), 104 | onLoad: React.PropTypes.func, 105 | onError: React.PropTypes.func 106 | }, 107 | 108 | getDefaultProps: function() { 109 | return { 110 | onLoad: function(){}, 111 | onError: function(){}, 112 | lazyOffset: 0, 113 | placeholderSrc: 'data:image/gif;base64,R0lGODlhEAAJAIAAAP///wAAACH5BAEAAAAALAAAAAAQAAkAAAIKhI+py+0Po5yUFQA7' 114 | } 115 | }, 116 | 117 | getInitialState: function() { 118 | 119 | if (typeof document !== 'undefined') { 120 | var img = document.createElement('img'); 121 | this.nativeSupport = ('sizes' in img) && ('srcset' in img); 122 | } 123 | 124 | this.onViewportResize = debounce(this.onViewportResize, 150); 125 | this.onViewportScroll = debounce(this.onViewportScroll, 150); 126 | 127 | return { 128 | w: this.getViewportWidth(), 129 | h: this.getViewportHeight(), 130 | x: this.getDevicePixelRatio(), 131 | candidates: this.buildCandidates(this.props.srcSet) 132 | } 133 | }, 134 | 135 | componentWillReceiveProps: function(nextProps) { 136 | if (nextProps && nextProps.srcSet) { 137 | this.setState({candidates: this.buildCandidates(nextProps.srcSet)}); 138 | } 139 | }, 140 | 141 | componentDidMount: function() { 142 | 143 | if (typeof window !== "undefined") { 144 | 145 | if(this.props.lazy) { 146 | this.onViewportScroll(); 147 | addEvent(window, "scroll", this.onViewportScroll); 148 | } 149 | 150 | if (!this.nativeSupport) { 151 | addEvent(window, "resize", this.onViewportResize); 152 | } 153 | } 154 | }, 155 | 156 | componentWillUnmount: function() { 157 | 158 | if (typeof window !== "undefined") { 159 | 160 | if(this.props.lazy) { 161 | removeEvent(window, "scroll", this.onViewportScroll); 162 | } 163 | 164 | if (!this.nativeSupport) { 165 | removeEvent(window, "resize", this.onViewportResize) 166 | } 167 | } 168 | }, 169 | 170 | render: function() { 171 | 172 | if(this.props.lazy && !this.state.lazyloaded ) return this.renderPlaceholder(); 173 | 174 | if (this.nativeSupport) return this.renderNative(); 175 | return ( 176 | React.createElement("img", React.__spread({}, this.props, {onLoad: this.props.onLoad, onError: this.props.onError, src: this.matchImage()})) 177 | ); 178 | }, 179 | 180 | renderPlaceholder: function(){ 181 | return ( 182 | React.createElement("img", {width: this.props.width, height: this.props.height, ref: "placeholder", src: this.props.placeholderSrc}) 183 | ) 184 | }, 185 | 186 | renderNative: function() { 187 | return ( 188 | React.createElement("img", React.__spread({}, this.props, {onLoad: this.props.onLoad, onError: this.props.onError, src: this.state.candidates[0].url, srcSet: this.props.srcSet})) 189 | ); 190 | }, 191 | 192 | /** 193 | * Takes a srcSet in the form of url/ 194 | * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or 195 | * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or 196 | * "images/pic-small.png" 197 | * Get an array of image candidates in the form of 198 | * {url: "/foo/bar.png", x: 1, w:0, h: 0} 199 | * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value 200 | * If sizes is specified, resolution is calculated 201 | */ 202 | buildCandidates: function(srcSet) { 203 | return srcSet.split(',').map(function(srcImg) { 204 | var stringComponents = srcImg.trim().split(' '); 205 | var candidate = { 206 | url: stringComponents[0].trim(), 207 | w: 0, 208 | h: 0, 209 | x: 1.0 210 | }; 211 | 212 | for (var i = 1; i < stringComponents.length; i++) { 213 | var str = stringComponents[i].trim(); 214 | if (str.indexOf('w', str.length - 1) !== -1) { 215 | candidate.w = parseInt(str.substring(0,str.length-1)); 216 | } else if (str.indexOf('h', str.length-1) !== -1) { 217 | candidate.h = parseInt(str.substring(0,str.length-1)); 218 | } else if (str.indexOf('x', str.length-1) !== -1) { 219 | candidate.x = parseFloat(str.substring(0,str.length-1)); 220 | } else { 221 | console.warn('Invalid parameter passed to Image srcSet: [' + str + '] in ' + srcImg); 222 | } 223 | } 224 | 225 | return candidate; 226 | }); 227 | }, 228 | 229 | matchImage: function() { 230 | return this.state.candidates.reduce(function(a, b) { 231 | if (a.x === b.x) { 232 | // Both have the same density so attempt to find a better one using width 233 | if (a.w === b.w) { 234 | // Both have the same width so attempt to use height 235 | if (a.h === b.h) { 236 | return a; // hey, it came first! 237 | } else { 238 | return compare(a, b, this.state.h, function(img) { return img.h }); 239 | } 240 | } else { 241 | return compare(a, b, this.state.w, function(img) { return img.w }); 242 | } 243 | } else { 244 | return compare(a, b, this.state.x, function(img) { return img.x }); 245 | } 246 | }.bind(this)).url; 247 | }, 248 | 249 | onViewportResize: function() { 250 | // TODO: We need to time delay this, only update maybe once a second or 2 251 | this.setState({w: this.getViewportWidth(), h: this.getViewportHeight()}); 252 | }, 253 | 254 | onViewportScroll: function(){ 255 | if(this.refs.placeholder && checkElementInViewport(this.refs.placeholder.getDOMNode(), this.getViewportHeight(), this.props.lazyOffset) ){ 256 | this.setState({ 257 | lazyloaded: true 258 | }); 259 | } 260 | }, 261 | 262 | getViewportWidth: function() { 263 | if (typeof window !== 'undefined') { 264 | return window.innerWidth || document.documentElement.clientWidth; 265 | } else { 266 | return 0; 267 | } 268 | }, 269 | 270 | getViewportHeight: function() { 271 | if (typeof window !== 'undefined') { 272 | return window.innerHeight || document.documentElement.clientHeight; 273 | } else { 274 | return 0; 275 | } 276 | }, 277 | 278 | getDevicePixelRatio: function() { 279 | if (typeof window !== 'undefined') { 280 | return window.devicePixelRatio; 281 | } else { 282 | return 1; 283 | } 284 | } 285 | 286 | }); 287 | 288 | module.exports = Image; 289 | 290 | },{"react":undefined}]},{},[]); 291 | -------------------------------------------------------------------------------- /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 | 29 | 30 | 52 | 53 | 75 | 116 | 117 | 118 |

Live Demo

119 |

Set image by device pixel ratio

120 |
121 |

Set image by viewport width

122 |
123 |

Lazy load image

124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /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/src/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Img = require('react-image'); 3 | 4 | var selfCleaningTimeout = { 5 | componentDidUpdate: function() { 6 | clearTimeout(this.timeoutID); 7 | }, 8 | 9 | setTimeout: function() { 10 | clearTimeout(this.timeoutID); 11 | this.timeoutID = setTimeout.apply(null, arguments); 12 | } 13 | }; 14 | 15 | var ComponentPreview = React.createClass({ 16 | propTypes: { 17 | code: React.PropTypes.string.isRequired 18 | }, 19 | 20 | mixins: [selfCleaningTimeout], 21 | 22 | render: function() { 23 | return
; 24 | }, 25 | 26 | componentDidMount: function() { 27 | this.executeCode(); 28 | }, 29 | 30 | componentDidUpdate: function(prevProps) { 31 | // execute code only when the state's not being updated by switching tab 32 | // this avoids re-displaying the error, which comes after a certain delay 33 | if (this.props.code !== prevProps.code) { 34 | this.executeCode(); 35 | } 36 | }, 37 | 38 | compileCode: function() { 39 | return JSXTransformer.transform( 40 | '(function() {' + 41 | this.props.code + 42 | '\n})();', 43 | { harmony: true } 44 | ).code; 45 | }, 46 | 47 | executeCode: function() { 48 | var mountNode = this.refs.mount.getDOMNode(); 49 | 50 | try { 51 | React.unmountComponentAtNode(mountNode); 52 | } catch (e) { } 53 | 54 | try { 55 | var compiledCode = this.compileCode(); 56 | React.render(eval(compiledCode), mountNode); 57 | } catch (err) { 58 | this.setTimeout(function() { 59 | React.render( 60 |
{err.toString()}
, 61 | mountNode 62 | ); 63 | }, 500); 64 | } 65 | } 66 | }); 67 | 68 | var IS_MOBILE = ( 69 | navigator.userAgent.match(/Android/i) 70 | || navigator.userAgent.match(/webOS/i) 71 | || navigator.userAgent.match(/iPhone/i) 72 | || navigator.userAgent.match(/iPad/i) 73 | || navigator.userAgent.match(/iPod/i) 74 | || navigator.userAgent.match(/BlackBerry/i) 75 | || navigator.userAgent.match(/Windows Phone/i) 76 | ); 77 | 78 | var CodeMirrorEditor = React.createClass({ 79 | componentDidMount: function() { 80 | if (IS_MOBILE) return; 81 | 82 | this.editor = CodeMirror.fromTextArea(this.refs.editor.getDOMNode(), { 83 | mode: 'javascript', 84 | lineNumbers: true, 85 | lineWrapping: true, 86 | smartIndent: false, // javascript mode does bad things with jsx indents 87 | matchBrackets: true, 88 | readOnly: this.props.readOnly 89 | }); 90 | this.editor.on('change', this.handleChange); 91 | 92 | this.editor.on('beforeSelectionChange', function(instance, obj){ 93 | // why is ranges plural? 94 | var selection = obj.ranges ? 95 | obj.ranges[0] : 96 | obj; 97 | 98 | var noRange = selection.anchor.ch === selection.head.ch && 99 | selection.anchor.line === selection.head.line; 100 | if (!noRange) { 101 | return; 102 | } 103 | 104 | var cursor = selection.anchor; 105 | var line = instance.getLine(cursor.line); 106 | var match = OPEN_MARK.exec(line) || CLOSE_MARK.exec(line); 107 | 108 | // the opening or closing mark appears on this line 109 | if (match && 110 | // and the cursor is on it 111 | // (this is buggy if both occur on the same line) 112 | cursor.ch >= match.index && 113 | cursor.ch < match.index + 3) { 114 | 115 | // TODO(joel) - figure out why this doesn't fold although it 116 | // seems like it should work. 117 | instance.foldCode(cursor, { widget: '...' }); 118 | } 119 | }); 120 | }, 121 | 122 | componentDidUpdate: function() { 123 | if (this.props.readOnly) { 124 | this.editor.setValue(this.props.codeText); 125 | } 126 | }, 127 | 128 | handleChange: function() { 129 | if (!this.props.readOnly && this.props.onChange) { 130 | this.props.onChange(this.editor.getValue()); 131 | } 132 | }, 133 | 134 | render: function() { 135 | // wrap in a div to fully contain CodeMirror 136 | var editor; 137 | 138 | if (IS_MOBILE) { 139 | editor =
{this.props.codeText}
; 140 | } else { 141 | editor =