├── .DS_Store ├── .idea ├── .name ├── encodings.xml ├── libraries │ └── sass_stdlib.xml ├── misc.xml ├── modules.xml ├── remote-assist.iml ├── scopes │ └── scope_settings.xml ├── vcs.xml └── workspace.xml ├── README.md ├── app.js └── public ├── .DS_Store ├── admin.html ├── cf.html ├── index.html ├── lib ├── css │ ├── admin.css │ ├── bootstrap-responsive.min.css │ ├── bootstrap.min.css │ ├── client.css │ └── screenshare.css ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png └── js │ ├── admin.js │ ├── bootstrap.min.js │ ├── cfinstall.js │ ├── client.js │ ├── jquery.js │ ├── loader.js │ ├── mutation_summary.js │ ├── preview.js │ ├── screenshare.js │ ├── session.js │ └── tree_mirror.js └── preview.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvideby0/screenshare/9f3a19a3db6eb964de95731bafb613c48a8ec129/.DS_Store -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | remote-assist -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/sass_stdlib.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/remote-assist.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | 150 | 151 | 152 | 153 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 192 | 193 | 194 | 195 | 198 | 199 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 264 | 265 | 266 | 267 | 1356566356929 268 | 1356566356929 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 298 | 299 | 310 | 352 | 353 | 354 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Screenshare 2 | 3 | Screenshare is an application that allows a user to share their current session on a website. This currently works for Firefox, Chrome, Opera and Safari. For mobile devices, Chrome and Firefox are supported on Android. 4 | 5 | [DEMO](http://www.youtube.com/watch?v=mqxTYcNXY-U) video here of it in action 6 | 7 | 8 | 9 | ##Requirements 10 | 11 | You must have NodeJS > 0.6.x installed, with socket.io and express modules. You can download the latest copy of NodeJS [here](http://nodejs.org). 12 | 13 | npm install socket.io 14 | npm install express 15 | 16 | ######Run the Example 17 | 18 | 1. Run app.js. `node app.js` 19 | 20 | 2. Navigate in your browser to `http://localhost:3000 (Client Page)` and `http://localhost:3000/admin.html (Admin Page)` 21 | 22 | > This will bring up 2 pages. The first page will be the client page and the other will be the administration page. 23 | 24 | 3. On the client page mouse over the 'Help' tab 25 | 26 | 4. Enter in a key, then click the Create Key button. 27 | 28 | 5. On the admin page type in the key you just created and hit enter. 29 | 30 | > Now you should be able to see exactly what is happening on the client screen. There are still plenty of bugs with this application but it is in a usable state for anyone to begin playing with. 31 | 32 | ######How To use 33 | 34 | 1. Make sure that your file structure follows `lib/js` & `lib/css`, then change the CDN and SocketCDN variables to your server environment. 35 | 36 | 2. Add the screenshare.js script `public/lib/js/screenshare.js` before the closing body tag `` on your html file(s) 37 | 38 | 3. Run the app.js `node app.js` 39 | 40 | 4. Open your website (there should now be a help tab on the right of your page) 41 | 42 | 5. Enter in a key, then click the Create Key button. 43 | 44 | 6. Open up another browser page to `http://localhost:3000/admin.html` 45 | 46 | 7. On the admin page type the key you created on your site and hit enter 47 | 48 | > Now you should be able to see exactly what is happening on the client screen. There are still plenty of bugs with this application but it is in a usable state for anyone to begin playing with. 49 | 50 | ##TODOs: 51 | 52 | 1. Configure mutation observers to send over only affected DOM elements **(completed)** 53 | 54 | 2. Configure Mutation observers to detect removal and addition of DOM elements **(completed)** 55 | 56 | 3. Detect scrolling and emulate on admin side **(completed)** 57 | 58 | 4. Correct mouse pointer offset **(completed)** 59 | 60 | 5. Find proper way to hard code currently computed css to each element before initial sendscreen **(completed)** 61 | 62 | 6. Correct issues with dynamically loaded scripts not working across all browsers **(not started)** 63 | 64 | 7. Support multi page sites **(working but still a little buggy)** 65 | 66 | 8. IE support **(started, chrome frame flow working for IE, Tested WebRTC working as well)** 67 | 68 | 9. Full mobile support **(not started, currently works for Chrome and Firefox on Android)** 69 | 70 | 10. Fix issues with proper scrolling to top **(not started)** 71 | 72 | 11. Add SSL support **(not started)** 73 | 74 | 12. Harden socket access/Security **(not started)** 75 | 76 | 13. Service clustering **(not started)** 77 | 78 | 14. Logging **(not started)** 79 | 80 | 15. Handle local images **(started, added commented out code to handle relative images)** 81 | 82 | 16. Relative to absolute paths **(see above, still need to work on unaccessable css and scripts)** 83 | 84 | 17. Script optimization with minification, consolidation and compression **(not started)** 85 | 86 | 87 | 88 | ##Future Plans: 89 | 90 | 1. Integrate streaming audio 91 | 92 | 2. Chat integration 93 | 94 | 3. Video integration **(WebRTC)** 95 | 96 | 4. Queue system for routing to appropriate personel 97 | 98 | 5. Ability to view/manipulate iframe content 99 | 100 | 6. Record and playback functionality 101 | 102 | 103 | 104 | ##Credits 105 | 106 | 107 | 108 | ###Client 109 | 110 | This application makes use of the [mutation-summary](http://code.google.com/p/mutation-summary/) library developed by Rafael Weinstein. 111 | 112 | The ever so awesome [jQuery](http://jquery.com) library. 113 | 114 | [sessvars](http://www.thomasfrank.se/sessionvars.html) by Thomas Frank. 115 | 116 | [Chrome Frame](https://developers.google.com/chrome/chrome-frame/) by Google which is an IE plugin to allow IE users to have all the advantages of using Chrome 117 | 118 | ###Server 119 | 120 | [NodeJS](http://nodejs.org) - Server side JavaScript. 121 | 122 | [Socket.io](http://socket.io) - Library for NodeJS that does an awesome job at handling websockets. 123 | 124 | [Express](http://expressjs.com) - Extremely powerful web framework for NodeJS. 125 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var app = express(); 4 | var server = http.createServer(app).listen(3001); 5 | var io = require('socket.io').listen(server); 6 | io.set('log level', 1); 7 | io.set('transports', ['websocket']); 8 | 9 | app.configure(function(){ 10 | app.use(function(req, res, next) { 11 | res.setHeader("X-UA-Compatible", "chrome=1"); 12 | return next(); 13 | }); 14 | app.use(express.static(__dirname + '/public')); 15 | }); 16 | io.sockets.on('connection', function(socket) { 17 | socket.on('CreateSession', function(msg){ 18 | socket.join(msg); 19 | }); 20 | socket.on('PageChange', function(msg){ 21 | socket.join(msg); 22 | io.sockets.in(msg).emit('SessionStarted', ''); 23 | console.log('PageChange'); 24 | }); 25 | socket.on('JoinRoom', function(msg){ 26 | socket.join(msg); 27 | io.sockets.in(msg).emit('SessionStarted', ''); 28 | }); 29 | socket.on('ClientMousePosition', function(msg){ 30 | socket.broadcast.to(socket.room).emit('ClientMousePosition', {PositionLeft:msg.PositionLeft, PositionTop:msg.PositionTop}); 31 | }); 32 | socket.on('AdminMousePosition', function(msg){ 33 | socket.broadcast.to(msg.room).emit('AdminMousePosition', {PositionLeft:msg.PositionLeft, PositionTop:msg.PositionTop}); 34 | }); 35 | socket.on('changeHappened', function(msg){ 36 | socket.broadcast.to(msg.room).emit('changes', msg.change); 37 | }); 38 | socket.on('DOMLoaded', function(msg){ 39 | socket.broadcast.to(msg.room).emit('DOMLoaded', ''); 40 | }); 41 | }); 42 | app.listen(3000); -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvideby0/screenshare/9f3a19a3db6eb964de95731bafb613c48a8ec129/public/.DS_Store -------------------------------------------------------------------------------- /public/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/cf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 |
14 | Chrome Frame is already installed 15 | 16 |
17 | 18 | 28 | 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Remote Assist Demo (Client) 5 | 6 | 7 | 8 | 9 | 10 | 27 |
28 |
29 |
30 |

Hello There Friend

31 |

We like meeting new people. Take some time to tell us a little about yourself.

32 |
33 |

Some Really confusing form

34 | Acct Type
35 |
41 | Account Number
42 |

43 | Blood Type
44 |

45 | Cubed Root of 343
46 |

47 | Neighbor's Middle Name (optional)
48 |
49 |
Distance to closest star (optional)
50 |

51 | The 3rd wonder of the world (optional)
52 |

53 | The only positively charged polyatomic ion (optional)
54 |
55 |
How long is 4 score (optional)
56 |

57 | The second largest producer of corn (optional)
58 |

59 | Cubed Root of 360 (optional)
60 |

61 | How many of your friends like you (optional)
62 | 63 |

64 | Submit 65 |
66 |
67 |
68 |

Something a Little Different

69 |

We want your experiences to be exceptional every time, that's what makes us different

70 |
71 |
72 |

Services

73 |
74 |
75 |

Products

76 |
77 |
78 |

Consulting

79 |
80 |
81 |
82 |
83 |
84 |
85 |

Keep in Touch

86 |

With so many options to choose, you are guaranteed to find the one that works for you

87 |
88 |
89 |

E-Mail

90 |
91 |
92 |

Phone

93 |
94 |
95 |

In Person

96 |
97 |
98 |
99 |
100 | 112 | 124 |
125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /public/lib/css/admin.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding-top: 20px; 3 | } 4 | iframe{ 5 | border: none; 6 | } -------------------------------------------------------------------------------- /public/lib/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /public/lib/css/client.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | padding-bottom: 40px; 4 | } 5 | .container{ 6 | padding-left: 10px; 7 | } 8 | .page{ 9 | display: none; 10 | } -------------------------------------------------------------------------------- /public/lib/css/screenshare.css: -------------------------------------------------------------------------------- 1 | .rotate { 2 | width: 30px; 3 | -webkit-transform: rotate(-90deg); 4 | -moz-transform: rotate(-90deg); 5 | -ms-transform: rotate(-90deg); 6 | -o-transform: rotate(-90deg); 7 | transform: rotate(-90deg); 8 | 9 | /* also accepts left, right, top, bottom coordinates; not required, but a good idea for styling */ 10 | -webkit-transform-origin: 50% 50%; 11 | -moz-transform-origin: 50% 50%; 12 | -ms-transform-origin: 50% 50%; 13 | -o-transform-origin: 50% 50%; 14 | transform-origin: 50% 50%; 15 | 16 | /* Should be unset in IE9+ I think. */ 17 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 18 | } 19 | #MenuTable{ 20 | position: fixed; 21 | z-index: 9998; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | #SlideMenu{ 26 | opacity: 0.6; 27 | border-radius: 10px 0 0 10px; 28 | background-color: #000000; 29 | width: 30px; 30 | height: 100px; 31 | padding-top: 80px; 32 | color: #ffffff; 33 | } 34 | #MainMenuTD{ 35 | opacity: 0.9; 36 | padding-top: 20px; 37 | padding-left: 10px; 38 | padding-right: 10px; 39 | font-weight: bold; 40 | vertical-align: text-top; 41 | border-radius: 15px 0 0 15px; 42 | background-color: #bfd730; 43 | color: #333333; 44 | height: 300px; 45 | width: 300px; 46 | text-align: center; 47 | } 48 | #RemoteAssistMessage{ 49 | text-align: justify; 50 | } 51 | #SessionKey{ 52 | margin-top: 15px; 53 | margin-bottom: 15px; 54 | width: 90%; 55 | height: 30px; 56 | font-size: 25px; 57 | } 58 | #AdminPointer{ 59 | position: absolute; 60 | z-index: 9999; 61 | height: 30px; 62 | width: 30px; 63 | border-radius: 5em; 64 | background-color: orange; 65 | opacity:0.5; 66 | top: 1px; 67 | } -------------------------------------------------------------------------------- /public/lib/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvideby0/screenshare/9f3a19a3db6eb964de95731bafb613c48a8ec129/public/lib/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/lib/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvideby0/screenshare/9f3a19a3db6eb964de95731bafb613c48a8ec129/public/lib/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/lib/js/admin.js: -------------------------------------------------------------------------------- 1 | var frame = $('#ClientView').contents(); 2 | window.location = 'admin.html?#'; 3 | function StartSession(){ 4 | var SessionKey = $('#SessionKey').val(); 5 | $('#ClientView')[0].contentWindow.JoinRoom(SessionKey); 6 | } 7 | function ResizePreview(width, height){ 8 | $('#ClientView').width(width); 9 | $('#ClientView').height(height); 10 | } 11 | function RemoveMouse(){ 12 | $('body').remove('#ClientPointer'); 13 | } 14 | function AddMouse(){ 15 | $('body').append('
'); 16 | } 17 | function MoveMouse(x,y){ 18 | $('#ClientPointer').css({'left': x - 15, 'top': y + 30}); 19 | } -------------------------------------------------------------------------------- /public/lib/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$(' '); 142 | $(window).scroll(function(){ 143 | socketSend({scroll: $(window).scrollTop()}); 144 | }); 145 | }); 146 | socket.on('AdminMousePosition', function(msg) { 147 | $('#AdminPointer').css({'left': msg.PositionLeft - 15, 'top': msg.PositionTop}); 148 | }); 149 | socket.on('DOMLoaded', function(){ 150 | BindEverything(); 151 | }) 152 | }); 153 | } 154 | function CreateSession(){ 155 | socket = io.connect(SocketCDN); 156 | SessionKey = document.getElementById('SessionKey').value; 157 | socket.on('connect', function(){ 158 | socket.emit('CreateSession', SessionKey); 159 | $('#RemoteStatus').text('Status: Waiting for connection.'); 160 | socket.on('SessionStarted', function() { 161 | sessvars.Session = SessionKey; 162 | $('#RemoteStatus').text('Status: Connected!'); 163 | socketSend({height: $(window).height(), width: $(window).width()}); 164 | socketSend({ base: location.href.match(/^(.*\/)[^\/]*$/)[1] }); 165 | socketSend(oDOM); 166 | SendMouse(); 167 | $('body').append('
'); 168 | $(window).scroll(function(){ 169 | socketSend({scroll: $(window).scrollTop()}); 170 | }); 171 | }); 172 | socket.on('AdminMousePosition', function(msg) { 173 | $('#AdminPointer').css({'left': msg.PositionLeft - 15, 'top': msg.PositionTop}); 174 | }); 175 | socket.on('DOMLoaded', function(){ 176 | BindEverything(); 177 | }) 178 | }); 179 | } 180 | 181 | 182 | function BindEverything(){ 183 | $(':input').each(function(){ 184 | $(this).attr('value', this.value); 185 | }); 186 | $('select').each(function(){ 187 | var Selected = $(this).children('option:selected'); 188 | $(this).children('option').removeAttr('selected', false); 189 | Selected.attr('selected', true); 190 | $(this).replaceWith($(this)[0].outerHTML); 191 | }); 192 | $(':input').bind('keyup', function() { 193 | $(this).attr('value', this.value); 194 | }); 195 | $('select').change(function(){ 196 | var Selected = $(this).children('option:selected'); 197 | $(this).children('option').removeAttr('selected', false); 198 | Selected.attr('selected', true); 199 | $(this).replaceWith($(this)[0].outerHTML); 200 | $('select').unbind('change'); 201 | $('select').change(function(){ 202 | var Selected = $(this).children('option:selected'); 203 | $(this).children('option').removeAttr('selected', false); 204 | Selected.attr('selected', true); 205 | $(this).replaceWith($(this)[0].outerHTML); 206 | $('select').unbind('change'); 207 | }); 208 | }); 209 | } 210 | 211 | 212 | function SendMouse(){ 213 | document.onmousemove = function(e) { 214 | if(!e) e = window.event; 215 | 216 | if(e.pageX == null && e.clientX != null) { 217 | var doc = document.documentElement, body = document.body; 218 | 219 | e.pageX = e.clientX 220 | + (doc && doc.scrollLeft || body && body.scrollLeft || 0) 221 | - (doc.clientLeft || 0); 222 | 223 | e.pageY = e.clientY 224 | + (doc && doc.scrollTop || body && body.scrollTop || 0) 225 | - (doc.clientTop || 0); 226 | } 227 | socket.emit('ClientMousePosition', {room: SessionKey, PositionLeft: e.pageX, PositionTop: e.pageY - 5}); 228 | } 229 | } 230 | 231 | /* ------------------------- Code for converting relative images to Data URLs ----------------------------------- 232 | 233 | 234 | function getImageDataURL(url, success, error) { 235 | var data, canvas, ctx; 236 | var img = new Image(); 237 | img.onload = function(){ 238 | // Create the canvas element. 239 | canvas = document.createElement('canvas'); 240 | canvas.width = img.width; 241 | canvas.height = img.height; 242 | // Get '2d' context and draw the image. 243 | ctx = canvas.getContext("2d"); 244 | ctx.drawImage(img, 0, 0); 245 | // Get canvas data URL 246 | try{ 247 | data = canvas.toDataURL(); 248 | success({image:img, data:data}); 249 | }catch(e){ 250 | error(e); 251 | } 252 | }; 253 | // Load image URL. 254 | try{ 255 | img.src = url; 256 | }catch(e){ 257 | error(e); 258 | } 259 | } 260 | 261 | getImageDataURL('image.png', function(succuss, error){ 262 | $('#Response').append(''); 263 | }) 264 | 265 | */ 266 | 267 | 268 | function AddMenu(){ 269 | $('body').append('' + 270 | '' + 271 | '' + 272 | '' + 277 | '' + 278 | ''); 279 | $('#MenuTable').css({left: $(window).width() - 30, top: ($(window).height()/2) - 150}); 280 | $('#SlideMenu').mouseenter(function(){ 281 | if($('#MenuTable').offset().left == $(window).width() -30){ 282 | $('#MenuTable').animate({left:'-=' + ($('#MenuTable').width() - 30)},'fast'); 283 | } 284 | }); 285 | $('#MenuTable').mouseleave(function(){ 286 | if($(this).offset().left == $(window).width() - $('#MenuTable').width()){ 287 | $(this).animate({left:'+=' + ($('#MenuTable').width() - 30)},'fast'); 288 | } 289 | }); 290 | } -------------------------------------------------------------------------------- /public/lib/js/session.js: -------------------------------------------------------------------------------- 1 | /* 2 | sessvars ver 1.01 3 | - JavaScript based session object 4 | copyright 2008 Thomas Frank 5 | 6 | This EULA grants you the following rights: 7 | 8 | Installation and Use. You may install and use an unlimited number of copies of the SOFTWARE PRODUCT. 9 | 10 | Reproduction and Distribution. You may reproduce and distribute an unlimited number of copies of the SOFTWARE PRODUCT either in whole or in part; each copy should include all copyright and trademark notices, and shall be accompanied by a copy of this EULA. Copies of the SOFTWARE PRODUCT may be distributed as a standalone product or included with your own product. 11 | 12 | Commercial Use. You may sell for profit and freely distribute scripts and/or compiled scripts that were created with the SOFTWARE PRODUCT. 13 | 14 | v 1.0 --> 1.01 15 | sanitizer added to toObject-method & includeFunctions flag now defaults to false 16 | 17 | */ 18 | 19 | sessvars=function(){ 20 | 21 | var x={}; 22 | 23 | x.$={ 24 | prefs:{ 25 | memLimit:2000, 26 | autoFlush:true, 27 | crossDomain:false, 28 | includeProtos:false, 29 | includeFunctions:false 30 | }, 31 | parent:x, 32 | clearMem:function(){ 33 | for(var i in this.parent){if(i!="$"){this.parent[i]=undefined}}; 34 | this.flush(); 35 | }, 36 | usedMem:function(){ 37 | x={}; 38 | return Math.round(this.flush(x)/1024); 39 | }, 40 | usedMemPercent:function(){ 41 | return Math.round(this.usedMem()/this.prefs.memLimit); 42 | }, 43 | flush:function(x){ 44 | var y,o={},j=this.$$; 45 | x=x||top; 46 | for(var i in this.parent){o[i]=this.parent[i]}; 47 | o.$=this.prefs; 48 | j.includeProtos=this.prefs.includeProtos; 49 | j.includeFunctions=this.prefs.includeFunctions; 50 | y=this.$$.make(o); 51 | if(x!=top){return y.length}; 52 | if(y.length/1024>this.prefs.memLimit){return false} 53 | x.name=y; 54 | return true; 55 | }, 56 | getDomain:function(){ 57 | var l=location.href 58 | l=l.split("///").join("//"); 59 | l=l.substring(l.indexOf("://")+3).split("/")[0]; 60 | while(l.split(".").length>2){l=l.substring(l.indexOf(".")+1)}; 61 | return l 62 | }, 63 | debug:function(t){ 64 | var t=t||this,a=arguments.callee; 65 | if(!document.body){setTimeout(function(){a(t)},200);return}; 66 | t.flush(); 67 | var d=document.getElementById("sessvarsDebugDiv"); 68 | if(!d){d=document.createElement("div");document.body.insertBefore(d,document.body.firstChild)}; 69 | d.id="sessvarsDebugDiv"; 70 | d.innerHTML='
'+ 72 | 'sessvars.js - debug info:

