Pecoes Wiki
Advertisement
1 /**

2 * @fileOverview 19 June 2012 -- This file defines the Railgun client, a user script which 3 * provides a set of additional features for the Wikia Rail. It is designed for personal usage 4 * on all .wikia.com wikis. 5 * @author Jeff Bradford (User:Mathmagician) 6 * @version 2.1.0 7 */ 8 9 /** 10 * @namespace A wrapper object for all Railgun client methods and modules. In this context, 11 * a "module" refers to a single section tag of the Wikia Rail, as well as all associated 12 * states and behaviors of that entity. All modules are themselves objects in the Railgun 13 * namespace. The Railgun.Storage namespace is not technically considered a module. 14 */ 15 Railgun = { 16 /** 17 * Specifies the debug mode of the Railgun client. In debug mode, the Railgun stylesheet 18 * and Railgun server pages are non-cached, and some extra data is printed to the console 19 * for testing purposes. 20 * @field 21 * @type Boolean 22 * @default false 23 */ 24 isDebug : false, 25 26 /** 27 * The version of the Railgun client source code. 28 * @field 29 * @type String 30 */ 31 version : "2.1.0" 32 }; 33 34 /** 35 * @namespace A wrapper object defining Railgun's storage namespace, which is responsible for 36 * handling communications with the Railgun server. 37 */ 38 Railgun.Storage = { 39 /** 40 * The domain where the Railgun server is located. 41 * @constant 42 * @type String 43 * @default "http://railgunscript.wikia.com" 44 */ 45 domain : "http://railgunscript.wikia.com", 46 47 /** 48 * A reference to the iframe HTML element which is appended to the body of the main page 49 * when initialized in <code>Railgun.Storage.init()</code>. The client communicates with 50 * the server by issuing postMessage requests to the iframe's contentWindow property. 51 * @type Object 52 * @see Railgun.Storage.init() 53 */ 54 iframe : null, 55 56 /** 57 * A copy of all the properties of the RailgunServer object. This property is 58 * initialized in <code>Railgun.initProcessServerResponse()</code>. 59 * @type Object 60 * @see Railgun.initProcessServerResponse() 61 */ 62 serverState : null, 63 64 /** 65 * A copy of all the contents of localStorage provided by the server upon pageload. This 66 * property is initialized in <code>Railgun.initProcessServerResponse()</code>. 67 * @type Object 68 * @see Railgun.initProcessServerResponse() 69 */ 70 storageState : null, 71 72 /** 73 * The main line of communication from client to server. This method accepts a 74 * request object as a parameter, converts it to JSON and sends it to the 75 * server via iframe.contentWindow.postMessage(). Sample usage:<br> 76 * <code>Railgun.Storage.sendRequest({<br> 77 * id : "showSiderail()",<br> 78 * instruction : "setItem",<br> 79 * key : "siderailHidden",<br> 80 * value : false 81 * });</code> 82 * @param {Object} request a request object to be sent to the server 83 */ 84 sendRequest : function (request) { 85 // console.log for debugging 86 if (Railgun.isDebug) { 87 console.log("Client issuing " + request.instruction + " request to the server:"); 88 console.log(request); 89 } 90 // issue request to the server 91 this.iframe.contentWindow.postMessage(JSON.stringify(request), this.domain); 92 }, 93 94 /** 95 * The initialization method for the Storage object. This method loads the server into an 96 * iframe and appends it to the body of the document. 97 */ 98 init : function () { 99 // generate HTML for the iframe 100 var iframe = '<iframe id="railgun-server"' 101 + 'style="display: none; position: absolute; left: -1px; width: 1px;"' 102 + 'src="' + this.domain; 103 if (Railgun.isDebug) { 104 iframe += '/wiki/RailgunServerDebug?action=render"></iframe>'; 105 } else { 106 iframe += '/wiki/RailgunServer?action=render"></iframe>'; 107 } 108 109 // insert iframe into the page 110 $(document.body).append(iframe); 111 112 // set Storage.iframe property 113 this.iframe = document.getElementById('railgun-server'); 114 } 115 }; // End definition of Railgun.Storage object 116 117 /** 118 * @namespace A wrapper object containing functionality for the a feature of Railgun that 119 * expands the content area. The <code>siderailHidden</code> property of this object 120 * has a counterpart in localStorage. 121 */ 122 Railgun.ShowHideSiderail = { 123 /** 124 * Stores the current state of the siderail: <code>true</code> if hidden, 125 * <code>false</code> otherwise. 126 * @type Boolean 127 * @default false 128 */ 129 siderailHidden : false, 130 131 /** 132 * Constant which contains the HTML code for the left arrow in the user interface. The 133 * arrow appears in the toolbar and has the railgun-siderail-left-arrow CSS class. When 134 * clicked, this arrow should reveal the siderail. 135 * @type String 136 * @constant 137 */ 138 leftArrow : '<img class="railgun-siderail-left-arrow" ' 139 + 'src="ArrowLeft.png" ' 140 + 'onclick="Railgun.ShowHideSiderail.showSiderail();">', 141 142 /** 143 * Constant which contains the HTML code for the right arrow in the user interface. The 144 * arrow appears in the toolbar and has the railgun-siderail-left-arrow CSS class. When 145 * clicked, this arrow should collapse the siderail. 146 * @type String 147 * @constant 148 */ 149 rightArrow : '<img class="railgun-siderail-right-arrow" ' 150 + 'src="ArrowRight.png" ' 151 + 'onclick="Railgun.ShowHideSiderail.hideSiderail();">', 152 153 /** 154 * Shows the siderail upon clicking the show siderail (leftArrow) button and issues 155 * a request to store siderailHidden == false in storage. 156 * @see Railgun.ShowHideSiderail.leftArrow 157 */ 158 showSiderail : function () { 159 // save state: siderailHidden == false in storage 160 Railgun.Storage.sendRequest({ 161 id : "showSiderail()", 162 instruction : "setItem", 163 key : "siderailHidden", 164 value : false 165 }); 166 167 // apply CSS (see stylesheet) to show the siderail 168 $('.WikiaRail, .WikiaMainContent, .catlinks').removeClass('railgun-no-siderail'); 169 170 // reveal the "hide siderail" arrow 171 $('.railgun-siderail-left-arrow').css('display', 'none'); 172 $('.railgun-siderail-right-arrow').css('display', 'block'); 173 }, 174 175 /** 176 * Shows the siderail upon clicking the hide siderail (rightArrow) button and issues 177 * a request to store siderailHidden == true in storage. 178 * @see Railgun.ShowHideSiderail.rightArrow 179 */ 180 hideSiderail : function () { 181 // save state: siderailHidden == true in storage 182 Railgun.Storage.sendRequest({ 183 id : "hideSiderail()", 184 instruction : "setItem", 185 key : "siderailHidden", 186 value : true 187 }); 188 189 // apply CSS (see stylesheet) to hide the siderail 190 $('.WikiaRail, .WikiaMainContent, .catlinks').addClass('railgun-no-siderail'); 191 192 // reveal the "show siderail" arrow 193 $('.railgun-siderail-left-arrow').css('display', 'block'); 194 $('.railgun-siderail-right-arrow').css('display', 'none'); 195 }, 196 197 /** 198 * Initialization method for the ShowHideSiderail module. Initializes the <code> 199 * siderailHidden</code> property and modifies the DOM accordingly. 200 */ 201 init : function () { 202 // initialize siderailHidden property 203 this.siderailHidden = Railgun.Storage.storageState.siderailHidden ? true : false; 204 205 // add arrows to show and hide the siderail into the document 206 $('.WikiaFooter .toolbar').prepend(this.leftArrow + this.rightArrow); 207 208 if (true === this.siderailHidden) { 209 // apply CSS (see stylesheet) to hide the siderail 210 $('.WikiaRail, .WikiaMainContent, .catlinks').addClass('railgun-no-siderail'); 211 212 // reveal the "show siderail" arrow 213 $('.railgun-siderail-left-arrow').css('display', 'block'); 214 $('.railgun-siderail-right-arrow').css('display', 'none'); 215 } else { 216 // reveal the "hide siderail" arrow 217 $('.railgun-siderail-left-arrow').css('display', 'none'); 218 $('.railgun-siderail-right-arrow').css('display', 'block'); 219 } 220 } 221 }; 222 223 /** 224 * @namespace A wrapper object around all properties and methods that make up the Friend's 225 * List module. The <code>friends</code> property of this object has a counterpart in 226 * localStorage. 227 */ 228 Railgun.FriendsList = { 229 /** 230 * An array of objects representing all of the user's friends, initialized in 231 * <code>Railgun.initProcessServerResponse()</code> and kept sorted by username in 232 * the <code>addFriend()</code> method. Each element of the array is an object of 233 * the form: <code>{ homewiki : "http://mathmagician.wikia.com", html : 'tr ... /tr', 234 * username : "Mathmagician" }</code> 235 * @type Array 236 * @see Railgun.initProcessServerResponse 237 */ 238 friends : null, 239 240 /** 241 * HTML code for the message which is displayed to the user when the Friend's List is empty. 242 * This message has the railgun-no-friends-message CSS class, and is initialized in 243 * the Railgun.FriendsList.init() method. 244 * @type String 245 * @constant 246 * @see Railgun.FriendsList.init() 247 */ 248 noFriendsMessage : '<p class="railgun-no-friends-message">' 249 + 'It looks like you haven\'t made any friends yet. ' 250 + 'To add someone to your friend\'s list, simply click the icon ' 251 + 'on their profile masthead. ' 252 + 'There\'s no hurry, Fluttershy will keep you company in the meantime.' 253 + '<img ' 254 + 'src="Fus_Ro_Yay%21.gif">' 255 + '</p>', 256 257 /** 258 * HTML code for the add friend button that appears on profile mastheads. 259 * This message has the railgun-add-friend-image CSS class, and is initialized in 260 * the Railgun.FriendsList.init() method. 261 * @type String 262 * @constant 263 * @see Railgun.FriendsList.init() 264 */ 265 addFriendProfileImage : '<img ' 266 + 'src="' 267 + 'mathmagician/images/0/00/Bomb_Omb_30px.png" ' 268 + 'onclick="Railgun.FriendsList.addFriend();" ' 269 + 'class="railgun-add-friend-image" ' 270 + 'title="This user is not your friend. Click to explodify!">', 271 272 /** 273 * HTML code for the remove friend button that appears on profile mastheads. 274 * This message has the railgun-remove-friend-image CSS class, and is initialized in 275 * the Railgun.FriendsList.init() method. 276 * @type String 277 * @constant 278 * @see Railgun.FriendsList.init() 279 */ 280 removeFriendProfileImage : '<img ' 281 + 'src="' 282 + 'mathmagician/images/7/79/Star_30px.png" ' 283 + 'onclick="Railgun.FriendsList.removeFriend();" ' 284 + 'class="railgun-remove-friend-image" ' 285 + 'title="Friendship is witchcraft. Click to steal your friend\'s star!">', 286 287 /** 288 * Shows friend's links on mouseover and hides them again on mouseout. 289 * @param {Object} element a table row element containing HTML for a single friend 290 * @param {Boolean} show <code>true</code> to show links, <code>false</code> to hide them 291 */ 292 toggleLinks : function (element, show) { 293 $(element) 294 .find('.railgun-friend-td2-span2') 295 .css('visibility', show ? 'visible' : 'hidden'); 296 }, 297 298 /** 299 * Creates a table row element for a single friend to be added to the Friend's List. 300 * @param {String} username name of a Wikia user, such as "Mathmagician" 301 * @returns {String} a table row HTML element with the railgun-friend-tr CSS class. 302 */ 303 createFriendHTML : function (username) { 304 var encoded = encodeURIComponent(username.replace(/ /, '_')); 305 306 var imagelink = '<a href="/wiki/User:' + encoded + '">'; 307 var image = '<img src="' + $('.masthead-avatar .avatar').attr('src') 308 + '" class="railgun-friend-avatar">'; 309 var tr = '<tr class="railgun-friend-tr" data-user="' + username + '" ' 310 + 'onmouseover="Railgun.FriendsList.toggleLinks(this, true);" ' 311 + 'onmouseout="Railgun.FriendsList.toggleLinks(this, false);">'; 312 var td1 = '<td class="railgun-friend-td1">'; 313 var td2 = '<td class="railgun-friend-td2">'; 314 var td2span1 = '<span class="railgun-friend-td2-span1">'; 315 var profile = '<a href="/wiki/User:' + encoded + '">' + username + '</a>'; 316 var td2span2 = '<span class="railgun-friend-td2-span2">'; 317 var talk = '[<a href="/wiki/User_talk:' + encoded + '">talk</a>]'; 318 var blog = ' [<a href="/wiki/User_blog:' + encoded + '">blog</a>]'; 319 var contrib = ' [<a href="/wiki/Special:Contributions/' + encoded + '">contrib</a>]'; 320 var count = ' [<a href="/wiki/Special:Editcount/' + encoded + '">count</a>]'; 321 var log = ' [<a href="/wiki/Special:Log/' + encoded + '">log</a>]'; 322 var sub = ' [<a href="/wiki/Special:PrefixIndex/User:' + encoded + '">subpages</a>]'; 323 324 var html = tr + td1 + imagelink + image + '</a></td>' + td2 + td2span1 + profile 325 + '</span><br />' + td2span2 + talk + blog + contrib + count + log + sub 326 + '</span></td></tr>'; 327 328 return html; 329 }, 330 331 /** 332 * Adds a single friend to the Friend's List upon clicking the add friend button and 333 * issues a request to save the updated <code>friends</code> array to localStorage. 334 * This method sorts <code>Railgun.FriendsList.friends</code> alphabetically by 335 * username and does not permit duplicates. 336 * @see Railgun.FriendsList.addFriendProfileImage 337 * @see Railgun.FriendsList.friends 338 */ 339 addFriend : function () { 340 var username = $('.masthead-info h1').text(); 341 var html = this.createFriendHTML(username); 342 var index = -1; 343 344 // remove this friend (don't allow duplicates) 345 for (var i = 0; i < this.friends.length; i++) { 346 if (this.friends[i].username === username) { 347 index = i; 348 break; 349 } 350 } 351 if (-1 !== index) { 352 this.friends.splice(index, 1); 353 } 354 355 // add friend object to friends array 356 this.friends.push({ 357 username : username, 358 html : html, 359 homewiki : wgServer 360 }); 361 362 // sort friends array 363 this.friends.sort(function (a, b) { 364 if (a.username < b.username) 365 return -1; 366 else if (a.username == b.username) 367 return 0; 368 else 369 return 1; 370 }); 371 372 // save friends in localStorage 373 Railgun.Storage.sendRequest({ 374 id : "addFriend()", 375 instruction : "setItem", 376 key : "friends", 377 value : this.friends 378 }); 379 380 // remove no friends message 381 $('.railgun-no-friends-message').remove(); 382 383 // add new friend into the document 384 var friendsListed = $('.railgun-friend-tr'); 385 if (0 === friendsListed.length) { 386 $('.railgun-friend-table').append(html); 387 } else if (username < friendsListed.first().attr('data-user')) { 388 $('.railgun-friend-table').prepend(html); 389 } else if (username > friendsListed.last().attr('data-user')) { 390 $('.railgun-friend-table').append(html); 391 } else { 392 for (var i = 0; i < friendsListed.length; i++) { 393 if (username < $(friendsListed[i]).attr('data-user')) { 394 $(friendsListed[i]).before(html); 395 break; 396 } 397 } 398 } 399 400 // update profile masthead image 401 $('.railgun-add-friend-image').replaceWith(this.removeFriendProfileImage); 402 }, 403 404 /** 405 * Removes a single friend from the Friend's List upon clicking the remove 406 * friend button and issues a request to save the updated <code>friends</code> 407 * array in localStorage. 408 * @see Railgun.FriendsList.friends 409 */ 410 removeFriend : function () { 411 var username = $('.masthead-info h1').text(); 412 var friendsListed = $('.railgun-friend-tr'); 413 var index = -1; 414 415 // remove friend 416 for (var i = 0; i < this.friends.length; i++) { 417 if (this.friends[i].username === username) { 418 index = i; 419 break; 420 } 421 } 422 if (-1 !== index) { 423 this.friends.splice(index, 1); 424 } 425 426 // save friends in localStorage 427 Railgun.Storage.sendRequest({ 428 id : "removeFriend()", 429 instruction : "setItem", 430 key : "friends", 431 value : this.friends 432 }); 433 434 // remove friend from the document 435 switch (friendsListed.length) 436 { 437 case 0 : 438 break; 439 case 1 : 440 friendsListed.replaceWith(this.noFriendsMessage); 441 break; 442 default : 443 for (var i = 0; i < friendsListed.length; i++) { 444 if (username === $(friendsListed[i]).attr('data-user')) { 445 $(friendsListed[i]).remove(); 446 break; 447 } 448 } 449 } 450 451 // update profile masthead image 452 $('.railgun-remove-friend-image').replaceWith(this.addFriendProfileImage); 453 }, 454 455 /** 456 * Initialization method for the Friend's List module. This method initializes the 457 * <code>friends</code> property and creates the visual representation of the module 458 * on the siderail. 459 */ 460 init : function () { 461 var section = '<section class="railgun-friend-module module">'; 462 var header = '<h1>Friend\'s List</h1>'; 463 var table = '<table class="railgun-friend-table" cellspacing="3">'; 464 465 // initialize friends property 466 this.friends = Railgun.Storage.storageState.friends || []; 467 468 // set friendsHTML 469 var friendsUsername = []; 470 var friendsHTML = ; 471 if (0 === this.friends.length) { 472 friendsHTML = this.noFriendsMessage; 473 } else { 474 for (var i = 0; i < this.friends.length; i++) { 475 friendsUsername[i] = this.friends[i].username; 476 friendsHTML += this.friends[i].html; 477 } 478 } 479 480 // add Friend's List module to the siderail 481 var html = section + header + table + friendsHTML + '</table></section>'; 482 if ($('#WikiaSearch').parent().attr('id') === "WikiaRail") { 483 $('#WikiaSearch').after(html); 484 } else { 485 $('#WikiaRail').prepend(html); 486 } 487 488 // add friend / defriend images to profile masthead 489 var mastheadInfo = $('.masthead-info h1'); 490 if (0 !== mastheadInfo.length) { 491 // append image to the profile masthead 492 if (-1 === friendsUsername.indexOf(mastheadInfo.text())) { 493 $('.masthead-info hgroup').append(Railgun.FriendsList.addFriendProfileImage); 494 } else { 495 $('.masthead-info hgroup').append(Railgun.FriendsList.removeFriendProfileImage); 496 } 497 } 498 } 499 }; // End definition of Railgun.FriendsList object 500 501 /** 502 * Loads all modules after all localStorage data has been copied to the Storage namespace. 503 * This method un-registers <code>Railgun.initProcessServerResponse()</code> as a message event 504 * listener for the window. 505 * @see Railgun.initProcessServerResponse() 506 */ 507 Railgun.initLoadModules = function () { 508 // un-register the Railgun.initProcessServerResponse() "message" event listener for this window 509 if (window.removeEventListener) { 510 window.removeEventListener("message", Railgun.initProcessServerResponse, false); 511 } else if (window.detachEvent) { 512 window.detachEvent("message", Railgun.initProcessServerResponse); 513 } 514 515 // load all modules 516 Railgun.ShowHideSiderail.init(); 517 Railgun.FriendsList.init(); 518 }; 519 520 /** 521 * Processes the initialization response from the Railgun server after the retrieve request 522 * has been made by Railgun.initRequestServer(). This method initializes the 523 * <code>Railgun.Storage.serverState</code> and <code>Railgun.Storage.storageState</code> 524 * properties in the Storage namespace and then calls <code>Railgun.initLoadModules()</code> 525 * @param {MessageEvent} event a message event representing the server's response 526 * @see Railgun.Storage.serverState 527 * @see Railgun.Storage.storageState 528 * @see Railgun.initLoadModules() 529 */ 530 Railgun.initProcessServerResponse = function (event) { 531 if (event.origin !== Railgun.Storage.domain) 532 return; // only process requests from Railgun.Storage.domain 533 534 // initialize data in Storage namespace to values received from the server 535 var data = JSON.parse(event.data); 536 Railgun.Storage.serverState = data.serverState; 537 Railgun.Storage.storageState = data.storageState; 538 539 // load the rest of the modules 540 Railgun.initLoadModules(); 541 }; 542 543 /** 544 * The client's iframe onload event listener that sends a retrieve request to the server to 545 * get all data from localStorage. Execution passes to <code> 546 * Railgun.initProcessServerResponse()</code> after the server has handled the request. 547 * @see Railgun.initProcessServerResponse() 548 */ 549 Railgun.initRequestServer = function () { 550 // register Railgun.initProcessServerResponse() as a "message" event listener for this window 551 if (window.addEventListener) { 552 window.addEventListener("message", Railgun.initProcessServerResponse, false); 553 } else if (window.attachEvent) { 554 window.attachEvent("message", Railgun.initProcessServerResponse); 555 } 556 557 // send initialization info to the server 558 Railgun.Storage.sendRequest({ 559 instruction : "retrieve", 560 isDebug : Railgun.isDebug 561 }); 562 }; 563 564 /** 565 * Railgun's main initialization method which is called upon pageload. This method is 566 * responsible for preventing Railgun from loading if the usage preconditions are not 567 * met. It also attaches the Railgun stylesheet to the head of the document, 568 * loads the iframe into the document by initializing the Storage object, and 569 * passes execution to <code>Railgun.initRequestServer()</code> when the iframe 570 * has loaded. 571 * @see Railgun.initRequestServer() 572 * @see Railgun.Storage.iframe 573 */ 574 Railgun.init = function () { 575 if (null === wgUserName) { 576 if (Railgun.isDebug) 577 console.log("Railgun didn't initialize because: not logged in"); 578 return; // Don't run if user is not signed in 579 } else if ("oasis" !== skin) { 580 if (Railgun.isDebug) 581 console.log("Railgun didn't initialize because: not using Oasis"); 582 return; // Don't run if user is not using Oasis skin 583 } else if (0 === $('.WikiaRail').length) { 584 if (Railgun.isDebug) 585 console.log("Railgun didn't initialize because: siderail absent"); 586 return; // Don't run if there's no siderail 587 } else if ("http://wikimarks.wikia.com" === wgServer) { 588 if (Railgun.isDebug) 589 console.log("Railgun didn't initialize because: domain is wikimarks"); 590 return; // Prevent interference with wikimarks 591 } 592 593 // attach CSS stylesheet 594 $('#railgun-stylesheet').remove(); 595 var href = Railgun.Storage.domain; 596 if (Railgun.isDebug) { 597 href += "/wiki/MediaWiki:RailgunStylesheetDebug.css?action=raw&ctype=text/css" 598 + "&maxage=0&smaxage=0"; 599 } else { 600 href += "/wiki/MediaWiki:RailgunStylesheet.css?action=raw&ctype=text/css"; 601 } 602 $(document.head).append('<link id="railgun-stylesheet" rel="stylesheet" type="text/css" ' 603 + 'href="' + href + '">'); 604 605 // initialize the Storage module first 606 Railgun.Storage.init(); 607 608 // register Railgun.initRequestServer() as the iframe onload event listener 609 if (Railgun.Storage.iframe.addEventListener) { 610 Railgun.Storage.iframe.addEventListener("load", Railgun.initRequestServer, false); 611 } else if (Railgun.Storage.iframe.attachEvent) { 612 Railgun.Storage.iframe.attachEvent("onload", Railgun.initRequestServer); 613 } 614 }; 615 616 // $(document).ready() 617 // Issues the command to initialize Railgun on pageload 618 $(function () { 619 Railgun.init();

620
});
Advertisement