├── .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 |
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 = ;
142 | }
143 |
144 | return (
145 |
146 | {editor}
147 |
148 | );
149 | }
150 | });
151 |
152 | var ReactPlayground = React.createClass({
153 | propTypes: {
154 | codeText: React.PropTypes.string.isRequired
155 | },
156 |
157 | getInitialState: function() {
158 | return {
159 | code: this.props.codeText
160 | };
161 | },
162 |
163 | handleCodeChange: function(code) {
164 | this.setState({
165 | code: code
166 | });
167 | },
168 |
169 | render: function() {
170 | return
171 |
172 |
176 |
177 |
178 |
179 |
180 |
;
181 | }
182 | });
183 |
184 | React.render(
185 | ,
186 | document.getElementById('example1')
187 | );
188 |
189 | React.render(
190 | ,
191 | document.getElementById('example2')
192 | );
193 |
194 | React.render(
195 | ,
196 | document.getElementById('example3')
197 | );
198 |
--------------------------------------------------------------------------------
/example/src/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 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify'),
2 | shim = require('browserify-shim'),
3 | chalk = require('chalk'),
4 | del = require('del'),
5 | gulp = require('gulp'),
6 | bump = require('gulp-bump'),
7 | connect = require('gulp-connect'),
8 | deploy = require("gulp-gh-pages"),
9 | git = require("gulp-git"),
10 | less = require('gulp-less'),
11 | rename = require('gulp-rename'),
12 | streamify = require('gulp-streamify'),
13 | uglify = require('gulp-uglify'),
14 | gutil = require('gulp-util'),
15 | merge = require('merge-stream'),
16 | reactify = require('reactify'),
17 | source = require('vinyl-source-stream'),
18 | watchify = require('watchify');
19 |
20 |
21 | /**
22 | * Constants
23 | */
24 |
25 | var SRC_PATH = 'src';
26 | var DIST_PATH = 'dist';
27 |
28 | var COMPONENT_NAME = 'Image';
29 | var PACKAGE_FILE = COMPONENT_NAME + '.js';
30 | var PACKAGE_NAME = 'react-' + COMPONENT_NAME.toLowerCase();
31 |
32 | var DEPENDENCIES = ['react'];
33 |
34 | var EXAMPLE_SRC_PATH = 'example/src';
35 | var EXAMPLE_DIST_PATH = 'example/dist';
36 |
37 | var EXAMPLE_APP = 'app.js';
38 | var EXAMPLE_COPY = [
39 | 'node_modules/codemirror/lib/codemirror.js',
40 | 'node_modules/codemirror/lib/codemirror.css',
41 | 'node_modules/codemirror/mode/javascript/javascript.js',
42 | 'node_modules/react/dist/JSXTransformer.js'
43 | ];
44 | var EXAMPLE_LESS = 'app.less';
45 | var EXAMPLE_FILES = [
46 | 'index.html'
47 | ];
48 |
49 |
50 | /**
51 | * Bundle helpers
52 | */
53 |
54 | function doBundle(target, name, dest) {
55 | return target.bundle()
56 | .on('error', function(e) {
57 | gutil.log('Browserify Error', e);
58 | })
59 | .pipe(source(name))
60 | .pipe(gulp.dest(dest))
61 | .pipe(connect.reload());
62 | }
63 |
64 | function watchBundle(target, name, dest) {
65 | return watchify(target)
66 | .on('update', function (scriptIds) {
67 | scriptIds = scriptIds
68 | .filter(function(i) { return i.substr(0,2) !== './' })
69 | .map(function(i) { return chalk.blue(i.replace(__dirname, '')) });
70 | if (scriptIds.length > 1) {
71 | gutil.log(scriptIds.length + ' Scripts updated:\n* ' + scriptIds.join('\n* ') + '\nrebuilding...');
72 | } else {
73 | gutil.log(scriptIds[0] + ' updated, rebuilding...');
74 | }
75 | doBundle(target, name, dest);
76 | })
77 | .on('time', function (time) {
78 | gutil.log(chalk.green(name + ' built in ' + (Math.round(time / 10) / 100) + 's'));
79 | });
80 | }
81 |
82 |
83 | /**
84 | * Prepare task for examples
85 | */
86 |
87 | gulp.task('prepare:examples', function(done) {
88 | del([EXAMPLE_DIST_PATH], done);
89 | });
90 |
91 |
92 | /**
93 | * Build example files
94 | */
95 |
96 | function buildExampleFiles() {
97 | return gulp.src(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }))
98 | .pipe(gulp.dest(EXAMPLE_DIST_PATH))
99 | .pipe(connect.reload());
100 | }
101 |
102 | gulp.task('dev:build:example:files', buildExampleFiles);
103 | gulp.task('build:example:files', ['prepare:examples'], buildExampleFiles);
104 |
105 |
106 | /**
107 | * Build example css from less
108 | */
109 |
110 | function buildExampleCSS() {
111 | return gulp.src(EXAMPLE_SRC_PATH + '/' + EXAMPLE_LESS)
112 | .pipe(less())
113 | .pipe(gulp.dest(EXAMPLE_DIST_PATH))
114 | .pipe(connect.reload());
115 | }
116 |
117 | gulp.task('dev:build:example:css', buildExampleCSS);
118 | gulp.task('build:example:css', ['prepare:examples'], buildExampleCSS);
119 |
120 |
121 | /**
122 | * Build example scripts
123 | *
124 | * Returns a gulp task with watchify when in development mode
125 | */
126 |
127 | function buildExampleScripts(dev) {
128 |
129 | var dest = EXAMPLE_DIST_PATH;
130 |
131 | var opts = dev ? watchify.args : {};
132 | opts.debug = dev ? true : false;
133 | opts.hasExports = true;
134 |
135 | return function() {
136 |
137 | var common = browserify(opts),
138 | bundle = browserify(opts).require('./' + SRC_PATH + '/' + PACKAGE_FILE, { expose: PACKAGE_NAME }),
139 | example = browserify(opts).exclude(PACKAGE_NAME).add('./' + EXAMPLE_SRC_PATH + '/' + EXAMPLE_APP);
140 |
141 | DEPENDENCIES.forEach(function(pkg) {
142 | common.require(pkg);
143 | bundle.exclude(pkg);
144 | example.exclude(pkg);
145 | });
146 |
147 | if (dev) {
148 | watchBundle(common, 'common.js', dest);
149 | watchBundle(bundle, 'bundle.js', dest);
150 | watchBundle(example, 'app.js', dest);
151 | }
152 |
153 | return merge(
154 | doBundle(common, 'common.js', dest),
155 | doBundle(bundle, 'bundle.js', dest),
156 | doBundle(example, 'app.js', dest)
157 | );
158 |
159 | }
160 |
161 | };
162 |
163 | gulp.task('dev:build:example:copy', function(){
164 | return gulp.src(EXAMPLE_COPY)
165 | .pipe(gulp.dest(EXAMPLE_DIST_PATH))
166 | .pipe(connect.reload());
167 | })
168 | gulp.task('build:example:copy', ['prepare:examples', 'dev:build:example:copy'])
169 |
170 |
171 | gulp.task('dev:build:example:scripts', buildExampleScripts(true));
172 | gulp.task('build:example:scripts', ['prepare:examples'], buildExampleScripts());
173 |
174 |
175 | /**
176 | * Build examples
177 | */
178 |
179 | gulp.task('build:examples', [
180 | 'build:example:files',
181 | 'build:example:css',
182 | 'build:example:scripts',
183 | 'build:example:copy'
184 | ]);
185 |
186 | gulp.task('watch:examples', [
187 | 'dev:build:example:files',
188 | 'dev:build:example:css',
189 | 'dev:build:example:scripts',
190 | 'dev:build:example:copy'
191 | ], function() {
192 | gulp.watch(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }), ['dev:build:example:files']);
193 | gulp.watch([EXAMPLE_SRC_PATH + '/' + EXAMPLE_LESS], ['dev:build:example:css']);
194 | });
195 |
196 |
197 | /**
198 | * Serve task for local development
199 | */
200 |
201 | gulp.task('dev:server', function() {
202 | connect.server({
203 | root: 'example/dist',
204 | port: 9999,
205 | livereload: true
206 | });
207 | });
208 |
209 |
210 | /**
211 | * Development task
212 | */
213 |
214 | gulp.task('dev', [
215 | 'dev:server',
216 | 'watch:examples'
217 | ]);
218 |
219 |
220 | /**
221 | * Build task
222 | */
223 |
224 | gulp.task('prepare:dist', function(done) {
225 | del([DIST_PATH], done);
226 | });
227 |
228 | gulp.task('build:dist', ['prepare:dist'], function() {
229 |
230 | var standalone = browserify('./' + SRC_PATH + '/' + PACKAGE_FILE, {
231 | standalone: COMPONENT_NAME
232 | })
233 | .transform(reactify)
234 | .transform(shim);
235 |
236 | DEPENDENCIES.forEach(function(pkg) {
237 | standalone.exclude(pkg);
238 | });
239 |
240 | return standalone.bundle()
241 | .on('error', function(e) {
242 | gutil.log('Browserify Error', e);
243 | })
244 | .pipe(source(PACKAGE_NAME + '.js'))
245 | .pipe(gulp.dest(DIST_PATH))
246 | .pipe(rename(PACKAGE_NAME + '.min.js'))
247 | .pipe(streamify(uglify()))
248 | .pipe(gulp.dest(DIST_PATH));
249 |
250 | });
251 |
252 | gulp.task('build', [
253 | 'build:dist',
254 | 'build:examples'
255 | ]);
256 |
257 |
258 | /**
259 | * Version bump tasks
260 | */
261 |
262 | function getBumpTask(type) {
263 | return function() {
264 | return gulp.src(['./package.json', './bower.json'])
265 | .pipe(bump({ type: type }))
266 | .pipe(gulp.dest('./'));
267 | };
268 | }
269 |
270 | gulp.task('bump', getBumpTask('patch'));
271 | gulp.task('bump:minor', getBumpTask('minor'));
272 | gulp.task('bump:major', getBumpTask('major'));
273 |
274 |
275 | /**
276 | * Git tag task
277 | * (version *must* be bumped first)
278 | */
279 |
280 | gulp.task('publish:tag', function(done) {
281 | var pkg = require('./package.json');
282 | var v = 'v' + pkg.version;
283 | var message = 'Release ' + v;
284 |
285 | git.tag(v, message, function (err) {
286 | if (err) throw err;
287 | git.push('origin', v, function (err) {
288 | if (err) throw err;
289 | done();
290 | });
291 | });
292 | });
293 |
294 |
295 | /**
296 | * npm publish task
297 | * * (version *must* be bumped first)
298 | */
299 |
300 | gulp.task('publish:npm', function(done) {
301 | require('child_process')
302 | .spawn('npm', ['publish'], { stdio: 'inherit' })
303 | .on('close', done);
304 | });
305 |
306 |
307 | /**
308 | * Deploy tasks
309 | */
310 |
311 | gulp.task('publish:examples', ['build:examples'], function() {
312 | return gulp.src(EXAMPLE_DIST_PATH + '/**/*').pipe(deploy());
313 | });
314 |
315 | gulp.task('release', ['publish:tag', 'publish:npm', 'publish:examples']);
316 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-image",
3 | "version": "0.1.0",
4 | "description": "Image Component for React",
5 | "main": "src/Image.js",
6 | "author": "Yuanyan Cao",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/yuanyan/react-image.git"
11 | },
12 | "dependencies": {
13 | "react": "^0.12.0",
14 | "reactify": "^0.17.0"
15 | },
16 | "devDependencies": {
17 | "browserify": "^6.3.3",
18 | "browserify-shim": "^3.8.0",
19 | "chalk": "^0.5.1",
20 | "codemirror": "^5.0.0",
21 | "del": "^0.1.3",
22 | "gulp": "^3.8.10",
23 | "gulp-bump": "^0.1.11",
24 | "gulp-connect": "^2.2.0",
25 | "gulp-gh-pages": "^0.4.0",
26 | "gulp-git": "^0.5.5",
27 | "gulp-less": "^1.3.6",
28 | "gulp-rename": "^1.2.0",
29 | "gulp-streamify": "^0.0.5",
30 | "gulp-uglify": "^1.0.1",
31 | "gulp-util": "^3.0.1",
32 | "merge-stream": "^0.1.6",
33 | "vinyl-source-stream": "^1.0.0",
34 | "watchify": "^2.1.1"
35 | },
36 | "browserify": {
37 | "transform": [
38 | "reactify"
39 | ]
40 | },
41 | "browserify-shim": {
42 | "react": "global:React"
43 | },
44 | "scripts": {
45 | "test": "echo \"no tests yet\" && exit 0"
46 | },
47 | "keywords": [
48 | "react",
49 | "react-component",
50 | "image",
51 | "img"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/src/Image.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var addEvent = (function () {
4 | if (document.addEventListener) {
5 | return function addStandardEventListener(el, eventName, fn) {
6 | return el.addEventListener(eventName, fn, false);
7 | };
8 | } else {
9 | return function addIEEventListener(el, eventName, fn) {
10 | return el.attachEvent('on' + eventName, fn);
11 | };
12 | }
13 | })();
14 |
15 | var removeEvent = (function(){
16 | if (document.addEventListener) {
17 | return function removeStandardEventListener(el, eventName, fn) {
18 | return el.removeEventListener(eventName, fn, false);
19 | };
20 | } else {
21 | return function removeIEEventListener(el, eventName, fn) {
22 | return el.detachEvent('on' + eventName, fn);
23 | };
24 | }
25 | })();
26 |
27 | function debounce(fn, wait) {
28 | var timeout;
29 | return function() {
30 | var context = this, args = arguments;
31 | var later = function() {
32 | timeout = null;
33 | fn.apply(context, args);
34 | };
35 |
36 | clearTimeout(timeout);
37 | timeout = setTimeout(later, wait);
38 | }
39 | }
40 |
41 | function compare(a, b, state, accessorFn) {
42 | var aDt = accessorFn(a) - state;
43 | var bDt = accessorFn(b) - state;
44 |
45 | if ((aDt === 0 && bDt !== 0) || // a perfectly matches target but b does not
46 | (bDt < 0 && aDt >= 0)) // b is less than target but a is the same or better
47 | {
48 | return a;
49 | }
50 |
51 | if ((bDt === 0 && aDt !== 0) || // b perfectly matches target but a does not
52 | (aDt < 0 && bDt >= 0)) // a is less than target but b is the same or better
53 | {
54 | return b;
55 | }
56 |
57 | if (Math.abs(aDt) < Math.abs(bDt))
58 | {
59 | return a;
60 | }
61 |
62 | if (Math.abs(bDt) < Math.abs(aDt))
63 | {
64 | return b;
65 | }
66 |
67 | return a;
68 | }
69 |
70 | // document.body.scrollTop was working in Chrome but didn't work on Firefox, so had to resort to window.pageYOffset
71 | // 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
72 | function getPageOffset(){
73 | return window.pageYOffset || document.documentElement.scrollTop;
74 | }
75 |
76 | function checkElementInViewport(element, viewportHeight, lazyOffset){
77 | var elementOffsetTop = 0;
78 | var offset = getPageOffset() + lazyOffset;
79 |
80 | if (element.offsetParent) {
81 | do {
82 | elementOffsetTop += element.offsetTop;
83 | }
84 | while (element = element.offsetParent);
85 | }
86 |
87 | return elementOffsetTop < (viewportHeight + offset);
88 | }
89 |
90 | var Image = React.createClass({
91 | nativeSupport: false,
92 | propTypes: {
93 | src: React.PropTypes.string.isRequired,
94 | srcSet: React.PropTypes.string,
95 | sizes: React.PropTypes.string,
96 | alt: React.PropTypes.string,
97 | width: React.PropTypes.number,
98 | height: React.PropTypes.number,
99 | fade: React.PropTypes.bool,
100 | placeholderSrc: React.PropTypes.string,
101 | lazyOffset: React.PropTypes.number,
102 | crossorigin: React.PropTypes.oneOf(['anonymous', 'use-credentials']),
103 | onLoad: React.PropTypes.func,
104 | onError: React.PropTypes.func
105 | },
106 |
107 | getDefaultProps: function() {
108 | return {
109 | onLoad: function(){},
110 | onError: function(){},
111 | lazyOffset: 0,
112 | placeholderSrc: 'data:image/gif;base64,R0lGODlhEAAJAIAAAP///wAAACH5BAEAAAAALAAAAAAQAAkAAAIKhI+py+0Po5yUFQA7'
113 | }
114 | },
115 |
116 | getInitialState: function() {
117 |
118 | if (typeof document !== 'undefined') {
119 | var img = document.createElement('img');
120 | this.nativeSupport = ('sizes' in img) && ('srcset' in img);
121 | }
122 |
123 | this.onViewportResize = debounce(this.onViewportResize, 150);
124 | this.onViewportScroll = debounce(this.onViewportScroll, 150);
125 |
126 | return {
127 | w: this.getViewportWidth(),
128 | h: this.getViewportHeight(),
129 | x: this.getDevicePixelRatio(),
130 | candidates: this.buildCandidates(this.props.srcSet)
131 | }
132 | },
133 |
134 | componentWillReceiveProps: function(nextProps) {
135 | if (nextProps && nextProps.srcSet) {
136 | this.setState({candidates: this.buildCandidates(nextProps.srcSet)});
137 | }
138 | },
139 |
140 | componentDidMount: function() {
141 |
142 | if (typeof window !== "undefined") {
143 |
144 | if(this.props.lazy) {
145 | this.onViewportScroll();
146 | addEvent(window, "scroll", this.onViewportScroll);
147 | }
148 |
149 | if (!this.nativeSupport) {
150 | addEvent(window, "resize", this.onViewportResize);
151 | }
152 | }
153 | },
154 |
155 | componentWillUnmount: function() {
156 |
157 | if (typeof window !== "undefined") {
158 |
159 | if(this.props.lazy) {
160 | removeEvent(window, "scroll", this.onViewportScroll);
161 | }
162 |
163 | if (!this.nativeSupport) {
164 | removeEvent(window, "resize", this.onViewportResize)
165 | }
166 | }
167 | },
168 |
169 | render: function() {
170 |
171 | if(this.props.lazy && !this.state.lazyloaded ) return this.renderPlaceholder();
172 |
173 | if (this.nativeSupport) return this.renderNative();
174 | return (
175 |
176 | );
177 | },
178 |
179 | renderPlaceholder: function(){
180 | return (
181 |
182 | )
183 | },
184 |
185 | renderNative: function() {
186 | return (
187 |
188 | );
189 | },
190 |
191 | /**
192 | * Takes a srcSet in the form of url/
193 | * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or
194 | * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or
195 | * "images/pic-small.png"
196 | * Get an array of image candidates in the form of
197 | * {url: "/foo/bar.png", x: 1, w:0, h: 0}
198 | * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value
199 | * If sizes is specified, resolution is calculated
200 | */
201 | buildCandidates: function(srcSet) {
202 | return srcSet.split(',').map(function(srcImg) {
203 | var stringComponents = srcImg.trim().split(' ');
204 | var candidate = {
205 | url: stringComponents[0].trim(),
206 | w: 0,
207 | h: 0,
208 | x: 1.0
209 | };
210 |
211 | for (var i = 1; i < stringComponents.length; i++) {
212 | var str = stringComponents[i].trim();
213 | if (str.indexOf('w', str.length - 1) !== -1) {
214 | candidate.w = parseInt(str.substring(0,str.length-1));
215 | } else if (str.indexOf('h', str.length-1) !== -1) {
216 | candidate.h = parseInt(str.substring(0,str.length-1));
217 | } else if (str.indexOf('x', str.length-1) !== -1) {
218 | candidate.x = parseFloat(str.substring(0,str.length-1));
219 | } else {
220 | console.warn('Invalid parameter passed to Image srcSet: [' + str + '] in ' + srcImg);
221 | }
222 | }
223 |
224 | return candidate;
225 | });
226 | },
227 |
228 | matchImage: function() {
229 | return this.state.candidates.reduce(function(a, b) {
230 | if (a.x === b.x) {
231 | // Both have the same density so attempt to find a better one using width
232 | if (a.w === b.w) {
233 | // Both have the same width so attempt to use height
234 | if (a.h === b.h) {
235 | return a; // hey, it came first!
236 | } else {
237 | return compare(a, b, this.state.h, function(img) { return img.h });
238 | }
239 | } else {
240 | return compare(a, b, this.state.w, function(img) { return img.w });
241 | }
242 | } else {
243 | return compare(a, b, this.state.x, function(img) { return img.x });
244 | }
245 | }.bind(this)).url;
246 | },
247 |
248 | onViewportResize: function() {
249 | // TODO: We need to time delay this, only update maybe once a second or 2
250 | this.setState({w: this.getViewportWidth(), h: this.getViewportHeight()});
251 | },
252 |
253 | onViewportScroll: function(){
254 | if(this.refs.placeholder && checkElementInViewport(this.refs.placeholder.getDOMNode(), this.getViewportHeight(), this.props.lazyOffset) ){
255 | this.setState({
256 | lazyloaded: true
257 | });
258 | }
259 | },
260 |
261 | getViewportWidth: function() {
262 | if (typeof window !== 'undefined') {
263 | return window.innerWidth || document.documentElement.clientWidth;
264 | } else {
265 | return 0;
266 | }
267 | },
268 |
269 | getViewportHeight: function() {
270 | if (typeof window !== 'undefined') {
271 | return window.innerHeight || document.documentElement.clientHeight;
272 | } else {
273 | return 0;
274 | }
275 | },
276 |
277 | getDevicePixelRatio: function() {
278 | if (typeof window !== 'undefined') {
279 | return window.devicePixelRatio;
280 | } else {
281 | return 1;
282 | }
283 | }
284 |
285 | });
286 |
287 | module.exports = Image;
288 |
--------------------------------------------------------------------------------