1 | (function () {
|
2 | var unique_sockjs_string = '_connect_to_statebus_'
|
3 |
|
4 | window.dom = window.dom || new Proxy({}, {
|
5 | get: function (o, k) { return o[k] },
|
6 | set: function (o, k, v) {
|
7 | o[k] = v
|
8 | make_component(k, v)
|
9 | return true
|
10 | }
|
11 | })
|
12 |
|
13 |
|
14 |
|
15 | function set_cookie (key, val) {
|
16 | document.cookie = key + '=' + val + '; Expires=21 Oct 2025 00:0:00 GMT;'
|
17 | }
|
18 | function get_cookie (key) {
|
19 | var c = document.cookie.match('(^|;)\\s*' + key + '\\s*=\\s*([^;]+)');
|
20 | return c ? c.pop() : '';
|
21 | }
|
22 | try { document.cookie } catch (e) {get_cookie = set_cookie = function (){}}
|
23 | function make_websocket (url) {
|
24 | if (!url.match(/^\w{0,7}:\/\//))
|
25 | url = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port : '') + url
|
26 |
|
27 | url = url.replace(/^state:\/\//, 'wss://')
|
28 | url = url.replace(/^istate:\/\//, 'ws://')
|
29 | url = url.replace(/^statei:\/\//, 'ws://')
|
30 |
|
31 | url = url.replace(/^https:\/\//, 'wss://')
|
32 | url = url.replace(/^http:\/\//, 'ws://')
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | console.log('opening websocket to', url)
|
40 | return new WebSocket(url + '/' + unique_sockjs_string + '/websocket')
|
41 |
|
42 | }
|
43 | function client_creds (server_url) {
|
44 | var me = bus.fetch('ls/me')
|
45 | bus.log('connect: me is', me)
|
46 | if (!me.client) {
|
47 |
|
48 |
|
49 | var c = get_cookie('client')
|
50 | me.client = c || (Math.random().toString(36).substring(2)
|
51 | + Math.random().toString(36).substring(2)
|
52 | + Math.random().toString(36).substring(2))
|
53 | bus.save(me)
|
54 | }
|
55 |
|
56 | set_cookie('client', me.client)
|
57 | return {clientid: me.client}
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | function localstorage_client (prefix) {
|
64 | try { localStorage } catch (e) { return }
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | var bus = this
|
70 | bus.log(this)
|
71 |
|
72 |
|
73 |
|
74 | var saves_are_pending = false
|
75 | var pending_saves = {}
|
76 |
|
77 | function save_the_pending_saves() {
|
78 | bus.log('localstore: saving', pending_saves)
|
79 | for (var k in pending_saves)
|
80 | localStorage.setItem(k, JSON.stringify(pending_saves[k]))
|
81 | saves_are_pending = false
|
82 | }
|
83 |
|
84 | bus(prefix).to_fetch = function (key) {
|
85 | var result = localStorage.getItem(key)
|
86 | return result ? JSON.parse(result) : {key: key}
|
87 | }
|
88 | bus(prefix).to_save = function (obj) {
|
89 |
|
90 | bus.log('localStore: on_save:', obj.key)
|
91 | pending_saves[obj.key] = obj
|
92 | if (!saves_are_pending) {
|
93 | setTimeout(save_the_pending_saves, 50)
|
94 | saves_are_pending = true
|
95 | }
|
96 | bus.save.fire(obj)
|
97 | return obj
|
98 | }
|
99 | bus(prefix).to_delete = function (key) { localStorage.removeItem(key) }
|
100 |
|
101 |
|
102 |
|
103 | function update (event) {
|
104 | bus.log('Got a localstorage update', event)
|
105 |
|
106 | }
|
107 | if (window.addEventListener) window.addEventListener("storage", update, false)
|
108 | else window.attachEvent("onstorage", update)
|
109 | }
|
110 |
|
111 |
|
112 | function url_store (prefix) {
|
113 | var bus = this
|
114 | function get_query_string_value (key) {
|
115 | return unescape(window.location.search.replace(
|
116 | new RegExp("^(?:.*[&\\?]"
|
117 | + escape(key).replace(/[\.\+\*]/g, "\\$&")
|
118 | + "(?:\\=([^&]*))?)?.*$", "i"),
|
119 | "$1"))
|
120 | }
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | var data = get_query_string_value(key)
|
126 | data = (data && JSON.parse(data)) || {key : key}
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | bus(prefix).to_save = function (obj) {
|
132 | window.history.replaceState(
|
133 | '',
|
134 | '',
|
135 | document.location.origin
|
136 | + document.location.pathname
|
137 | + escape('?'+key+'='+JSON.stringify(obj)))
|
138 | bus.save.fire(obj)
|
139 | }
|
140 | }
|
141 |
|
142 | function live_reload_from (prefix) {
|
143 | if (!window.live_reload_initialized) {
|
144 | var first_time = true
|
145 | this(function () {
|
146 | var re = new RegExp(".*/" + prefix + "/(.*)")
|
147 | var file = window.location.href.match(re)[1]
|
148 | var code = bus.fetch('/code/invisible.college/' + file).code
|
149 | if (!code) return
|
150 | if (first_time) {first_time = false; return}
|
151 | var old_scroll_position = window.pageYOffset
|
152 | document.body.innerHTML = code
|
153 | var i = 0
|
154 | var d = 100
|
155 | var interval = setInterval(function () {
|
156 | if (i > 500) clearInterval(interval)
|
157 | i += d
|
158 | window.scrollTo(0, old_scroll_position)
|
159 | }, d)
|
160 | })
|
161 | window.live_reload_initialized = true
|
162 | }
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | var components = {}
|
173 | var components_count = 0
|
174 | var dirty_components = {}
|
175 | function React_View(component) {
|
176 | function wrap(name, new_func) {
|
177 | var old_func = component[name]
|
178 | component[name] = function wrapper () { return new_func.bind(this)(old_func) }
|
179 | }
|
180 |
|
181 |
|
182 | wrap('componentWillMount', function new_cwm (orig_func) {
|
183 | if (component.displayName === undefined)
|
184 | throw 'Component needs a displayName'
|
185 | this.name = component.displayName.toLowerCase().replace(' ', '_')
|
186 | this.key = 'component/' + components_count++
|
187 | components[this.key] = this
|
188 |
|
189 | function add_shortcut (obj, shortcut_name, to_key) {
|
190 | delete obj[shortcut_name]
|
191 | Object.defineProperty(obj, shortcut_name, {
|
192 | get: function () { return bus.fetch(to_key) },
|
193 | configurable: true })
|
194 | }
|
195 | add_shortcut(this, 'local', this.key)
|
196 |
|
197 | orig_func && orig_func.apply(this, arguments)
|
198 |
|
199 |
|
200 | var orig_render = this.render
|
201 | this.render = bus.reactive(function () {
|
202 | console.assert(this !== window)
|
203 | if (this.render.called_directly) {
|
204 | delete dirty_components[this.key]
|
205 |
|
206 |
|
207 | for (var k in this.props)
|
208 | if (this.props.hasOwnProperty(k)
|
209 | && this.props[k] !== null
|
210 | && typeof this.props[k] === 'object'
|
211 | && this.props[k].key)
|
212 |
|
213 | bus.fetch(this.props[k].key)
|
214 |
|
215 |
|
216 | return orig_render.apply(this, arguments)
|
217 | } else {
|
218 | dirty_components[this.key] = true
|
219 | schedule_re_render()
|
220 | }
|
221 | })
|
222 | })
|
223 |
|
224 | wrap('componentWillUnmount', function new_cwu (orig_func) {
|
225 | orig_func && orig_func.apply(this, arguments)
|
226 |
|
227 | bus.delete(this.key)
|
228 | delete components[this.key]
|
229 | delete dirty_components[this.key]
|
230 | })
|
231 |
|
232 | function shallow_clone(original) {
|
233 | var clone = Object.create(Object.getPrototypeOf(original))
|
234 | var i, keys = Object.getOwnPropertyNames(original)
|
235 | for (i=0; i < keys.length; i++){
|
236 | Object.defineProperty(clone, keys[i],
|
237 | Object.getOwnPropertyDescriptor(original, keys[i])
|
238 | )
|
239 | }
|
240 | return clone
|
241 | }
|
242 |
|
243 | component.shouldComponentUpdate = function new_scu (next_props, next_state) {
|
244 |
|
245 | if (dirty_components[this.key] !== undefined) return true
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | next_props = shallow_clone(next_props)
|
251 | this_props = shallow_clone(this.props)
|
252 |
|
253 | delete next_props['children']; delete this_props['children']
|
254 |
|
255 |
|
256 | next_props = bus.clone(next_props)
|
257 | this_props = bus.clone(this_props)
|
258 |
|
259 |
|
260 | return !bus.deep_equals([next_state, next_props], [this.state, this_props])
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | }
|
271 |
|
272 | component.loading = function loading () {
|
273 | return this.render.loading()
|
274 | }
|
275 |
|
276 |
|
277 |
|
278 | var react_class = React.createClass(component)
|
279 | var result = function (props, children) {
|
280 | props = props || {}
|
281 | props['data-key'] = props.key
|
282 | props['data-widget'] = component.displayName
|
283 |
|
284 | return (React.version >= '0.12.'
|
285 | ? React.createElement(react_class, props, children)
|
286 | : react_class(props, children))
|
287 | }
|
288 |
|
289 |
|
290 | result.prototype = react_class.prototype
|
291 | return result
|
292 | }
|
293 | window.React_View = React_View
|
294 | if (window.statebus) window.statebus.create_react_class = window.statebus.createReactClass = React_View
|
295 |
|
296 |
|
297 |
|
298 | var re_render_scheduled = false
|
299 | re_rendering = false
|
300 | function schedule_re_render() {
|
301 | if (!re_render_scheduled) {
|
302 | requestAnimationFrame(function () {
|
303 | re_render_scheduled = false
|
304 |
|
305 |
|
306 | for (var comp_key in dirty_components) {
|
307 | if (dirty_components[comp_key]
|
308 | && components[comp_key])
|
309 |
|
310 | try {
|
311 | re_rendering = true
|
312 | components[comp_key].forceUpdate()
|
313 | } finally {
|
314 | re_rendering = false
|
315 | }
|
316 | }
|
317 | })
|
318 | re_render_scheduled = true
|
319 | }
|
320 | }
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | function make_client_statebus_maker () {
|
328 | var extra_stuff = ['localstorage_client make_websocket client_creds',
|
329 | 'url_store components live_reload_from'].join(' ').split(' ')
|
330 | if (window.statebus) {
|
331 | var orig_statebus = statebus
|
332 | window.statebus = function make_client_bus () {
|
333 | var bus = orig_statebus()
|
334 | for (var i=0; i<extra_stuff.length; i++)
|
335 | bus[extra_stuff[i]] = eval(extra_stuff[i])
|
336 | bus.localstorage_client('ls/*')
|
337 | return bus
|
338 | }
|
339 | }
|
340 | }
|
341 |
|
342 | function load_scripts() {
|
343 |
|
344 | if (!window.statebus) {
|
345 | var statebus_dir = clientjs_option('src')
|
346 | if (statebus_dir) statebus_dir = statebus_dir.match(/(.*)[\/\\]/)
|
347 | if (statebus_dir) statebus_dir = statebus_dir[1] + '/'
|
348 | else statebus_dir = ''
|
349 |
|
350 | var js_urls = {
|
351 | react: statebus_dir + 'extras/react.js',
|
352 | sockjs: statebus_dir + 'extras/sockjs.js',
|
353 | coffee: statebus_dir + 'extras/coffee.js',
|
354 | statebus: statebus_dir + 'statebus.js'
|
355 | }
|
356 | if (statebus_dir == 'https://stateb.us/')
|
357 | js_urls.statebus = statebus_dir + 'statebus4.js'
|
358 |
|
359 | for (var name in js_urls)
|
360 | document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
|
361 |
|
362 | document.addEventListener('DOMContentLoaded', scripts_ready, false)
|
363 | }
|
364 | else
|
365 | scripts_ready()
|
366 | }
|
367 |
|
368 | function clientjs_option (option_name) {
|
369 |
|
370 |
|
371 | var script_elem = (
|
372 | document.querySelector('script[src*="/client"][src$=".js"]') ||
|
373 | document.querySelector('script[src^="client"][src$=".js"]'))
|
374 | return script_elem && script_elem.getAttribute(option_name)
|
375 | }
|
376 | var loaded_from_file_url = window.location.href.match(/^file:\/\//)
|
377 | window.statebus_server = window.statebus_server || clientjs_option('server') ||
|
378 | (loaded_from_file_url ? 'https://stateb.us:3006' : '/')
|
379 | window.statebus_backdoor = window.statebus_backdoor || clientjs_option('backdoor')
|
380 | var react_render
|
381 | function scripts_ready () {
|
382 | react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
|
383 | make_client_statebus_maker()
|
384 | window.bus = window.statebus()
|
385 | window.bus.label = 'bus'
|
386 | window.sb = bus.sb
|
387 | statebus.widget = React_View
|
388 | statebus.create_react_class = React_View
|
389 | statebus.createReactClass = React_View
|
390 |
|
391 | improve_react()
|
392 | window.ignore_flashbacks = false
|
393 | if (statebus_server !== 'none')
|
394 | bus.net_mount ('/*', statebus_server)
|
395 |
|
396 | if (window.statebus_backdoor) {
|
397 | window.master = statebus()
|
398 | master.net_mount('*', statebus_backdoor)
|
399 | }
|
400 | bus.net_automount()
|
401 |
|
402 |
|
403 | bus('/new/*').to_save = function (o) {
|
404 | if (o.key.split('/').length > 3) return
|
405 |
|
406 | var old_key = o.key
|
407 | o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
|
408 | statebus.cache[o.key] = o
|
409 | delete statebus.cache[old_key]
|
410 | bus.save(o)
|
411 | }
|
412 | load_coffee()
|
413 |
|
414 | statebus.compile_coffee = compile_coffee
|
415 | statebus.load_client_code = load_client_code
|
416 | statebus.load_widgets = load_widgets
|
417 |
|
418 | document.addEventListener('DOMContentLoaded', function () {
|
419 | if (window.statebus_ready)
|
420 | for (var i=0; i<statebus_ready.length; i++)
|
421 | statebus_ready[i]()
|
422 | }, false)
|
423 |
|
424 | document.addEventListener('DOMContentLoaded', load_widgets, false)
|
425 |
|
426 |
|
427 |
|
428 | }
|
429 |
|
430 | function improve_react() {
|
431 | function capitalize (s) {return s[0].toUpperCase() + s.slice(1)}
|
432 | function camelcase (s) { var a = s.split(/[_-]/)
|
433 | return a.slice(0,1).concat(a.slice(1).map(capitalize)).join('') }
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 | var all_css_props = ["alignContent","alignItems","alignSelf","alignmentBaseline","all","animation","animationDelay","animationDirection","animationDuration","animationFillMode","animationIterationCount","animationName","animationPlayState","animationTimingFunction","backfaceVisibility","background","backgroundAttachment","backgroundBlendMode","backgroundClip","backgroundColor","backgroundImage","backgroundOrigin","backgroundPosition","backgroundPositionX","backgroundPositionY","backgroundRepeat","backgroundRepeatX","backgroundRepeatY","backgroundSize","baselineShift","blockSize","border","borderBottom","borderBottomColor","borderBottomLeftRadius","borderBottomRightRadius","borderBottomStyle","borderBottomWidth","borderCollapse","borderColor","borderImage","borderImageOutset","borderImageRepeat","borderImageSlice","borderImageSource","borderImageWidth","borderLeft","borderLeftColor","borderLeftStyle","borderLeftWidth","borderRadius","borderRight","borderRightColor","borderRightStyle","borderRightWidth","borderSpacing","borderStyle","borderTop","borderTopColor","borderTopLeftRadius","borderTopRightRadius","borderTopStyle","borderTopWidth","borderWidth","bottom","boxShadow","boxSizing","breakAfter","breakBefore","breakInside","bufferedRendering","captionSide","caretColor","clear","clip","clipPath","clipRule","color","colorInterpolation","colorInterpolationFilters","colorRendering","columnCount","columnFill","columnGap","columnRule","columnRuleColor","columnRuleStyle","columnRuleWidth","columnSpan","columnWidth","columns","contain","content","counterIncrement","counterReset","cursor","cx","cy","d","direction","display","dominantBaseline","emptyCells","fill","fillOpacity","fillRule","filter","flex","flexBasis","flexDirection","flexFlow","flexGrow","flexShrink","flexWrap","float","floodColor","floodOpacity","font","fontDisplay","fontFamily","fontFeatureSettings","fontKerning","fontSize","fontStretch","fontStyle","fontVariant","fontVariantCaps","fontVariantEastAsian","fontVariantLigatures","fontVariantNumeric","fontVariationSettings","fontWeight","gap","grid","gridArea","gridAutoColumns","gridAutoFlow","gridAutoRows","gridColumn","gridColumnEnd","gridColumnGap","gridColumnStart","gridGap","gridRow","gridRowEnd","gridRowGap","gridRowStart","gridTemplate","gridTemplateAreas","gridTemplateColumns","gridTemplateRows","height","hyphens","imageRendering","inlineSize","isolation","justifyContent","justifyItems","justifySelf","left","letterSpacing","lightingColor","lineBreak","lineHeight","listStyle","listStyleImage","listStylePosition","listStyleType","margin","marginBottom","marginLeft","marginRight","marginTop","marker","markerEnd","markerMid","markerStart","mask","maskType","maxBlockSize","maxHeight","maxInlineSize","maxWidth","maxZoom","minBlockSize","minHeight","minInlineSize","minWidth","minZoom","mixBlendMode","objectFit","objectPosition","offset","offsetDistance","offsetPath","offsetRotate","opacity","order","orientation","orphans","outline","outlineColor","outlineOffset","outlineStyle","outlineWidth","overflow","overflowAnchor","overflowWrap","overflowX","overflowY","overscrollBehavior","overscrollBehaviorX","overscrollBehaviorY","padding","paddingBottom","paddingLeft","paddingRight","paddingTop","page","pageBreakAfter","pageBreakBefore","pageBreakInside","paintOrder","perspective","perspectiveOrigin","placeContent","placeItems","placeSelf","pointerEvents","position","quotes","r","resize","right","rowGap","rx","ry","scrollBehavior","shapeImageThreshold","shapeMargin","shapeOutside","shapeRendering","size","speak","src","stopColor","stopOpacity","stroke","strokeDasharray","strokeDashoffset","strokeLinecap","strokeLinejoin","strokeMiterlimit","strokeOpacity","strokeWidth","tabSize","tableLayout","textAlign","textAlignLast","textAnchor","textCombineUpright","textDecoration","textDecorationColor","textDecorationLine","textDecorationSkipInk","textDecorationStyle","textIndent","textOrientation","textOverflow","textRendering","textShadow","textSizeAdjust","textTransform","textUnderlinePosition","top","touchAction","transform","transformBox","transformOrigin","transformStyle","transition","transitionDelay","transitionDuration","transitionProperty","transitionTimingFunction","unicodeBidi","unicodeRange","userSelect","userZoom","vectorEffect","verticalAlign","visibility","webkitAlignContent","webkitAlignItems","webkitAlignSelf","webkitAnimation","webkitAnimationDelay","webkitAnimationDirection","webkitAnimationDuration","webkitAnimationFillMode","webkitAnimationIterationCount","webkitAnimationName","webkitAnimationPlayState","webkitAnimationTimingFunction","webkitAppRegion","webkitAppearance","webkitBackfaceVisibility","webkitBackgroundClip","webkitBackgroundOrigin","webkitBackgroundSize","webkitBorderAfter","webkitBorderAfterColor","webkitBorderAfterStyle","webkitBorderAfterWidth","webkitBorderBefore","webkitBorderBeforeColor","webkitBorderBeforeStyle","webkitBorderBeforeWidth","webkitBorderBottomLeftRadius","webkitBorderBottomRightRadius","webkitBorderEnd","webkitBorderEndColor","webkitBorderEndStyle","webkitBorderEndWidth","webkitBorderHorizontalSpacing","webkitBorderImage","webkitBorderRadius","webkitBorderStart","webkitBorderStartColor","webkitBorderStartStyle","webkitBorderStartWidth","webkitBorderTopLeftRadius","webkitBorderTopRightRadius","webkitBorderVerticalSpacing","webkitBoxAlign","webkitBoxDecorationBreak","webkitBoxDirection","webkitBoxFlex","webkitBoxOrdinalGroup","webkitBoxOrient","webkitBoxPack","webkitBoxReflect","webkitBoxShadow","webkitBoxSizing","webkitClipPath","webkitColumnBreakAfter","webkitColumnBreakBefore","webkitColumnBreakInside","webkitColumnCount","webkitColumnGap","webkitColumnRule","webkitColumnRuleColor","webkitColumnRuleStyle","webkitColumnRuleWidth","webkitColumnSpan","webkitColumnWidth","webkitColumns","webkitFilter","webkitFlex","webkitFlexBasis","webkitFlexDirection","webkitFlexFlow","webkitFlexGrow","webkitFlexShrink","webkitFlexWrap","webkitFontFeatureSettings","webkitFontSizeDelta","webkitFontSmoothing","webkitHighlight","webkitHyphenateCharacter","webkitJustifyContent","webkitLineBreak","webkitLineClamp","webkitLocale","webkitLogicalHeight","webkitLogicalWidth","webkitMarginAfter","webkitMarginAfterCollapse","webkitMarginBefore","webkitMarginBeforeCollapse","webkitMarginBottomCollapse","webkitMarginCollapse","webkitMarginEnd","webkitMarginStart","webkitMarginTopCollapse","webkitMask","webkitMaskBoxImage","webkitMaskBoxImageOutset","webkitMaskBoxImageRepeat","webkitMaskBoxImageSlice","webkitMaskBoxImageSource","webkitMaskBoxImageWidth","webkitMaskClip","webkitMaskComposite","webkitMaskImage","webkitMaskOrigin","webkitMaskPosition","webkitMaskPositionX","webkitMaskPositionY","webkitMaskRepeat","webkitMaskRepeatX","webkitMaskRepeatY","webkitMaskSize","webkitMaxLogicalHeight","webkitMaxLogicalWidth","webkitMinLogicalHeight","webkitMinLogicalWidth","webkitOpacity","webkitOrder","webkitPaddingAfter","webkitPaddingBefore","webkitPaddingEnd","webkitPaddingStart","webkitPerspective","webkitPerspectiveOrigin","webkitPerspectiveOriginX","webkitPerspectiveOriginY","webkitPrintColorAdjust","webkitRtlOrdering","webkitRubyPosition","webkitShapeImageThreshold","webkitShapeMargin","webkitShapeOutside","webkitTapHighlightColor","webkitTextCombine","webkitTextDecorationsInEffect","webkitTextEmphasis","webkitTextEmphasisColor","webkitTextEmphasisPosition","webkitTextEmphasisStyle","webkitTextFillColor","webkitTextOrientation","webkitTextSecurity","webkitTextSizeAdjust","webkitTextStroke","webkitTextStrokeColor","webkitTextStrokeWidth","webkitTransform","webkitTransformOrigin","webkitTransformOriginX","webkitTransformOriginY","webkitTransformOriginZ","webkitTransformStyle","webkitTransition","webkitTransitionDelay","webkitTransitionDuration","webkitTransitionProperty","webkitTransitionTimingFunction","webkitUserDrag","webkitUserModify","webkitUserSelect","webkitWritingMode","whiteSpace","widows","width","willChange","wordBreak","wordSpacing","wordWrap","writingMode","x","y","zIndex","zoom"]
|
443 |
|
444 | var ignore = {d:1, cx:1, cy:1, rx:1, ry:1, x:1, y:1,
|
445 | content:1, fill:1, stroke:1, src:1}
|
446 | var is_css_prop = {}
|
447 | for (var i=0; i<all_css_props.length; i++)
|
448 | if (!ignore[all_css_props[i]])
|
449 | is_css_prop[all_css_props[i]] = true
|
450 |
|
451 | function better_element(el) {
|
452 |
|
453 |
|
454 |
|
455 |
|
456 | return function () {
|
457 | var children = []
|
458 | var attrs = {style: {}}
|
459 |
|
460 | for (var i=0; i<arguments.length; i++) {
|
461 | var arg = arguments[i]
|
462 |
|
463 |
|
464 | if (typeof arg === 'string'
|
465 | || arg instanceof String
|
466 | || arg && React.isValidElement(arg)
|
467 | || arg === undefined)
|
468 | children.push(arg)
|
469 |
|
470 |
|
471 | else if (arg instanceof Array)
|
472 | Array.prototype.push.apply(children, arg)
|
473 |
|
474 |
|
475 |
|
476 | else if (arg instanceof Object)
|
477 | for (var k in arg)
|
478 | if (is_css_prop[k]
|
479 | && !(k in {width:1,height:1,size:1}
|
480 | && el in {canvas:1, input:1, embed:1, object:1}))
|
481 | attrs.style[k] = arg[k]
|
482 | else if (k === 'style')
|
483 | for (var k2 in arg[k])
|
484 | attrs.style[k2] = arg[k][k2]
|
485 | else {
|
486 | attrs[k] = arg[k]
|
487 |
|
488 | if (k === 'key')
|
489 | attrs['data-key'] = arg[k]
|
490 | }
|
491 | }
|
492 | if (children.length === 0) children = undefined
|
493 | if (attrs['ref'] === 'input')
|
494 | bus.log(attrs, children)
|
495 | return React.DOM[el](attrs, children)
|
496 | }
|
497 | }
|
498 | for (var el in React.DOM)
|
499 | window[el.toUpperCase()] = better_element(el)
|
500 |
|
501 | function make_better_input (name, element) {
|
502 | window[name] = React.createFactory(React.createClass({
|
503 | getInitialState: function() {
|
504 | return {value: this.props.value}
|
505 | },
|
506 | componentWillReceiveProps: function(new_props) {
|
507 | this.setState({value: new_props.value})
|
508 | },
|
509 | onChange: function(e) {
|
510 | this.props.onChange && this.props.onChange(e)
|
511 | if (this.props.value)
|
512 | this.setState({value: e.target.value})
|
513 | },
|
514 | render: function() {
|
515 | var new_props = {}
|
516 | for (var k in this.props)
|
517 | if (this.props.hasOwnProperty(k))
|
518 | new_props[k] = this.props[k]
|
519 | if (this.state.value) new_props.value = this.state.value
|
520 | new_props.onChange = this.onChange
|
521 | return element(new_props)
|
522 | }
|
523 | }))
|
524 | }
|
525 |
|
526 | make_better_input("INPUT", window.INPUT)
|
527 | make_better_input("TEXTAREA", window.TEXTAREA)
|
528 | make_syncarea()
|
529 |
|
530 |
|
531 | var og_img = window.IMG
|
532 | window.IMG = function () {
|
533 | var args = []
|
534 | for (var i=0; i<arguments.length; i++) {
|
535 | args.push(arguments[i])
|
536 | if (arguments[i].state)
|
537 | args[i].src = 'data:;base64,' + fetch(args[i].state)._
|
538 | }
|
539 | return og_img.apply(this, args)
|
540 | }
|
541 |
|
542 |
|
543 |
|
544 |
|
545 |
|
546 | function escape_html (s) {
|
547 |
|
548 | return s.replace(/</g, "<").replace(/>/g, ">")
|
549 | }
|
550 | window.STYLE = function (s) {
|
551 | return React.DOM.style({dangerouslySetInnerHTML: {__html: escape_html(s)}})
|
552 | }
|
553 | window.TITLE = function (s) {
|
554 | return React.DOM.title({dangerouslySetInnerHTML: {__html: escape_html(s)}})
|
555 | }
|
556 | }
|
557 |
|
558 | function autodetect_args (func) {
|
559 | if (func.args) return
|
560 |
|
561 |
|
562 | var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,
|
563 | params = /([^\s,]+)/g,
|
564 | s = func.toString().replace(comments, '')
|
565 | func.args = s.slice(s.indexOf('(')+1, s.indexOf(')')).match(params) || []
|
566 | }
|
567 |
|
568 |
|
569 |
|
570 | var users_widgets = {}
|
571 | function make_component(name, func) {
|
572 |
|
573 |
|
574 | window[name] = users_widgets[name] = window.React_View({
|
575 | displayName: name,
|
576 | render: function () {
|
577 | var args = []
|
578 |
|
579 |
|
580 | autodetect_args(func)
|
581 |
|
582 | for (var i=0; i<func.args.length; i++)
|
583 | args.push(this.props[func.args[i]])
|
584 |
|
585 |
|
586 | var vdom = func.apply(this, args)
|
587 |
|
588 |
|
589 |
|
590 |
|
591 | if (vdom && vdom.props) {
|
592 | vdom.props['data-widget'] = name
|
593 | vdom.props['data-key'] = this.props['data-key']
|
594 | }
|
595 |
|
596 |
|
597 | if (!React.isValidElement(vdom))
|
598 |
|
599 | vdom = React.DOM.span(null, (typeof vdom === 'string')
|
600 | ? vdom : JSON.stringify(vdom))
|
601 | return vdom
|
602 | },
|
603 | componentDidMount: function () {
|
604 | var refresh = func.refresh
|
605 | refresh && refresh.bind(this)()
|
606 | },
|
607 | componentWillUnmount: function () {
|
608 | var down = func.down
|
609 | return down && down.bind(this)()
|
610 | },
|
611 | componentDidUpdate: function () {
|
612 | if (!this.initial_render_complete && !this.loading()) {
|
613 | this.initial_render_complete = true
|
614 | var up = func.up
|
615 | up && up.bind(this)()
|
616 | }
|
617 | var refresh = func.refresh
|
618 | return refresh && refresh.bind(this)()
|
619 | },
|
620 | getInitialState: function () { return {} }
|
621 | })
|
622 | }
|
623 |
|
624 | function make_syncarea () {
|
625 |
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 |
|
632 |
|
633 | window['SYNCAREA'] = users_widgets['SYNCAREA'] = React.createClass({
|
634 | getInitialState : function () {
|
635 | return { cursor_positions : {} }
|
636 | },
|
637 | on_text_changed : function () {
|
638 | if (this.props.autosize) {
|
639 | var t = this.textarea_ref
|
640 | t.style.height = null
|
641 | while (t.rows > 1 && t.scrollHeight < t.offsetHeight) t.rows--
|
642 | while (t.scrollHeight > t.offsetHeight) t.rows++
|
643 | }
|
644 | },
|
645 | componentDidMount : function () {
|
646 | var self = this
|
647 | self.on_ranges = function (ranges) {
|
648 | self.ranges = ranges
|
649 | var cursor_positions = {}
|
650 | Object.keys(ranges).forEach(function (k) {
|
651 | var r = ranges[k]
|
652 | var xy = getCaretCoordinates(self.textarea_ref, r[0])
|
653 | var x = self.textarea_ref.offsetLeft - self.textarea_ref.scrollLeft + xy.left + 'px'
|
654 | var y = self.textarea_ref.offsetTop - self.textarea_ref.scrollTop + xy.top + 'px'
|
655 | cursor_positions[k] = [x, y]
|
656 | })
|
657 | self.setState({ cursor_positions : cursor_positions })
|
658 | }
|
659 |
|
660 | this.ds = diffsync.create_client({
|
661 | ws_url : this.props.ws_url,
|
662 | channel : this.props.channel,
|
663 | get_text : function () {
|
664 | return self.textarea_ref.value
|
665 | },
|
666 | get_range : function () {
|
667 | var t = self.textarea_ref
|
668 | return [t.selectionStart, t.selectionEnd]
|
669 | },
|
670 | on_text : function (s, range) {
|
671 | self.textarea_ref.value = s
|
672 | self.textarea_ref.setSelectionRange(range[0], range[1])
|
673 | self.on_text_changed()
|
674 | },
|
675 | on_ranges : this.on_ranges
|
676 | })
|
677 | },
|
678 | render : function () {
|
679 | var self = this
|
680 | var cursors = []
|
681 | Object.keys(this.state.cursor_positions).forEach(function (k) {
|
682 | var p = self.state.cursor_positions[k]
|
683 | var style = {
|
684 | position : 'absolute',
|
685 | left : p[0],
|
686 | top : p[1]
|
687 | }
|
688 | Object.keys(self.props.cursor_style).forEach(function (k) {
|
689 | style[k] = self.props.cursor_style[k]
|
690 | })
|
691 | cursors.push(React.createElement('div', {
|
692 | key : k,
|
693 | style : style
|
694 | }))
|
695 | })
|
696 | return React.createElement('div', {
|
697 | style : {
|
698 | clipPath : 'inset(0px 0px 0px 0px)'
|
699 | },
|
700 | }, React.createElement('textarea', {
|
701 | ref : function (t) { self.textarea_ref = t },
|
702 | style : this.props.textarea_style,
|
703 | onChange : function (e) {
|
704 | self.ds.on_change()
|
705 | self.on_text_changed()
|
706 | },
|
707 | onMouseDown : function () {
|
708 | setTimeout(function () { self.ds.on_change() }, 0)
|
709 | },
|
710 | onKeyDown : function () {
|
711 | setTimeout(function () { self.ds.on_change() }, 0)
|
712 | },
|
713 | onScroll : function () {
|
714 | self.on_ranges(self.ranges)
|
715 | }
|
716 | }), cursors)
|
717 | }
|
718 | })
|
719 | }
|
720 |
|
721 | function compile_coffee (coffee, filename) {
|
722 | var compiled
|
723 | try {
|
724 | compiled = CoffeeScript.compile(coffee,
|
725 | {bare: true,
|
726 | sourceMap: true,
|
727 | filename: filename})
|
728 | var source_map = JSON.parse(compiled.v3SourceMap)
|
729 | source_map.sourcesContent = coffee
|
730 | compiled = compiled.js
|
731 |
|
732 |
|
733 | try {
|
734 | compiled += '\n'
|
735 | compiled += '//# sourceMappingURL=data:application/json;base64,'
|
736 | compiled += btoa(JSON.stringify(source_map)) + '\n'
|
737 | compiled += '//# sourceURL=' + filename
|
738 | } catch (e) {}
|
739 |
|
740 | } catch (error) {
|
741 | if (error.location)
|
742 | console.error('Syntax error in '+ filename + ' on line',
|
743 | error.location.first_line
|
744 | + ', column ' + error.location.first_column + ':',
|
745 | error.message)
|
746 | else throw error
|
747 | }
|
748 | return compiled
|
749 | }
|
750 | function load_client_code (code) {
|
751 | var dom = {}, ui = {}
|
752 | if (code) eval(code)
|
753 | else { dom = window.dom; ui = window.ui }
|
754 | for (var k in ui) dom[k] = dom[k] || ui[k]
|
755 | for (var widget_name in dom)
|
756 | window.dom[widget_name] = dom[widget_name]
|
757 | }
|
758 | function load_coffee () {
|
759 | load_client_code()
|
760 | var scripts = document.getElementsByTagName("script")
|
761 | var filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1)
|
762 | for (var i=0; i<scripts.length; i++)
|
763 | if (scripts[i].getAttribute('type')
|
764 | in {'statebus':1, 'coffeedom':1,'statebus-js':1,
|
765 | 'coffee':1, 'coffeescript':1}) {
|
766 |
|
767 | var compiled = scripts[i].text
|
768 | if (scripts[i].getAttribute('type') !== 'statebus-js')
|
769 | compiled = compile_coffee(scripts[i].text, filename)
|
770 | if (compiled)
|
771 | load_client_code(compiled)
|
772 | }
|
773 | }
|
774 |
|
775 | function dom_to_widget (node) {
|
776 | if (node.nodeName === '#text') return node.textContent
|
777 | if (!(node.nodeName in users_widgets)) return node
|
778 |
|
779 | node.seen = true
|
780 | var children = [], props = {}
|
781 |
|
782 | for (var i=0; i<node.childNodes.length; i++)
|
783 | children.push(dom_to_widget(node.childNodes[i]))
|
784 |
|
785 |
|
786 | var props = {}
|
787 | for (var i=0; node.attributes && i<node.attributes.length; i++)
|
788 | props[node.attributes[i].name] = node.attributes[i].value
|
789 |
|
790 | var widge = (window[node.nodeName.toLowerCase()]
|
791 | || window[node.nodeName.toUpperCase()])
|
792 | console.assert(widge, node.nodeName + ' has not been defined as a UI widget.')
|
793 |
|
794 | return widge(props, children)
|
795 | }
|
796 |
|
797 | window.users_widgets = users_widgets
|
798 | function load_widgets () {
|
799 | for (var w in users_widgets) {
|
800 | var nodes = document.getElementsByTagName(w)
|
801 | for (var i=0; i<nodes.length; i++)
|
802 | if (!nodes[i].seen)
|
803 | react_render(dom_to_widget(nodes[i]), nodes[i])
|
804 | }
|
805 | }
|
806 |
|
807 | load_scripts()
|
808 | })()
|