1 | (function () {
|
2 | var unique_sockjs_string = '_connect_to_statebus_'
|
3 |
|
4 |
|
5 |
|
6 | function set_cookie (key, val) {
|
7 | document.cookie = key + '=' + val + '; Expires=21 Oct 2025 00:0:00 GMT;'
|
8 | }
|
9 | function get_cookie (key) {
|
10 | var c = document.cookie.match('(^|;)\\s*' + key + '\\s*=\\s*([^;]+)');
|
11 | return c ? c.pop() : '';
|
12 | }
|
13 | try { document.cookie } catch (e) {get_cookie = set_cookie = function (){}}
|
14 | function make_websocket (url) {
|
15 | if (!url.match(/^\w{0,7}:\/\//))
|
16 | url = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port : '') + url
|
17 |
|
18 | url = url.replace(/^state:\/\//, 'wss://')
|
19 | url = url.replace(/^istate:\/\//, 'ws://')
|
20 | url = url.replace(/^statei:\/\//, 'ws://')
|
21 |
|
22 | url = url.replace(/^https:\/\//, 'wss://')
|
23 | url = url.replace(/^http:\/\//, 'ws://')
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | console.log('opening websocket to', url)
|
31 | return new WebSocket(url + '/' + unique_sockjs_string + '/websocket')
|
32 |
|
33 | }
|
34 | function client_creds (server_url) {
|
35 | var me = bus.fetch('ls/me')
|
36 | bus.log('connect: me is', me)
|
37 | if (!me.client) {
|
38 |
|
39 |
|
40 | var c = get_cookie('client')
|
41 | me.client = c || (Math.random().toString(36).substring(2)
|
42 | + Math.random().toString(36).substring(2)
|
43 | + Math.random().toString(36).substring(2))
|
44 | bus.save(me)
|
45 | }
|
46 |
|
47 | set_cookie('client', me.client)
|
48 | return {clientid: me.client}
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | function localstorage_client (prefix) {
|
55 | try { localStorage } catch (e) { return }
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | var bus = this
|
61 | bus.log(this)
|
62 |
|
63 |
|
64 |
|
65 | var saves_are_pending = false
|
66 | var pending_saves = {}
|
67 |
|
68 | function save_the_pending_saves() {
|
69 | bus.log('localstore: saving', pending_saves)
|
70 | for (var k in pending_saves)
|
71 | localStorage.setItem(k, JSON.stringify(pending_saves[k]))
|
72 | saves_are_pending = false
|
73 | }
|
74 |
|
75 | bus(prefix).to_fetch = function (key) {
|
76 | var result = localStorage.getItem(key)
|
77 | return result ? JSON.parse(result) : {key: key}
|
78 | }
|
79 | bus(prefix).to_save = function (obj) {
|
80 |
|
81 | bus.log('localStore: on_save:', obj.key)
|
82 | pending_saves[obj.key] = obj
|
83 | if (!saves_are_pending) {
|
84 | setTimeout(save_the_pending_saves, 50)
|
85 | saves_are_pending = true
|
86 | }
|
87 | bus.save.fire(obj)
|
88 | return obj
|
89 | }
|
90 | bus(prefix).to_delete = function (key) { localStorage.removeItem(key) }
|
91 |
|
92 |
|
93 |
|
94 | function update (event) {
|
95 | bus.log('Got a localstorage update', event)
|
96 |
|
97 | }
|
98 | if (window.addEventListener) window.addEventListener("storage", update, false)
|
99 | else window.attachEvent("onstorage", update)
|
100 | }
|
101 |
|
102 |
|
103 | function url_store (prefix) {
|
104 | var bus = this
|
105 | function get_query_string_value (key) {
|
106 | return unescape(window.location.search.replace(
|
107 | new RegExp("^(?:.*[&\\?]"
|
108 | + escape(key).replace(/[\.\+\*]/g, "\\$&")
|
109 | + "(?:\\=([^&]*))?)?.*$", "i"),
|
110 | "$1"))
|
111 | }
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | var data = get_query_string_value(key)
|
117 | data = (data && JSON.parse(data)) || {key : key}
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | bus(prefix).to_save = function (obj) {
|
123 | window.history.replaceState(
|
124 | '',
|
125 | '',
|
126 | document.location.origin
|
127 | + document.location.pathname
|
128 | + escape('?'+key+'='+JSON.stringify(obj)))
|
129 | bus.save.fire(obj)
|
130 | }
|
131 | }
|
132 |
|
133 | function live_reload_from (prefix) {
|
134 | if (!window.live_reload_initialized) {
|
135 | var first_time = true
|
136 | this(function () {
|
137 | var re = new RegExp(".*/" + prefix + "/(.*)")
|
138 | var file = window.location.href.match(re)[1]
|
139 | var code = bus.fetch('/code/invisible.college/' + file).code
|
140 | if (!code) return
|
141 | if (first_time) {first_time = false; return}
|
142 | var old_scroll_position = window.pageYOffset
|
143 | document.body.innerHTML = code
|
144 | var i = 0
|
145 | var d = 100
|
146 | var interval = setInterval(function () {
|
147 | if (i > 500) clearInterval(interval)
|
148 | i += d
|
149 | window.scrollTo(0, old_scroll_position)
|
150 | }, d)
|
151 | })
|
152 | window.live_reload_initialized = true
|
153 | }
|
154 | }
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | var components = {}
|
164 | var components_count = 0
|
165 | var dirty_components = {}
|
166 | function React_View(component) {
|
167 | function wrap(name, new_func) {
|
168 | var old_func = component[name]
|
169 | component[name] = function wrapper () { return new_func.bind(this)(old_func) }
|
170 | }
|
171 |
|
172 |
|
173 | wrap('componentWillMount', function new_cwm (orig_func) {
|
174 | if (component.displayName === undefined)
|
175 | throw 'Component needs a displayName'
|
176 | this.name = component.displayName.toLowerCase().replace(' ', '_')
|
177 | this.key = 'component/' + components_count++
|
178 | components[this.key] = this
|
179 |
|
180 | function add_shortcut (obj, shortcut_name, to_key) {
|
181 | delete obj[shortcut_name]
|
182 | Object.defineProperty(obj, shortcut_name, {
|
183 | get: function () { return bus.fetch(to_key) },
|
184 | configurable: true })
|
185 | }
|
186 | add_shortcut(this, 'local', this.key)
|
187 |
|
188 | orig_func && orig_func.apply(this, arguments)
|
189 |
|
190 |
|
191 | var orig_render = this.render
|
192 | this.render = bus.reactive(function () {
|
193 | console.assert(this !== window)
|
194 | if (this.render.called_directly) {
|
195 | delete dirty_components[this.key]
|
196 |
|
197 |
|
198 | for (var k in this.props)
|
199 | if (this.props.hasOwnProperty(k)
|
200 | && this.props[k] !== null
|
201 | && typeof this.props[k] === 'object'
|
202 | && this.props[k].key)
|
203 |
|
204 | bus.fetch(this.props[k].key)
|
205 |
|
206 |
|
207 | return orig_render.apply(this, arguments)
|
208 | } else {
|
209 | dirty_components[this.key] = true
|
210 | schedule_re_render()
|
211 | }
|
212 | })
|
213 | })
|
214 |
|
215 | wrap('componentWillUnmount', function new_cwu (orig_func) {
|
216 | orig_func && orig_func.apply(this, arguments)
|
217 |
|
218 | bus.delete(this.key)
|
219 | delete components[this.key]
|
220 | delete dirty_components[this.key]
|
221 | })
|
222 |
|
223 | function shallow_clone(original) {
|
224 | var clone = Object.create(Object.getPrototypeOf(original))
|
225 | var i, keys = Object.getOwnPropertyNames(original)
|
226 | for (i=0; i < keys.length; i++){
|
227 | Object.defineProperty(clone, keys[i],
|
228 | Object.getOwnPropertyDescriptor(original, keys[i])
|
229 | )
|
230 | }
|
231 | return clone
|
232 | }
|
233 |
|
234 | component.shouldComponentUpdate = function new_scu (next_props, next_state) {
|
235 |
|
236 | if (dirty_components[this.key] !== undefined) return true
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | next_props = shallow_clone(next_props)
|
242 | this_props = shallow_clone(this.props)
|
243 |
|
244 | delete next_props['children']; delete this_props['children']
|
245 |
|
246 |
|
247 | next_props = bus.clone(next_props)
|
248 | this_props = bus.clone(this_props)
|
249 |
|
250 |
|
251 | return !bus.deep_equals([next_state, next_props], [this.state, this_props])
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | }
|
262 |
|
263 | component.loading = function loading () {
|
264 | return this.render.loading()
|
265 | }
|
266 |
|
267 |
|
268 |
|
269 | var react_class = React.createClass(component)
|
270 | var result = function (props, children) {
|
271 | props = props || {}
|
272 | props['data-key'] = props.key
|
273 | props['data-widget'] = component.displayName
|
274 |
|
275 | return (React.version >= '0.12.'
|
276 | ? React.createElement(react_class, props, children)
|
277 | : react_class(props, children))
|
278 | }
|
279 |
|
280 |
|
281 | result.prototype = react_class.prototype
|
282 | return result
|
283 | }
|
284 | window.React_View = React_View
|
285 | if (window.statebus) window.statebus.create_react_class = React_View
|
286 |
|
287 |
|
288 |
|
289 | var re_render_scheduled = false
|
290 | re_rendering = false
|
291 | function schedule_re_render() {
|
292 | if (!re_render_scheduled) {
|
293 | requestAnimationFrame(function () {
|
294 | re_render_scheduled = false
|
295 |
|
296 |
|
297 | for (var comp_key in dirty_components) {
|
298 | if (dirty_components[comp_key]
|
299 | && components[comp_key])
|
300 |
|
301 | try {
|
302 | re_rendering = true
|
303 | components[comp_key].forceUpdate()
|
304 | } finally {
|
305 | re_rendering = false
|
306 | }
|
307 | }
|
308 | })
|
309 | re_render_scheduled = true
|
310 | }
|
311 | }
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | function make_client_statebus_maker () {
|
319 | var extra_stuff = ['localstorage_client make_websocket client_creds',
|
320 | 'url_store components live_reload_from'].join(' ').split(' ')
|
321 | if (window.statebus) {
|
322 | var orig_statebus = statebus
|
323 | window.statebus = function make_client_bus () {
|
324 | var bus = orig_statebus()
|
325 | for (var i=0; i<extra_stuff.length; i++)
|
326 | bus[extra_stuff[i]] = eval(extra_stuff[i])
|
327 | bus.localstorage_client('ls/*')
|
328 | return bus
|
329 | }
|
330 | }
|
331 | }
|
332 |
|
333 | load_scripts()
|
334 | function load_scripts() {
|
335 |
|
336 | if (!window.statebus) {
|
337 | var statebus_dir = script_elem().getAttribute('src').match(/(.*)[\/\\]/)
|
338 | statebus_dir = (statebus_dir && statebus_dir[1] + '/')||''
|
339 |
|
340 | var js_urls = {
|
341 | react: statebus_dir + 'extras/react.js',
|
342 | sockjs: statebus_dir + 'extras/sockjs.js',
|
343 | coffee: statebus_dir + 'extras/coffee.js',
|
344 | statebus: statebus_dir + 'statebus.js'
|
345 | }
|
346 | if (statebus_dir == 'https://stateb.us/')
|
347 | js_urls.statebus = statebus_dir + 'statebus4.js'
|
348 |
|
349 | for (var name in js_urls)
|
350 | document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
|
351 | }
|
352 |
|
353 | document.addEventListener('DOMContentLoaded', scripts_ready, false)
|
354 | }
|
355 |
|
356 | function script_elem () {
|
357 | return document.querySelector('script[src*="client"][src$=".js"]')
|
358 | }
|
359 | var loaded_from_file_url = window.location.href.match(/^file:\/\//)
|
360 | window.statebus_server = window.statebus_server ||
|
361 | script_elem().getAttribute('server') ||
|
362 | (loaded_from_file_url ? 'https://stateb.us:3006' : '/')
|
363 | window.statebus_backdoor = window.statebus_backdoor ||
|
364 | script_elem().getAttribute('backdoor')
|
365 | var react_render
|
366 | function scripts_ready () {
|
367 | react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
|
368 | make_client_statebus_maker()
|
369 | window.bus = window.statebus()
|
370 | window.bus.label = 'bus'
|
371 | window.sb = bus.sb
|
372 | statebus.widget = React_View
|
373 | statebus.create_react_class = React_View
|
374 |
|
375 | improve_react()
|
376 | window.dom = window.ui = window.dom || window.ui || {}
|
377 | window.ignore_flashbacks = false
|
378 | if (statebus_server !== 'none')
|
379 | bus.net_mount ('/*', statebus_server)
|
380 |
|
381 | if (window.statebus_backdoor) {
|
382 | window.master = statebus()
|
383 | master.net_mount('*', statebus_backdoor)
|
384 | }
|
385 | bus.net_automount()
|
386 |
|
387 |
|
388 | bus('/new/*').to_save = function (o) {
|
389 | if (o.key.split('/').length > 3) return
|
390 |
|
391 | var old_key = o.key
|
392 | o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
|
393 | statebus.cache[o.key] = o
|
394 | delete statebus.cache[old_key]
|
395 | bus.save(o)
|
396 | }
|
397 | load_coffee()
|
398 |
|
399 | statebus.compile_coffee = compile_coffee
|
400 | statebus.load_client_code = load_client_code
|
401 | statebus.load_widgets = load_widgets
|
402 |
|
403 | if (window.statebus_ready)
|
404 | for (var i=0; i<statebus_ready.length; i++)
|
405 | statebus_ready[i]()
|
406 |
|
407 | load_widgets()
|
408 |
|
409 |
|
410 | }
|
411 |
|
412 | function improve_react() {
|
413 | function capitalize (s) {return s[0].toUpperCase() + s.slice(1)}
|
414 | function camelcase (s) { var a = s.split(/[_-]/)
|
415 | return a.slice(0,1).concat(a.slice(1).map(capitalize)).join('') }
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 | 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"]
|
425 |
|
426 | var ignore = {d:1, cx:1, cy:1, rx:1, ry:1, x:1, y:1,
|
427 | content:1, fill:1, stroke:1, src:1}
|
428 | var is_css_prop = {}
|
429 | for (var i=0; i<all_css_props.length; i++)
|
430 | if (!ignore[all_css_props[i]])
|
431 | is_css_prop[all_css_props[i]] = true
|
432 |
|
433 | function better_element(el) {
|
434 |
|
435 |
|
436 |
|
437 |
|
438 | return function () {
|
439 | var children = []
|
440 | var attrs = {style: {}}
|
441 |
|
442 | for (var i=0; i<arguments.length; i++) {
|
443 | var arg = arguments[i]
|
444 |
|
445 |
|
446 | if (typeof arg === 'string'
|
447 | || arg instanceof String
|
448 | || arg && React.isValidElement(arg)
|
449 | || arg === undefined)
|
450 | children.push(arg)
|
451 |
|
452 |
|
453 | else if (arg instanceof Array)
|
454 | Array.prototype.push.apply(children, arg)
|
455 |
|
456 |
|
457 |
|
458 | else if (arg instanceof Object)
|
459 | for (var k in arg)
|
460 | if (is_css_prop[k]
|
461 | && !(k in {width:1,height:1,size:1}
|
462 | && el in {canvas:1, input:1, embed:1, object:1}))
|
463 | attrs.style[k] = arg[k]
|
464 | else if (k === 'style')
|
465 | for (var k2 in arg[k])
|
466 | attrs.style[k2] = arg[k][k2]
|
467 | else {
|
468 | attrs[k] = arg[k]
|
469 |
|
470 | if (k === 'key')
|
471 | attrs['data-key'] = arg[k]
|
472 | }
|
473 | }
|
474 | if (children.length === 0) children = undefined
|
475 | if (attrs['ref'] === 'input')
|
476 | bus.log(attrs, children)
|
477 | return React.DOM[el](attrs, children)
|
478 | }
|
479 | }
|
480 | for (var el in React.DOM)
|
481 | window[el.toUpperCase()] = better_element(el)
|
482 |
|
483 | function make_better_input (name, element) {
|
484 | window[name] = React.createFactory(React.createClass({
|
485 | getInitialState: function() {
|
486 | return {value: this.props.value}
|
487 | },
|
488 | componentWillReceiveProps: function(new_props) {
|
489 | this.setState({value: new_props.value})
|
490 | },
|
491 | onChange: function(e) {
|
492 | this.props.onChange && this.props.onChange(e)
|
493 | if (this.props.value)
|
494 | this.setState({value: e.target.value})
|
495 | },
|
496 | render: function() {
|
497 | var new_props = {}
|
498 | for (var k in this.props)
|
499 | if (this.props.hasOwnProperty(k))
|
500 | new_props[k] = this.props[k]
|
501 | if (this.state.value) new_props.value = this.state.value
|
502 | new_props.onChange = this.onChange
|
503 | return element(new_props)
|
504 | }
|
505 | }))
|
506 | }
|
507 |
|
508 | make_better_input("INPUT", window.INPUT)
|
509 | make_better_input("TEXTAREA", window.TEXTAREA)
|
510 | make_syncarea()
|
511 |
|
512 |
|
513 | var og_img = window.IMG
|
514 | window.IMG = function () {
|
515 | var args = []
|
516 | for (var i=0; i<arguments.length; i++) {
|
517 | args.push(arguments[i])
|
518 | if (arguments[i].state)
|
519 | args[i].src = 'data:;base64,' + fetch(args[i].state)._
|
520 | }
|
521 | return og_img.apply(this, args)
|
522 | }
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 | function escape_html (s) {
|
529 |
|
530 | return s.replace(/</g, "<").replace(/>/g, ">")
|
531 | }
|
532 | window.STYLE = function (s) {
|
533 | return React.DOM.style({dangerouslySetInnerHTML: {__html: escape_html(s)}})
|
534 | }
|
535 | window.TITLE = function (s) {
|
536 | return React.DOM.title({dangerouslySetInnerHTML: {__html: escape_html(s)}})
|
537 | }
|
538 | }
|
539 |
|
540 | function autodetect_args (func) {
|
541 | if (func.args) return
|
542 |
|
543 |
|
544 | var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,
|
545 | params = /([^\s,]+)/g,
|
546 | s = func.toString().replace(comments, '')
|
547 | func.args = s.slice(s.indexOf('(')+1, s.indexOf(')')).match(params) || []
|
548 | }
|
549 |
|
550 |
|
551 |
|
552 | var users_widgets = {}
|
553 | function make_component(name, safe_renders) {
|
554 |
|
555 |
|
556 | window[name] = users_widgets[name] = window.React_View({
|
557 | displayName: name,
|
558 | render: function () {
|
559 | var args = [], func = window.dom[name]
|
560 |
|
561 |
|
562 | autodetect_args(func)
|
563 |
|
564 | for (var i=0; i<func.args.length; i++)
|
565 | args.push(this.props[func.args[i]])
|
566 |
|
567 |
|
568 | var vdom
|
569 | if (safe_renders)
|
570 | try {
|
571 | vdom = func.apply(this, args)
|
572 | } catch (error) {
|
573 | console.error(error)
|
574 | }
|
575 | else
|
576 | vdom = func.apply(this, args)
|
577 |
|
578 |
|
579 |
|
580 |
|
581 | if (vdom && vdom.props) {
|
582 | vdom.props['data-widget'] = name
|
583 | vdom.props['data-key'] = this.props['data-key']
|
584 | }
|
585 |
|
586 |
|
587 | if (!React.isValidElement(vdom))
|
588 |
|
589 | vdom = React.DOM.span(null, (typeof vdom === 'string')
|
590 | ? vdom : JSON.stringify(vdom))
|
591 | return vdom
|
592 | },
|
593 | componentDidMount: function () {
|
594 | var refresh = window.dom[name].refresh
|
595 | refresh && refresh.bind(this)()
|
596 | },
|
597 | componentWillUnmount: function () {
|
598 | var down = window.dom[name].down
|
599 | return down && down.bind(this)()
|
600 | },
|
601 | componentDidUpdate: function () {
|
602 | if (!this.initial_render_complete && !this.loading()) {
|
603 | this.initial_render_complete = true
|
604 | var up = window.dom[name].up
|
605 | up && up.bind(this)()
|
606 | }
|
607 | var refresh = window.dom[name].refresh
|
608 | return refresh && refresh.bind(this)()
|
609 | },
|
610 | getInitialState: function () { return {} }
|
611 | })
|
612 | }
|
613 |
|
614 | function make_syncarea () {
|
615 |
|
616 |
|
617 |
|
618 |
|
619 |
|
620 |
|
621 |
|
622 |
|
623 | window['SYNCAREA'] = users_widgets['SYNCAREA'] = React.createClass({
|
624 | getInitialState : function () {
|
625 | return { cursor_positions : {} }
|
626 | },
|
627 | on_text_changed : function () {
|
628 | if (this.props.autosize) {
|
629 | var t = this.textarea_ref
|
630 | t.style.height = null
|
631 | while (t.rows > 1 && t.scrollHeight < t.offsetHeight) t.rows--
|
632 | while (t.scrollHeight > t.offsetHeight) t.rows++
|
633 | }
|
634 | },
|
635 | componentDidMount : function () {
|
636 | var self = this
|
637 | self.on_ranges = function (ranges) {
|
638 | self.ranges = ranges
|
639 | var cursor_positions = {}
|
640 | Object.keys(ranges).forEach(function (k) {
|
641 | var r = ranges[k]
|
642 | var xy = getCaretCoordinates(self.textarea_ref, r[0])
|
643 | var x = self.textarea_ref.offsetLeft - self.textarea_ref.scrollLeft + xy.left + 'px'
|
644 | var y = self.textarea_ref.offsetTop - self.textarea_ref.scrollTop + xy.top + 'px'
|
645 | cursor_positions[k] = [x, y]
|
646 | })
|
647 | self.setState({ cursor_positions : cursor_positions })
|
648 | }
|
649 |
|
650 | this.ds = diffsync.create_client({
|
651 | ws_url : this.props.ws_url,
|
652 | channel : this.props.channel,
|
653 | get_text : function () {
|
654 | return self.textarea_ref.value
|
655 | },
|
656 | get_range : function () {
|
657 | var t = self.textarea_ref
|
658 | return [t.selectionStart, t.selectionEnd]
|
659 | },
|
660 | on_text : function (s, range) {
|
661 | self.textarea_ref.value = s
|
662 | self.textarea_ref.setSelectionRange(range[0], range[1])
|
663 | self.on_text_changed()
|
664 | },
|
665 | on_ranges : this.on_ranges
|
666 | })
|
667 | },
|
668 | render : function () {
|
669 | var self = this
|
670 | var cursors = []
|
671 | Object.keys(this.state.cursor_positions).forEach(function (k) {
|
672 | var p = self.state.cursor_positions[k]
|
673 | var style = {
|
674 | position : 'absolute',
|
675 | left : p[0],
|
676 | top : p[1]
|
677 | }
|
678 | Object.keys(self.props.cursor_style).forEach(function (k) {
|
679 | style[k] = self.props.cursor_style[k]
|
680 | })
|
681 | cursors.push(React.createElement('div', {
|
682 | key : k,
|
683 | style : style
|
684 | }))
|
685 | })
|
686 | return React.createElement('div', {
|
687 | style : {
|
688 | clipPath : 'inset(0px 0px 0px 0px)'
|
689 | },
|
690 | }, React.createElement('textarea', {
|
691 | ref : function (t) { self.textarea_ref = t },
|
692 | style : this.props.textarea_style,
|
693 | onChange : function (e) {
|
694 | self.ds.on_change()
|
695 | self.on_text_changed()
|
696 | },
|
697 | onMouseDown : function () {
|
698 | setTimeout(function () { self.ds.on_change() }, 0)
|
699 | },
|
700 | onKeyDown : function () {
|
701 | setTimeout(function () { self.ds.on_change() }, 0)
|
702 | },
|
703 | onScroll : function () {
|
704 | self.on_ranges(self.ranges)
|
705 | }
|
706 | }), cursors)
|
707 | }
|
708 | })
|
709 | }
|
710 |
|
711 | function compile_coffee (coffee, filename) {
|
712 | var compiled
|
713 | try {
|
714 | compiled = CoffeeScript.compile(coffee,
|
715 | {bare: true,
|
716 | sourceMap: true,
|
717 | filename: filename})
|
718 | var source_map = JSON.parse(compiled.v3SourceMap)
|
719 | source_map.sourcesContent = coffee
|
720 | compiled = compiled.js
|
721 |
|
722 |
|
723 | try {
|
724 | compiled += '\n'
|
725 | compiled += '//# sourceMappingURL=data:application/json;base64,'
|
726 | compiled += btoa(JSON.stringify(source_map)) + '\n'
|
727 | compiled += '//# sourceURL=' + filename
|
728 | } catch (e) {}
|
729 |
|
730 | } catch (error) {
|
731 | if (error.location)
|
732 | console.error('Syntax error in '+ filename + ' on line',
|
733 | error.location.first_line
|
734 | + ', column ' + error.location.first_column + ':',
|
735 | error.message)
|
736 | else throw error
|
737 | }
|
738 | return compiled
|
739 | }
|
740 | function load_client_code (code, safe) {
|
741 | var dom = {}, ui = {}
|
742 | if (code) eval(code)
|
743 | else { dom = window.dom; ui = window.ui }
|
744 | for (var k in ui) dom[k] = dom[k] || ui[k]
|
745 | for (var widget_name in dom) {
|
746 | window.dom[widget_name] = dom[widget_name]
|
747 | make_component(widget_name, safe)
|
748 | }
|
749 | }
|
750 | function load_coffee () {
|
751 | load_client_code()
|
752 | var scripts = document.getElementsByTagName("script")
|
753 | var filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1)
|
754 | for (var i=0; i<scripts.length; i++)
|
755 | if (scripts[i].getAttribute('type')
|
756 | in {'statebus':1, 'coffeedom':1,'statebus-js':1,
|
757 | 'coffee':1, 'coffeescript':1}) {
|
758 |
|
759 | var compiled = scripts[i].text
|
760 | if (scripts[i].getAttribute('type') !== 'statebus-js')
|
761 | compiled = compile_coffee(scripts[i].text, filename)
|
762 | if (compiled)
|
763 | load_client_code(compiled)
|
764 | }
|
765 | }
|
766 |
|
767 | function dom_to_widget (node) {
|
768 | if (node.nodeName === '#text') return node.textContent
|
769 |
|
770 | node.seen = true
|
771 | var children = [], props = {}
|
772 |
|
773 | for (var i=0; i<node.childNodes.length; i++)
|
774 | children.push(dom_to_widget(node.childNodes[i]))
|
775 |
|
776 |
|
777 | var props = {}
|
778 | for (var i=0; node.attributes && i<node.attributes.length; i++)
|
779 | props[node.attributes[i].name] = node.attributes[i].value
|
780 |
|
781 | var widge = (window[node.nodeName.toLowerCase()]
|
782 | || window[node.nodeName.toUpperCase()])
|
783 | console.assert(widge, node.nodeName + ' has not been defined as a UI widget.')
|
784 |
|
785 | return widge(props, children)
|
786 | }
|
787 |
|
788 | window.users_widgets = users_widgets
|
789 | function load_widgets () {
|
790 | for (var w in users_widgets) {
|
791 | var nodes = document.getElementsByTagName(w)
|
792 | for (var i=0; i<nodes.length; i++)
|
793 | if (!nodes[i].seen)
|
794 | react_render(dom_to_widget(nodes[i]), nodes[i])
|
795 | }
|
796 | }
|
797 | })()
|