/** * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* global $, window */ var socket; $(document).ready(function() { handshake(); }); $(window).unload(function() { pad.dispose(); }); function createCookie(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else var expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } function randomString() { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; var string_length = 20; var randomstring = ''; for (var i=0; i<string_length; i++) { var rnum = Math.floor(Math.random() * chars.length); randomstring += chars.substring(rnum,rnum+1); } return "t." + randomstring; } function handshake() { socket = new io.Socket(); socket.connect(); socket.on('connect', function(){ var padId= document.URL.substring(document.URL.lastIndexOf("/")+1); document.title = document.title + " | " + padId; var token = readCookie("token"); if(token == null) { token = randomString(); createCookie("token", token, 60); } var msg = { "type":"CLIENT_READY", "padId": padId, "token": token, "protocolVersion": 1}; socket.send(msg); }); var receivedClientVars=false; var initalized = false; socket.on('message', function(obj){ //if we haven't recieved the clientVars yet, then this message should it be if(!receivedClientVars) { //We get a disconnect message if(obj.disconnect) { socket.reconnect = false; socket.disconnect(); alert("You have this Pad already opened in another Window/Tab"); return; } //yeah, the clientVars are here :). So we can start initalizing the Pad else { if(window.console) console.log(obj); receivedClientVars=true; clientVars = obj; clientVars.userAgent=navigator.userAgent; clientVars.collab_client_vars.clientAgent=navigator.userAgent; pad.init(); initalized=true; } } //This handles every Message after the clientVars else { //We can't handle the message before we initalized the pad, so let this message bounce back in 100ms if(!initalized) { setTimeOut(this(obj), 100); } //We're initalized, so give this message to the collabClient else { pad.collabClient.handleMessageFromServer(obj); } } }); } var pad = { // don't access these directly from outside this file, except // for debugging collabClient: null, myUserInfo: null, diagnosticInfo: {}, initTime: 0, clientTimeOffset: (+new Date()) - clientVars.serverTimestamp, preloadedImages: false, padOptions: {}, // these don't require init; clientVars should all go through here getPadId: function() { return clientVars.padId; }, getClientIp: function() { return clientVars.clientIp; }, getIsProPad: function() { return clientVars.isProPad; }, getColorPalette: function() { return clientVars.colorPalette; }, getDisplayUserAgent: function() { return padutils.uaDisplay(clientVars.userAgent); }, getIsDebugEnabled: function() { return clientVars.debugEnabled; }, getPrivilege: function(name) { return clientVars.accountPrivs[name]; }, getUserIsGuest: function() { return clientVars.userIsGuest; }, // getUserId: function() { return pad.myUserInfo.userId; }, getUserName: function() { return pad.myUserInfo.name; }, sendClientMessage: function(msg) { pad.collabClient.sendClientMessage(msg); }, init: function() { pad.diagnosticInfo.uniqueId = padutils.uniqueId(); pad.initTime = +(new Date()); pad.padOptions = clientVars.initialOptions; if ((! $.browser.msie) && (! ($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { document.domain = document.domain; // for comet } // for IE if ($.browser.msie) { try { doc.execCommand("BackgroundImageCache", false, true); } catch (e) {} } // order of inits is important here: padcookie.init(clientVars.cookiePrefsToSet); $("#widthprefcheck").click(pad.toggleWidthPref); $("#sidebarcheck").click(pad.toggleSidebar); pad.myUserInfo = { userId: clientVars.userId, name: clientVars.userName, ip: pad.getClientIp(), colorId: clientVars.userColor, userAgent: pad.getDisplayUserAgent() }; if (clientVars.specialKey) { pad.myUserInfo.specialKey = clientVars.specialKey; if (clientVars.specialKeyTranslation) { $("#specialkeyarea").html("mode: "+ String(clientVars.specialKeyTranslation).toUpperCase()); } } paddocbar.init({isTitleEditable: pad.getIsProPad(), initialTitle:clientVars.initialTitle, initialPassword:clientVars.initialPassword, guestPolicy: pad.padOptions.guestPolicy }); padimpexp.init(); padsavedrevs.init(clientVars.initialRevisionList); padeditor.init(postAceInit, pad.padOptions.view || {}); paduserlist.init(pad.myUserInfo); // padchat.init(clientVars.chatHistory, pad.myUserInfo); padconnectionstatus.init(); padmodals.init(); pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, { colorPalette: pad.getColorPalette() }); pad.collabClient.setOnUserJoin(pad.handleUserJoin); pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate); pad.collabClient.setOnUserLeave(pad.handleUserLeave); pad.collabClient.setOnClientMessage(pad.handleClientMessage); pad.collabClient.setOnServerMessage(pad.handleServerMessage); pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange); pad.collabClient.setOnInternalAction(pad.handleCollabAction); function postAceInit() { padeditbar.init(); setTimeout(function() { padeditor.ace.focus(); }, 0); } }, dispose: function() { padeditor.dispose(); }, notifyChangeName: function(newName) { pad.myUserInfo.name = newName; pad.collabClient.updateUserInfo(pad.myUserInfo); //padchat.handleUserJoinOrUpdate(pad.myUserInfo); }, notifyChangeColor: function(newColorId) { pad.myUserInfo.colorId = newColorId; pad.collabClient.updateUserInfo(pad.myUserInfo); //padchat.handleUserJoinOrUpdate(pad.myUserInfo); }, notifyChangeTitle: function(newTitle) { pad.collabClient.sendClientMessage({ type: 'padtitle', title: newTitle, changedBy: pad.myUserInfo.name || "unnamed" }); }, notifyChangePassword: function(newPass) { pad.collabClient.sendClientMessage({ type: 'padpassword', password: newPass, changedBy: pad.myUserInfo.name || "unnamed" }); }, changePadOption: function(key, value) { var options = {}; options[key] = value; pad.handleOptionsChange(options); pad.collabClient.sendClientMessage({ type: 'padoptions', options: options, changedBy: pad.myUserInfo.name || "unnamed" }); }, changeViewOption: function(key, value) { var options = {view: {}}; options.view[key] = value; pad.handleOptionsChange(options); pad.collabClient.sendClientMessage({ type: 'padoptions', options: options, changedBy: pad.myUserInfo.name || "unnamed" }); }, handleOptionsChange: function(opts) { // opts object is a full set of options or just // some options to change if (opts.view) { if (! pad.padOptions.view) { pad.padOptions.view = {}; } for(var k in opts.view) { pad.padOptions.view[k] = opts.view[k]; } padeditor.setViewOptions(pad.padOptions.view); } if (opts.guestPolicy) { // order important here pad.padOptions.guestPolicy = opts.guestPolicy; paddocbar.setGuestPolicy(opts.guestPolicy); } }, getPadOptions: function() { // caller shouldn't mutate the object return pad.padOptions; }, isPadPublic: function() { return (! pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow'); }, suggestUserName: function(userId, name) { pad.collabClient.sendClientMessage({ type: 'suggestUserName', unnamedId: userId, newName: name }); }, handleUserJoin: function(userInfo) { paduserlist.userJoinOrUpdate(userInfo); //padchat.handleUserJoinOrUpdate(userInfo); }, handleUserUpdate: function(userInfo) { paduserlist.userJoinOrUpdate(userInfo); //padchat.handleUserJoinOrUpdate(userInfo); }, handleUserLeave: function(userInfo) { paduserlist.userLeave(userInfo); //padchat.handleUserLeave(userInfo); }, handleClientMessage: function(msg) { if (msg.type == 'suggestUserName') { if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && ! pad.myUserInfo.name) { pad.notifyChangeName(msg.newName); paduserlist.setMyUserInfo(pad.myUserInfo); } } else if (msg.type == 'chat') { //padchat.receiveChat(msg); } else if (msg.type == 'padtitle') { paddocbar.changeTitle(msg.title); } else if (msg.type == 'padpassword') { paddocbar.changePassword(msg.password); } else if (msg.type == 'newRevisionList') { padsavedrevs.newRevisionList(msg.revisionList); } else if (msg.type == 'revisionLabel') { padsavedrevs.newRevisionList(msg.revisionList); } else if (msg.type == 'padoptions') { var opts = msg.options; pad.handleOptionsChange(opts); } else if (msg.type == 'guestanswer') { // someone answered a prompt, remove it paduserlist.removeGuestPrompt(msg.guestId); } }, editbarClick: function(cmd) { if (padeditbar) { padeditbar.toolbarClick(cmd); } }, dmesg: function(m) { if (pad.getIsDebugEnabled()) { var djs = $('#djs').get(0); var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) >= -20); $('#djs').append('<p>'+m+'</p>'); if (wasAtBottom) { djs.scrollTop = djs.scrollHeight; } } }, handleServerMessage: function(m) { if (m.type == 'NOTICE') { if (m.text) { alertBar.displayMessage(function (abar) { abar.find("#servermsgdate").html(" ("+padutils.simpleDateTime(new Date)+")"); abar.find("#servermsgtext").html(m.text); }); } if (m.js) { window['ev'+'al'](m.js); } } else if (m.type == 'GUEST_PROMPT') { paduserlist.showGuestPrompt(m.userId, m.displayName); } }, handleChannelStateChange: function(newState, message) { var oldFullyConnected = !! padconnectionstatus.isFullyConnected(); var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting'); if (newState == "CONNECTED") { padconnectionstatus.connected(); } else if (newState == "RECONNECTING") { padconnectionstatus.reconnecting(); } else if (newState == "DISCONNECTED") { pad.diagnosticInfo.disconnectedMessage = message; pad.diagnosticInfo.padInitTime = pad.initTime; pad.asyncSendDiagnosticInfo(); if (typeof window.ajlog == "string") { window.ajlog += ("Disconnected: "+message+'\n'); } padeditor.disable(); padeditbar.disable(); paddocbar.disable(); padimpexp.disable(); padconnectionstatus.disconnected(message); } var newFullyConnected = !! padconnectionstatus.isFullyConnected(); if (newFullyConnected != oldFullyConnected) { pad.handleIsFullyConnected(newFullyConnected, wasConnecting); } }, handleIsFullyConnected: function(isConnected, isInitialConnect) { // load all images referenced from CSS, one at a time, // starting one second after connection is first established. if (isConnected && ! pad.preloadedImages) { window.setTimeout(function() { if (! pad.preloadedImages) { pad.preloadImages(); pad.preloadedImages = true; } }, 1000); } padsavedrevs.handleIsFullyConnected(isConnected); pad.determineSidebarVisibility(isConnected && ! isInitialConnect); }, determineSidebarVisibility: function(asNowConnectedFeedback) { if (pad.isFullyConnected()) { var setSidebarVisibility = padutils.getCancellableAction( "set-sidebar-visibility", function() { $("body").toggleClass('hidesidebar', !! padcookie.getPref('hideSidebar')); }); window.setTimeout(setSidebarVisibility, asNowConnectedFeedback ? 3000 : 0); } else { padutils.cancelActions("set-sidebar-visibility"); $("body").removeClass('hidesidebar'); } }, handleCollabAction: function(action) { if (action == "commitPerformed") { padeditbar.setSyncStatus("syncing"); } else if (action == "newlyIdle") { padeditbar.setSyncStatus("done"); } }, hideServerMessage: function() { alertBar.hideMessage(); }, asyncSendDiagnosticInfo: function() { pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); window.setTimeout(function() { $.ajax({ type: 'post', url: '/ep/pad/connection-diagnostic-info', data: {padId: pad.getPadId(), diagnosticInfo: JSON.stringify(pad.diagnosticInfo)}, success: function() {}, error: function() {} }); }, 0); }, forceReconnect: function() { $('form#reconnectform input.padId').val(pad.getPadId()); pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); $('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo)); $('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges())); $('form#reconnectform').submit(); }, toggleWidthPref: function() { var newValue = ! padcookie.getPref('fullWidth'); padcookie.setPref('fullWidth', newValue); $("#widthprefcheck").toggleClass('widthprefchecked', !!newValue).toggleClass( 'widthprefunchecked', !newValue); pad.handleWidthChange(); }, toggleSidebar: function() { var newValue = ! padcookie.getPref('hideSidebar'); padcookie.setPref('hideSidebar', newValue); $("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass( 'sidebarunchecked', !!newValue); pad.determineSidebarVisibility(); }, handleWidthChange: function() { var isFullWidth = padcookie.getPref('fullWidth'); if (isFullWidth) { $("body").addClass('fullwidth').removeClass('limwidth').removeClass( 'squish1width').removeClass('squish2width'); } else { $("body").addClass('limwidth').removeClass('fullwidth'); var pageWidth = $(window).width(); $("body").toggleClass('squish1width', (pageWidth < 912 && pageWidth > 812)).toggleClass( 'squish2width', (pageWidth <= 812)); } }, // this is called from code put into a frame from the server: handleImportExportFrameCall: function(callName, varargs) { padimpexp.handleFrameCall.call(padimpexp, callName, Array.prototype.slice.call(arguments, 1)); }, callWhenNotCommitting: function(f) { pad.collabClient.callWhenNotCommitting(f); }, getCollabRevisionNumber: function() { return pad.collabClient.getCurrentRevisionNumber(); }, isFullyConnected: function() { return padconnectionstatus.isFullyConnected(); }, addHistoricalAuthors: function(data) { if (! pad.collabClient) { window.setTimeout(function() { pad.addHistoricalAuthors(data); }, 1000); } else { pad.collabClient.addHistoricalAuthors(data); } }, preloadImages: function() { var images = [ '../static/img/colorpicker.gif' ]; function loadNextImage() { if (images.length == 0) { return; } var img = new Image(); img.src = images.shift(); if (img.complete) { scheduleLoadNextImage(); } else { $(img).bind('error load onreadystatechange', scheduleLoadNextImage); } } function scheduleLoadNextImage() { window.setTimeout(loadNextImage, 0); } scheduleLoadNextImage(); } }; var alertBar = (function() { var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400); function arriveAtAnimationState(state) { if (state == -1) { $("#alertbar").css('opacity', 0).css('display', 'block'); } else if (state == 0) { $("#alertbar").css('opacity', 1); } else if (state == 1) { $("#alertbar").css('opacity', 0).css('display', 'none'); } else if (state < 0) { $("#alertbar").css('opacity', state+1); } else if (state > 0) { $("#alertbar").css('opacity', 1 - state); } } var self = { displayMessage: function(setupFunc) { animator.show(); setupFunc($("#alertbar")); }, hideMessage: function() { animator.hide(); } }; return self; }());