'+ 73 | 'Memory usage: '+t.usedMem()+' Kb ('+t.usedMemPercent()+'%)   '+ 74 | '[Clear memory]
'+ 75 | top.name.split('\n').join('
')+'
'; 76 | d.getElementsByTagName('span')[0].onclick=function(){t.clearMem();location.reload()} 77 | }, 78 | init:function(){ 79 | var o={}, t=this; 80 | try {o=this.$$.toObject(top.name)} catch(e){o={}}; 81 | this.prefs=o.$||t.prefs; 82 | if(this.prefs.crossDomain || this.prefs.currentDomain==this.getDomain()){ 83 | for(var i in o){this.parent[i]=o[i]}; 84 | } 85 | else { 86 | this.prefs.currentDomain=this.getDomain(); 87 | }; 88 | this.parent.$=t; 89 | t.flush(); 90 | var f=function(){if(t.prefs.autoFlush){t.flush()}}; 91 | if(window["addEventListener"]){addEventListener("unload",f,false)} 92 | else if(window["attachEvent"]){window.attachEvent("onunload",f)} 93 | else {this.prefs.autoFlush=false}; 94 | } 95 | }; 96 | 97 | x.$.$$={ 98 | compactOutput:false, 99 | includeProtos:false, 100 | includeFunctions: false, 101 | detectCirculars:true, 102 | restoreCirculars:true, 103 | make:function(arg,restore) { 104 | this.restore=restore; 105 | this.mem=[];this.pathMem=[]; 106 | return this.toJsonStringArray(arg).join(''); 107 | }, 108 | toObject:function(x){ 109 | if(!this.cleaner){ 110 | try{this.cleaner=new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$')} 111 | catch(a){this.cleaner=/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/} 112 | }; 113 | if(!this.cleaner.test(x)){return {}}; 114 | eval("this.myObj="+x); 115 | if(!this.restoreCirculars || !alert){return this.myObj}; 116 | if(this.includeFunctions){ 117 | var x=this.myObj; 118 | for(var i in x){if(typeof x[i]=="string" && !x[i].indexOf("JSONincludedFunc:")){ 119 | x[i]=x[i].substring(17); 120 | eval("x[i]="+x[i]) 121 | }} 122 | }; 123 | this.restoreCode=[]; 124 | this.make(this.myObj,true); 125 | var r=this.restoreCode.join(";")+";"; 126 | eval('r=r.replace(/\\W([0-9]{1,})(\\W)/g,"[$1]$2").replace(/\\.\\;/g,";")'); 127 | eval(r); 128 | return this.myObj 129 | }, 130 | toJsonStringArray:function(arg, out) { 131 | if(!out){this.path=[]}; 132 | out = out || []; 133 | var u; // undefined 134 | switch (typeof arg) { 135 | case 'object': 136 | this.lastObj=arg; 137 | if(this.detectCirculars){ 138 | var m=this.mem; var n=this.pathMem; 139 | for(var i=0;i 0) 152 | out.push(',\n'); 153 | this.toJsonStringArray(arg[i], out); 154 | this.path.pop(); 155 | } 156 | out.push(']'); 157 | return out; 158 | } else if (typeof arg.toString != 'undefined') { 159 | out.push('{'); 160 | var first = true; 161 | for (var i in arg) { 162 | if(!this.includeProtos && arg[i]===arg.constructor.prototype[i]){continue}; 163 | this.path.push(i); 164 | var curr = out.length; 165 | if (!first) 166 | out.push(this.compactOutput?',':',\n'); 167 | this.toJsonStringArray(i, out); 168 | out.push(':'); 169 | this.toJsonStringArray(arg[i], out); 170 | if (out[out.length - 1] == u) 171 | out.splice(curr, out.length - curr); 172 | else 173 | first = false; 174 | this.path.pop(); 175 | } 176 | out.push('}'); 177 | return out; 178 | } 179 | return out; 180 | } 181 | out.push('null'); 182 | return out; 183 | case 'unknown': 184 | case 'undefined': 185 | case 'function': 186 | if(!this.includeFunctions){out.push(u);return out}; 187 | arg="JSONincludedFunc:"+arg; 188 | out.push('"'); 189 | var a=['\n','\\n','\r','\\r','"','\\"']; 190 | arg+=""; for(var i=0;i<6;i+=2){arg=arg.split(a[i]).join(a[i+1])}; 191 | out.push(arg); 192 | out.push('"'); 193 | return out; 194 | case 'string': 195 | if(this.restore && arg.indexOf("JSONcircRef:")==0){ 196 | this.restoreCode.push('this.myObj.'+this.path.join(".")+"="+arg.split("JSONcircRef:").join("this.myObj.")); 197 | }; 198 | out.push('"'); 199 | var a=['\n','\\n','\r','\\r','"','\\"']; 200 | arg+=""; for(var i=0;i<6;i+=2){arg=arg.split(a[i]).join(a[i+1])}; 201 | out.push(arg); 202 | out.push('"'); 203 | return out; 204 | default: 205 | out.push(String(arg)); 206 | return out; 207 | } 208 | } 209 | }; 210 | 211 | x.$.init(); 212 | return x; 213 | }() -------------------------------------------------------------------------------- /public/lib/js/tree_mirror.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function TreeMirror(root, delegate) { 16 | this.root = root; 17 | this.idMap = {}; 18 | this.delegate = delegate; 19 | } 20 | 21 | TreeMirror.prototype = { 22 | initialize: function(rootId, children) { 23 | this.idMap[rootId] = this.root; 24 | 25 | for (var i = 0; i < children.length; i++) 26 | this.deserializeNode(children[i], this.root); 27 | }, 28 | 29 | deserializeNode: function(nodeData, parent) { 30 | if (nodeData === null) 31 | return null; 32 | 33 | if (typeof nodeData == 'number') 34 | return this.idMap[nodeData]; 35 | 36 | var doc = this.root instanceof HTMLDocument ? this.root : this.root.ownerDocument; 37 | 38 | var node; 39 | switch(nodeData.nodeType) { 40 | case Node.COMMENT_NODE: 41 | node = doc.createComment(nodeData.textContent); 42 | break; 43 | 44 | case Node.TEXT_NODE: 45 | node = doc.createTextNode(nodeData.textContent); 46 | break; 47 | 48 | case Node.DOCUMENT_TYPE_NODE: 49 | node = doc.implementation.createDocumentType(nodeData.name, nodeData.publicId, nodeData.systemId); 50 | break; 51 | 52 | case Node.ELEMENT_NODE: 53 | if (this.delegate && this.delegate.createElement) 54 | node = this.delegate.createElement(nodeData.tagName); 55 | if (!node) 56 | node = doc.createElement(nodeData.tagName); 57 | 58 | Object.keys(nodeData.attributes).forEach(function(name) { 59 | if (!this.delegate || 60 | !this.delegate.setAttribute || 61 | !this.delegate.setAttribute(node, name, nodeData.attributes[name])) { 62 | node.setAttribute(name, nodeData.attributes[name]); 63 | } 64 | }, this); 65 | 66 | break; 67 | } 68 | 69 | this.idMap[nodeData.id] = node; 70 | 71 | if (parent) 72 | parent.appendChild(node); 73 | 74 | if (nodeData.childNodes) { 75 | for (var i = 0; i < nodeData.childNodes.length; i++) 76 | this.deserializeNode(nodeData.childNodes[i], node); 77 | } 78 | 79 | return node; 80 | }, 81 | 82 | applyChanged: function(removed, addedOrMoved, attributes, text) { 83 | function removeNode(node) { 84 | if (node.parentNode) 85 | node.parentNode.removeChild(node); 86 | } 87 | 88 | function moveOrInsertNode(data) { 89 | var parent = data.parentNode; 90 | var previous = data.previousSibling; 91 | var node = data.node; 92 | 93 | parent.insertBefore(node, previous ? previous.nextSibling : parent.firstChild); 94 | } 95 | 96 | function updateAttributes(data) { 97 | var node = this.deserializeNode(data.node); 98 | Object.keys(data.attributes).forEach(function(attrName) { 99 | var newVal = data.attributes[attrName]; 100 | if (newVal === null) { 101 | node.removeAttribute(attrName); 102 | } else { 103 | if (!this.delegate || 104 | !this.delegate.setAttribute || 105 | !this.delegate.setAttribute(node, attrName, newVal)) { 106 | node.setAttribute(attrName, newVal); 107 | } 108 | } 109 | }, this); 110 | } 111 | 112 | function updateText(data) { 113 | var node = this.deserializeNode(data.node); 114 | node.textContent = data.textContent; 115 | } 116 | 117 | addedOrMoved.forEach(function(data) { 118 | data.node = this.deserializeNode(data.node); 119 | data.previousSibling = this.deserializeNode(data.previousSibling); 120 | data.parentNode = this.deserializeNode(data.parentNode); 121 | 122 | // NOTE: Applying the changes can result in an attempting to add a child 123 | // to a parent which is presently an ancestor of the parent. This can occur 124 | // based on random ordering of moves. The way we handle this is to first 125 | // remove all changed nodes from their parents, then apply. 126 | removeNode(data.node); 127 | }, this); 128 | 129 | removed.map(this.deserializeNode, this).forEach(removeNode); 130 | addedOrMoved.forEach(moveOrInsertNode); 131 | attributes.forEach(updateAttributes, this); 132 | text.forEach(updateText, this); 133 | 134 | removed.forEach(function(id) { 135 | delete this.idMap[id] 136 | }, this); 137 | } 138 | } 139 | 140 | function TreeMirrorClient(target, mirror, testingQueries) { 141 | this.target = target; 142 | this.mirror = mirror; 143 | this.knownNodes = new MutationSummary.NodeMap; 144 | 145 | var rootId = this.serializeNode(target).id; 146 | var children = []; 147 | for (var child = target.firstChild; child; child = child.nextSibling) 148 | children.push(this.serializeNode(child, true)); 149 | 150 | this.mirror.initialize(rootId, children); 151 | 152 | var self = this; 153 | 154 | var queries = [{ all: true }]; 155 | 156 | if (testingQueries) 157 | queries = queries.concat(testingQueries); 158 | 159 | this.mutationSummary = new MutationSummary({ 160 | rootNode: target, 161 | callback: function(summaries) { 162 | self.applyChanged(summaries); 163 | }, 164 | queries: queries 165 | }); 166 | } 167 | 168 | TreeMirrorClient.prototype = { 169 | nextId: 1, 170 | 171 | disconnect: function() { 172 | if (this.mutationSummary) { 173 | this.mutationSummary.disconnect(); 174 | this.mutationSummary = undefined; 175 | } 176 | }, 177 | 178 | rememberNode: function(node) { 179 | var id = this.nextId++; 180 | this.knownNodes.set(node, id); 181 | return id; 182 | }, 183 | 184 | forgetNode: function(node) { 185 | delete this.knownNodes.delete(node); 186 | }, 187 | 188 | serializeNode: function(node, recursive) { 189 | if (node === null) 190 | return null; 191 | 192 | var id = this.knownNodes.get(node); 193 | if (id !== undefined) { 194 | return id; 195 | } 196 | 197 | var data = { 198 | nodeType: node.nodeType, 199 | id: this.rememberNode(node) 200 | }; 201 | 202 | switch(data.nodeType) { 203 | case Node.DOCUMENT_TYPE_NODE: 204 | data.name = node.name; 205 | data.publicId = node.publicId; 206 | data.systemId = node.systemId; 207 | break; 208 | 209 | case Node.COMMENT_NODE: 210 | case Node.TEXT_NODE: 211 | data.textContent = node.textContent; 212 | break; 213 | 214 | case Node.ELEMENT_NODE: 215 | data.tagName = node.tagName; 216 | data.attributes = {}; 217 | for (var i = 0; i < node.attributes.length; i++) { 218 | var attr = node.attributes.item(i); 219 | data.attributes[attr.name] = attr.value; 220 | } 221 | 222 | if (recursive && node.childNodes.length) { 223 | data.childNodes = []; 224 | 225 | for (var child = node.firstChild; child; child = child.nextSibling) 226 | data.childNodes.push(this.serializeNode(child, true)); 227 | } 228 | break; 229 | } 230 | 231 | return data; 232 | }, 233 | 234 | serializeAddedAndMoved: function(changed) { 235 | var all = changed.added.concat(changed.reparented).concat(changed.reordered); 236 | 237 | var parentMap = new MutationSummary.NodeMap; 238 | all.forEach(function(node) { 239 | var parent = node.parentNode; 240 | var children = parentMap.get(parent) 241 | if (!children) { 242 | children = new MutationSummary.NodeMap; 243 | parentMap.set(parent, children); 244 | } 245 | 246 | children.set(node, true); 247 | }); 248 | 249 | var moved = []; 250 | 251 | parentMap.keys().forEach(function(parent) { 252 | var children = parentMap.get(parent); 253 | 254 | var keys = children.keys(); 255 | while (keys.length) { 256 | var node = keys[0]; 257 | while (node.previousSibling && children.has(node.previousSibling)) 258 | node = node.previousSibling; 259 | 260 | while (node && children.has(node)) { 261 | moved.push({ 262 | node: this.serializeNode(node), 263 | previousSibling: this.serializeNode(node.previousSibling), 264 | parentNode: this.serializeNode(node.parentNode) 265 | }); 266 | 267 | children.delete(node); 268 | node = node.nextSibling; 269 | } 270 | 271 | var keys = children.keys(); 272 | } 273 | }, this); 274 | 275 | return moved; 276 | }, 277 | 278 | serializeAttributeChanges: function(attributeChanged) { 279 | var map = new MutationSummary.NodeMap; 280 | 281 | Object.keys(attributeChanged).forEach(function(attrName) { 282 | attributeChanged[attrName].forEach(function(element) { 283 | var record = map.get(element); 284 | if (!record) { 285 | record = { 286 | node: this.serializeNode(element), 287 | attributes: {} 288 | }; 289 | map.set(element, record); 290 | } 291 | 292 | record.attributes[attrName] = element.getAttribute(attrName); 293 | }, this); 294 | }, this); 295 | 296 | return map.keys().map(function(element) { 297 | return map.get(element); 298 | }); 299 | }, 300 | 301 | serializeCharacterDataChange: function(node) { 302 | return { 303 | node: this.serializeNode(node), 304 | textContent: node.textContent 305 | } 306 | }, 307 | 308 | applyChanged: function(summaries) { 309 | var changed = summaries[0] 310 | var removed = changed.removed.map(this.serializeNode, this); 311 | var moved = this.serializeAddedAndMoved(changed); 312 | var attributes = this.serializeAttributeChanges(changed.attributeChanged); 313 | var text = changed.characterDataChanged.map(this.serializeCharacterDataChange, this); 314 | 315 | this.mirror.applyChanged(removed, moved, attributes, text); 316 | 317 | changed.removed.forEach(this.forgetNode, this); 318 | } 319 | } -------------------------------------------------------------------------------- /public/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------