diff --git a/src/node/db/API.js b/src/node/db/API.js index f99a43afd..07141fec2 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -325,17 +325,17 @@ exports.getChatHistory = function(padID, start, end, callback) if(!start || !end) { start = 0; - end = pad.chatHead - 1; + end = pad.chatHead; } - if(start >= chatHead) + if(start >= chatHead && chatHead > 0) { callback(new customError("start is higher or equal to the current chatHead","apierror")); return; } - if(end >= chatHead) + if(end > chatHead) { - callback(new customError("end is higher or equal to the current chatHead","apierror")); + callback(new customError("end is higher than the current chatHead","apierror")); return; } diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 6781cd884..69bb568db 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -35,11 +35,6 @@ var messageLogger = log4js.getLogger("message"); var _ = require('underscore'); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); -/** - * A associative array that saves which sessions belong to a pad - */ -var pad2sessions = {}; - /** * A associative array that saves informations about a session * key = sessionId @@ -83,14 +78,11 @@ exports.handleConnect = function(client) exports.kickSessionsFromPad = function(padID) { //skip if there is nobody on this pad - if(!pad2sessions[padID]) + if(socketio.sockets.clients(padID).length == 0) return; //disconnect everyone from this pad - for(var i in pad2sessions[padID]) - { - socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"}); - } + socketio.sockets.in(padID).json.send({disconnect:"deleted"}); } /** @@ -100,15 +92,13 @@ exports.kickSessionsFromPad = function(padID) exports.handleDisconnect = function(client) { //save the padname of this session - var sessionPad=sessioninfos[client.id].padId; + var session = sessioninfos[client.id]; //if this connection was already etablished with a handshake, send a disconnect message to the others - if(sessioninfos[client.id] && sessioninfos[client.id].author) + if(session && session.author) { - var author = sessioninfos[client.id].author; - //get the author color out of the db - authorManager.getAuthorColorId(author, function(err, color) + authorManager.getAuthorColorId(session.author, function(err, color) { ERR(err); @@ -121,33 +111,16 @@ exports.handleDisconnect = function(client) "ip": "127.0.0.1", "colorId": color, "userAgent": "Anonymous", - "userId": author + "userId": session.author } } }; //Go trough all user that are still on the pad, and send them the USER_LEAVE message - for(i in pad2sessions[sessionPad]) - { - var socket = socketio.sockets.sockets[pad2sessions[sessionPad][i]]; - if(socket !== undefined){ - socket.json.send(messageToTheOtherUsers); - } - - } + client.broadcast.to(session.padId).json.send(messageToTheOtherUsers); }); } - //Go trough all sessions of this pad, search and destroy the entry of this client - for(i in pad2sessions[sessionPad]) - { - if(pad2sessions[sessionPad][i] == client.id) - { - pad2sessions[sessionPad].splice(i, 1); - break; - } - } - //Delete the sessioninfos entrys of this session delete sessioninfos[client.id]; } @@ -228,11 +201,10 @@ exports.handleMessage = function(client, message) function(callback) { - if(!message.padId){ - // If the message has a padId we assume the client is already known to the server and needs no re-authorization - callback(); - return; - } + // If the message has a padId we assume the client is already known to the server and needs no re-authorization + if(!message.padId) + return callback(); + // Note: message.sessionID is an entirely different kind of // session from the sessions we use here! Beware! FIXME: Call // our "sessions" "connections". @@ -292,9 +264,7 @@ exports.handleCustomMessage = function (padID, msg, cb) { time: time } }; - for (var i in pad2sessions[padID]) { - socketio.sockets.sockets[pad2sessions[padID][i]].json.send(msg); - } + socketio.sockets.in(padID).json.send(msg); cb(null, {}); } @@ -352,10 +322,7 @@ function handleChatMessage(client, message) }; //broadcast the chat message to everyone on the pad - for(var i in pad2sessions[padId]) - { - socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg); - } + socketio.sockets.in(padId).json.send(msg); callback(); } @@ -413,23 +380,16 @@ function handleGetChatMessages(client, message) { if(ERR(err, callback)) return; - var infoMsg = { - type: "COLLABROOM", - data: { - type: "CHAT_MESSAGES", - messages: chatMessages - } - }; - - // send the messages back to the client - for(var i in pad2sessions[padId]) - { - if(pad2sessions[padId][i] == client.id) - { - socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg); - break; + var infoMsg = { + type: "COLLABROOM", + data: { + type: "CHAT_MESSAGES", + messages: chatMessages } - } + }; + + // send the messages back to the client + client.json.send(infoMsg); }); }]); } @@ -453,14 +413,14 @@ function handleSuggestUserName(client, message) return; } - var padId = sessioninfos[client.id].padId; + var padId = sessioninfos[client.id].padId, + clients = socketio.sockets.clients(padId); //search the author and send him this message - for(var i in pad2sessions[padId]) - { - if(sessioninfos[pad2sessions[padId][i]].author == message.data.payload.unnamedId) - { - socketio.sockets.sockets[pad2sessions[padId][i]].send(message); + for(var i = 0; i < clients.length; i++) { + var session = sessioninfos[clients[i].id]; + if(session && session.author == message.data.payload.unnamedId) { + clients[i].json.send(message); break; } } @@ -501,7 +461,8 @@ function handleUserInfoUpdate(client, message) type: "USER_NEWINFO", userInfo: { userId: author, - name: message.data.userInfo.name, + //set a null name, when there is no name set. cause the client wants it null + name: message.data.userInfo.name || null, colorId: message.data.userInfo.colorId, userAgent: "Anonymous", ip: "127.0.0.1", @@ -509,20 +470,8 @@ function handleUserInfoUpdate(client, message) } }; - //set a null name, when there is no name set. cause the client wants it null - if(infoMsg.data.userInfo.name == null) - { - infoMsg.data.userInfo.name = null; - } - //Send the other clients on the pad the update message - for(var i in pad2sessions[padId]) - { - if(pad2sessions[padId][i] != client.id) - { - socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg); - } - } + client.broadcast.to(padId).json.send(infoMsg); } /** @@ -682,90 +631,76 @@ function handleUserChanges(client, message) exports.updatePadClients = function(pad, callback) { //skip this step if noone is on this pad - if(!pad2sessions[pad.id]) - { - callback(); - return; - } + var roomClients = socketio.sockets.clients(pad.id); + if(roomClients.length==0) + return callback(); + // since all clients usually get the same set of changesets, store them in local cache + // to remove unnecessary roundtrip to the datalayer + // TODO: in REAL world, if we're working without datalayer cache, all requests to revisions will be fired + // BEFORE first result will be landed to our cache object. The solution is to replace parallel processing + // via async.forEach with sequential for() loop. There is no real benefits of running this in parallel, + // but benefit of reusing cached revision object is HUGE + var revCache = {}; + //go trough all sessions on this pad - async.forEach(pad2sessions[pad.id], function(session, callback) + async.forEach(roomClients, function(client, callback) { + var sid = client.id; //https://github.com/caolan/async#whilst //send them all new changesets async.whilst( - function (){ return sessioninfos[session] && sessioninfos[session].rev < pad.getHeadRevisionNumber()}, + function (){ return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()}, function(callback) { - var author, revChangeset, currentTime; - var r = sessioninfos[session].rev + 1; - - async.parallel([ - function (callback) - { - pad.getRevisionAuthor(r, function(err, value) - { - if(ERR(err, callback)) return; - author = value; - callback(); - }); - }, - function (callback) - { - pad.getRevisionChangeset(r, function(err, value) - { - if(ERR(err, callback)) return; - revChangeset = value; - callback(); - }); - }, - function (callback) - { - pad.getRevisionDate(r, function(err, date) - { - if(ERR(err, callback)) return; - currentTime = date; - callback(); - }); - } - ], function(err) - { - if(ERR(err, callback)) return; - // next if session has not been deleted - if(sessioninfos[session] == null) - { - callback(null); - return; - } - if(author == sessioninfos[session].author) - { - socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); - } - else - { - var forWire = Changeset.prepareForWire(revChangeset, pad.pool); - var wireMsg = {"type":"COLLABROOM", - "data":{type:"NEW_CHANGES", - newRev:r, - changeset: forWire.translated, - apool: forWire.pool, - author: author, - currentTime: currentTime, - timeDelta: currentTime - sessioninfos[session].time - }}; - - socketio.sockets.sockets[session].json.send(wireMsg); - } + var r = sessioninfos[sid].rev + 1; - if(sessioninfos[session] != null) - { - sessioninfos[session].time = currentTime; - sessioninfos[session].rev = r; - } - - callback(null); - }); + async.waterfall([ + function(callback) { + if(revCache[r]) + callback(null, revCache[r]); + else + pad.getRevision(r, callback); + }, + function(revision, callback) + { + revCache[r] = revision; + + var author = revision.meta.author, + revChangeset = revision.changeset, + currentTime = revision.meta.timestamp; + + // next if session has not been deleted + if(sessioninfos[sid] == null) + return callback(null); + + if(author == sessioninfos[sid].author) + { + client.json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); + } + else + { + var forWire = Changeset.prepareForWire(revChangeset, pad.pool); + var wireMsg = {"type":"COLLABROOM", + "data":{type:"NEW_CHANGES", + newRev:r, + changeset: forWire.translated, + apool: forWire.pool, + author: author, + currentTime: currentTime, + timeDelta: currentTime - sessioninfos[sid].time + }}; + + client.json.send(wireMsg); + } + + sessioninfos[sid].time = currentTime; + sessioninfos[sid].rev = r; + + callback(null); + } + ], callback); }, callback ); @@ -895,23 +830,14 @@ function handleClientReady(client, message) function(callback) { async.parallel([ - //get colorId + //get colorId and name function(callback) { - authorManager.getAuthorColorId(author, function(err, value) + authorManager.getAuthor(author, function(err, value) { if(ERR(err, callback)) return; - authorColorId = value; - callback(); - }); - }, - //get author name - function(callback) - { - authorManager.getAuthorName(author, function(err, value) - { - if(ERR(err, callback)) return; - authorName = value; + authorColorId = value.colorId; + authorName = value.name; callback(); }); }, @@ -965,21 +891,17 @@ function handleClientReady(client, message) { //Check that the client is still here. It might have disconnected between callbacks. if(sessioninfos[client.id] === undefined) - { - callback(); - return; - } + return callback(); //Check if this author is already on the pad, if yes, kick the other sessions! - if(pad2sessions[padIds.padId]) - { - for(var i in pad2sessions[padIds.padId]) - { - if(sessioninfos[pad2sessions[padIds.padId][i]] && sessioninfos[pad2sessions[padIds.padId][i]].author == author) - { - var socket = socketio.sockets.sockets[pad2sessions[padIds.padId][i]]; - if(socket) socket.json.send({disconnect:"userdup"}); - } + var roomClients = socketio.sockets.clients(padIds.padId); + for(var i = 0; i < roomClients.length; i++) { + var sinfo = sessioninfos[roomClients[i].id]; + if(sinfo && sinfo.author == author) { + // fix user's counter, works on page refresh or if user closes browser window and then rejoins + sessioninfos[roomClients[i].id] = {}; + roomClients[i].leave(padIds.padId); + roomClients[i].json.send({disconnect:"userdup"}); } } @@ -988,15 +910,6 @@ function handleClientReady(client, message) sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId; sessioninfos[client.id].readonly = padIds.readonly; - //check if there is already a pad2sessions entry, if not, create one - if(!pad2sessions[padIds.padId]) - { - pad2sessions[padIds.padId] = []; - } - - //Saves in pad2sessions that this session belongs to this pad - pad2sessions[padIds.padId].push(client.id); - //If this is a reconnect, we don't have to send the client the ClientVars again if(message.reconnect == true) { @@ -1044,7 +957,7 @@ function handleClientReady(client, message) // tell the client the number of the latest chat-message, which will be // used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES) "chatHead": pad.chatHead, - "numConnectedUsers": pad2sessions[padIds.padId].length, + "numConnectedUsers": roomClients.length, "isProPad": false, "readOnlyId": padIds.readOnlyPadId, "readonly": padIds.readonly, @@ -1080,6 +993,8 @@ function handleClientReady(client, message) } }); + //Join the pad and start receiving updates + client.join(padIds.padId); //Send the clientVars to the Client client.json.send({type: "CLIENT_VARS", data: clientVars}); //Save the current revision in sessioninfos, should be the same as in clientVars @@ -1108,74 +1023,56 @@ function handleClientReady(client, message) { messageToTheOtherUsers.data.userInfo.name = authorName; } + + // notify all existing users about new user + client.broadcast.to(padIds.padIds).json.send(messageToTheOtherUsers); //Run trough all sessions of this pad - async.forEach(pad2sessions[padIds.padId], function(sessionID, callback) + async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback) { - var author, socket, sessionAuthorName, sessionAuthorColorId; + var author; + + //Jump over, if this session is the connection session + if(roomClient.id == client.id) + return callback(); + //Since sessioninfos might change while being enumerated, check if the //sessionID is still assigned to a valid session - if(sessioninfos[sessionID] !== undefined && - socketio.sockets.sockets[sessionID] !== undefined){ - author = sessioninfos[sessionID].author; - socket = socketio.sockets.sockets[sessionID]; - }else { - // If the sessionID is not valid, callback(); - callback(); - return; - } - async.series([ + if(sessioninfos[roomClient.id] !== undefined) + author = sessioninfos[roomClient.id].author; + else // If the client id is not valid, callback(); + return callback(); + + async.waterfall([ //get the authorname & colorId function(callback) { - async.parallel([ - function(callback) - { - authorManager.getAuthorColorId(author, function(err, value) - { - if(ERR(err, callback)) return; - sessionAuthorColorId = value; - callback(); - }) - }, - function(callback) - { - authorManager.getAuthorName(author, function(err, value) - { - if(ERR(err, callback)) return; - sessionAuthorName = value; - callback(); - }) - } - ],callback); + // reuse previously created cache of author's data + if(historicalAuthorData[author]) + callback(null, historicalAuthorData[author]); + else + authorManager.getAuthor(author, callback); }, - function (callback) + function (authorInfo, callback) { - //Jump over, if this session is the connection session - if(sessionID != client.id) - { - //Send this Session the Notification about the new user - socket.json.send(messageToTheOtherUsers); - - //Send the new User a Notification about this other user - var messageToNotifyTheClientAboutTheOthers = { - "type": "COLLABROOM", - "data": { - type: "USER_NEWINFO", - userInfo: { - "ip": "127.0.0.1", - "colorId": sessionAuthorColorId, - "name": sessionAuthorName, - "userAgent": "Anonymous", - "userId": author - } + //Send the new User a Notification about this other user + var msg = { + "type": "COLLABROOM", + "data": { + type: "USER_NEWINFO", + userInfo: { + "ip": "127.0.0.1", + "colorId": authorInfo.colorId, + "name": authorInfo.name, + "userAgent": "Anonymous", + "userId": author } - }; - client.json.send(messageToNotifyTheClientAboutTheOthers); - } + } + }; + client.json.send(msg); } - ], callback); + ], callback); }, callback); } ],function(err) @@ -1521,33 +1418,30 @@ function composePadChangesets(padId, startNum, endNum, callback) * Get the number of users in a pad */ exports.padUsersCount = function (padID, callback) { - if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) { - callback(null, {padUsersCount: 0}); - } else { - callback(null, {padUsersCount: pad2sessions[padID].length}); - } + callback(null, { + padUsersCount: socketio.sockets.clients(padId).length + }); } /** * Get the list of users in a pad */ exports.padUsers = function (padID, callback) { - if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) { - callback(null, {padUsers: []}); - } else { - var authors = []; - for ( var ix in sessioninfos ) { - if ( sessioninfos[ix].padId !== padID ) { - continue; - } - var aid = sessioninfos[ix].author; - authorManager.getAuthor( aid, function ( err, author ) { - author.id = aid; - authors.push( author ); - if ( authors.length === pad2sessions[padID].length ) { - callback(null, {padUsers: authors}); - } - } ); + var result = []; + + async.forEach(socketio.sockets.clients(padId), function(roomClient, callback) { + var s = sessioninfos[roomClient.id]; + if(s) { + authorManager.getAuthor(s.author, function(err, author) { + if(ERR(err, callback)) return; + + author.id = s.author; + result.push(author); + }); } - } + }, function(err) { + if(ERR(err, callback)) return; + + callback(null, {padUsers: result}); + }); } diff --git a/src/static/css/admin.css b/src/static/css/admin.css index 3a7291516..9ea02744f 100644 --- a/src/static/css/admin.css +++ b/src/static/css/admin.css @@ -42,6 +42,7 @@ div.innerwrapper { border-radius: 0 0 7px 7px; margin-left:250px; min-width:400px; + width:100%; } #wrapper { diff --git a/src/static/js/ace2_common.js b/src/static/js/ace2_common.js index 8a7d16ee3..fb7ce4feb 100644 --- a/src/static/js/ace2_common.js +++ b/src/static/js/ace2_common.js @@ -33,19 +33,6 @@ function object(o) f.prototype = o; return new f(); } -var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase(); - -// Figure out what browser is being used (stolen from jquery 1.2.1) -var browser = { - version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1], - safari: /webkit/.test(userAgent), - opera: /opera/.test(userAgent), - msie: /msie/.test(userAgent) && !/opera/.test(userAgent), - mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent), - windows: /windows/.test(userAgent), - mobile: /mobile/.test(userAgent) || /android/.test(userAgent) -}; - function getAssoc(obj, name) { @@ -97,7 +84,6 @@ var noop = function(){}; exports.isNodeText = isNodeText; exports.object = object; -exports.browser = browser; exports.getAssoc = getAssoc; exports.setAssoc = setAssoc; exports.binarySearch = binarySearch; diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index da8dea85d..6d8c58bbc 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -28,7 +28,7 @@ $ = jQuery = require('./rjquery').$; _ = require("./underscore"); var isNodeText = Ace2Common.isNodeText, - browser = Ace2Common.browser, + browser = $.browser, getAssoc = Ace2Common.getAssoc, setAssoc = Ace2Common.setAssoc, isTextNode = Ace2Common.isTextNode, @@ -154,7 +154,17 @@ function Ace2Inner(){ var dmesg = noop; window.dmesg = noop; - var scheduler = parent; + // Ugly hack for Firefox 18 + // get the timeout and interval methods from the parent iframe + // This hack breaks IE8 + try{ + setTimeout = parent.setTimeout; + clearTimeout = parent.clearTimeout; + setInterval = parent.setInterval; + clearInterval = parent.clearInterval; + }catch(err){ + // IE8 can panic here. + } var textFace = 'monospace'; var textSize = 12; @@ -174,7 +184,7 @@ function Ace2Inner(){ parentDynamicCSS = makeCSSManager("dynamicsyntax", true); } - var changesetTracker = makeChangesetTracker(scheduler, rep.apool, { + var changesetTracker = makeChangesetTracker(rep.apool, { withCallbacks: function(operationName, f) { inCallStackIfNecessary(operationName, function() @@ -594,7 +604,7 @@ function Ace2Inner(){ doesWrap = newVal; var dwClass = "doesWrap"; setClassPresence(root, "doesWrap", doesWrap); - scheduler.setTimeout(function() + setTimeout(function() { inCallStackIfNecessary("setWraps", function() { @@ -634,7 +644,7 @@ function Ace2Inner(){ textFace = face; root.style.fontFamily = textFace; lineMetricsDiv.style.fontFamily = textFace; - scheduler.setTimeout(function() + setTimeout(function() { setUpTrackingCSS(); }, 0); @@ -647,7 +657,7 @@ function Ace2Inner(){ root.style.lineHeight = textLineHeight() + "px"; sideDiv.style.lineHeight = textLineHeight() + "px"; lineMetricsDiv.style.fontSize = textSize + "px"; - scheduler.setTimeout(function() + setTimeout(function() { setUpTrackingCSS(); }, 0); @@ -1085,7 +1095,7 @@ function Ace2Inner(){ { if (scheduledTimeout) { - scheduler.clearTimeout(scheduledTimeout); + clearTimeout(scheduledTimeout); scheduledTimeout = null; } } @@ -1096,7 +1106,7 @@ function Ace2Inner(){ scheduledTime = time; var delay = time - now(); if (delay < 0) delay = 0; - scheduledTimeout = scheduler.setTimeout(callback, delay); + scheduledTimeout = setTimeout(callback, delay); } function callback() @@ -2817,7 +2827,6 @@ function Ace2Inner(){ rep.selStart = selectStart; rep.selEnd = selectEnd; rep.selFocusAtStart = newSelFocusAtStart; - if (mozillaFakeArrows) mozillaFakeArrows.notifySelectionChanged(); currentCallStack.repChanged = true; return true; @@ -3614,7 +3623,7 @@ function Ace2Inner(){ evt.preventDefault(); doReturnKey(); //scrollSelectionIntoView(); - scheduler.setTimeout(function() + setTimeout(function() { outerWin.scrollBy(-100, 0); }, 0); @@ -3690,11 +3699,41 @@ function Ace2Inner(){ doDeleteKey(); specialHandled = true; } + if((evt.which == 33 || evt.which == 34) && type == 'keydown'){ + var oldVisibleLineRange = getVisibleLineRange(); + var topOffset = rep.selStart[0] - oldVisibleLineRange[0]; + if(topOffset < 0 ){ + topOffset = 0; + } - if (mozillaFakeArrows && mozillaFakeArrows.handleKeyEvent(evt)) - { - evt.preventDefault(); - specialHandled = true; + var isPageDown = evt.which === 34; + var isPageUp = evt.which === 33; + + setTimeout(function(){ + var newVisibleLineRange = getVisibleLineRange(); + var linesCount = rep.lines.length(); + + var newCaretRow = rep.selStart[0]; + if(isPageUp){ + newCaretRow = oldVisibleLineRange[0]; + } + + if(isPageDown){ + newCaretRow = newVisibleLineRange[0] + topOffset; + } + + //ensure min and max + if(newCaretRow < 0){ + newCaretRow = 0; + } + if(newCaretRow >= linesCount){ + newCaretRow = linesCount-1; + } + + rep.selStart[0] = newCaretRow; + rep.selEnd[0] = newCaretRow; + updateBrowserSelectionFromRep(); + }, 200); } } @@ -4119,6 +4158,11 @@ function Ace2Inner(){ selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset); selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset); selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset)); + + if(selection.startPoint.node.ownerDocument !== window.document){ + return null; + } + return selection; } else return null; @@ -4722,7 +4766,7 @@ function Ace2Inner(){ }); - scheduler.setTimeout(function() + setTimeout(function() { parent.readyFunc(); // defined in code that sets up the inner iframe }, 0); @@ -5032,331 +5076,6 @@ function Ace2Inner(){ } editorInfo.ace_doInsertUnorderedList = doInsertUnorderedList; editorInfo.ace_doInsertOrderedList = doInsertOrderedList; - - var mozillaFakeArrows = (browser.mozilla && (function() - { - // In Firefox 2, arrow keys are unstable while DOM-manipulating - // operations are going on. Specifically, if an operation - // (computation that ties up the event queue) is going on (in the - // call-stack of some event, like a timeout) that at some point - // mutates nodes involved in the selection, then the arrow - // keypress may (randomly) move the caret to the beginning or end - // of the document. If the operation also mutates the selection - // range, the old selection or the new selection may be used, or - // neither. - // As long as the arrow is pressed during the busy operation, it - // doesn't seem to matter that the keydown and keypress events - // aren't generated until afterwards, or that the arrow movement - // can still be stopped (meaning it hasn't been performed yet); - // Firefox must be preserving some old information about the - // selection or the DOM from when the key was initially pressed. - // However, it also doesn't seem to matter when the key was - // actually pressed relative to the time of the mutation within - // the prolonged operation. Also, even in very controlled tests - // (like a mutation followed by a long period of busyWaiting), the - // problem shows up often but not every time, with no discernable - // pattern. Who knows, it could have something to do with the - // caret-blinking timer, or DOM changes not being applied - // immediately. - // This problem, mercifully, does not show up at all in IE or - // Safari. My solution is to have my own, full-featured arrow-key - // implementation for Firefox. - // Note that the problem addressed here is potentially very subtle, - // especially if the operation is quick and is timed to usually happen - // when the user is idle. - // features: - // - 'up' and 'down' arrows preserve column when passing through shorter lines - // - shift-arrows extend the "focus" point, which may be start or end of range - // - the focus point is kept horizontally and vertically scrolled into view - // - arrows without shift cause caret to move to beginning or end of selection (left,right) - // or move focus point up or down a line (up,down) - // - command-(left,right,up,down) on Mac acts like (line-start, line-end, doc-start, doc-end) - // - takes wrapping into account when doesWrap is true, i.e. up-arrow and down-arrow move - // between the virtual lines within a wrapped line; this was difficult, and unfortunately - // requires mutating the DOM to get the necessary information - var savedFocusColumn = 0; // a value of 0 has no effect - var updatingSelectionNow = false; - - function getVirtualLineView(lineNum) - { - var lineNode = rep.lines.atIndex(lineNum).lineNode; - while (lineNode.firstChild && isBlockElement(lineNode.firstChild)) - { - lineNode = lineNode.firstChild; - } - return makeVirtualLineView(lineNode); - } - - function markerlessLineAndChar(line, chr) - { - return [line, chr - rep.lines.atIndex(line).lineMarker]; - } - - function markerfulLineAndChar(line, chr) - { - return [line, chr + rep.lines.atIndex(line).lineMarker]; - } - - return { - notifySelectionChanged: function() - { - if (!updatingSelectionNow) - { - savedFocusColumn = 0; - } - }, - handleKeyEvent: function(evt) - { - // returns "true" if handled - if (evt.type != "keypress") return false; - var keyCode = evt.keyCode; - if (keyCode < 37 || keyCode > 40) return false; - incorporateUserChanges(); - - if (!(rep.selStart && rep.selEnd)) return true; - - // {byWord,toEnd,normal} - var moveMode = (evt.altKey ? "byWord" : (evt.ctrlKey ? "byWord" : (evt.metaKey ? "toEnd" : "normal"))); - - var anchorCaret = markerlessLineAndChar(rep.selStart[0], rep.selStart[1]); - var focusCaret = markerlessLineAndChar(rep.selEnd[0], rep.selEnd[1]); - var wasCaret = isCaret(); - if (rep.selFocusAtStart) - { - var tmp = anchorCaret; - anchorCaret = focusCaret; - focusCaret = tmp; - } - var K_UP = 38, - K_DOWN = 40, - K_LEFT = 37, - K_RIGHT = 39; - var dontMove = false; - if (wasCaret && !evt.shiftKey) - { - // collapse, will mutate both together - anchorCaret = focusCaret; - } - else if ((!wasCaret) && (!evt.shiftKey)) - { - if (keyCode == K_LEFT) - { - // place caret at beginning - if (rep.selFocusAtStart) anchorCaret = focusCaret; - else focusCaret = anchorCaret; - if (moveMode == "normal") dontMove = true; - } - else if (keyCode == K_RIGHT) - { - // place caret at end - if (rep.selFocusAtStart) focusCaret = anchorCaret; - else anchorCaret = focusCaret; - if (moveMode == "normal") dontMove = true; - } - else - { - // collapse, will mutate both together - anchorCaret = focusCaret; - } - } - if (!dontMove) - { - function lineLength(i) - { - var entry = rep.lines.atIndex(i); - return entry.text.length - entry.lineMarker; - } - - function lineText(i) - { - var entry = rep.lines.atIndex(i); - return entry.text.substring(entry.lineMarker); - } - - if (keyCode == K_UP || keyCode == K_DOWN) - { - var up = (keyCode == K_UP); - var canChangeLines = ((up && focusCaret[0]) || ((!up) && focusCaret[0] < rep.lines.length() - 1)); - var virtualLineView, virtualLineSpot, canChangeVirtualLines = false; - if (doesWrap) - { - virtualLineView = getVirtualLineView(focusCaret[0]); - virtualLineSpot = virtualLineView.getVLineAndOffsetForChar(focusCaret[1]); - canChangeVirtualLines = ((up && virtualLineSpot.vline > 0) || ((!up) && virtualLineSpot.vline < ( - virtualLineView.getNumVirtualLines() - 1))); - } - var newColByVirtualLineChange; - if (moveMode == "toEnd") - { - if (up) - { - focusCaret[0] = 0; - focusCaret[1] = 0; - } - else - { - focusCaret[0] = rep.lines.length() - 1; - focusCaret[1] = lineLength(focusCaret[0]); - } - } - else if (moveMode == "byWord") - { - // move by "paragraph", a feature that Firefox lacks but IE and Safari both have - if (up) - { - if (focusCaret[1] === 0 && canChangeLines) - { - focusCaret[0]--; - focusCaret[1] = 0; - } - else focusCaret[1] = 0; - } - else - { - var lineLen = lineLength(focusCaret[0]); - if (browser.windows) - { - if (canChangeLines) - { - focusCaret[0]++; - focusCaret[1] = 0; - } - else - { - focusCaret[1] = lineLen; - } - } - else - { - if (focusCaret[1] == lineLen && canChangeLines) - { - focusCaret[0]++; - focusCaret[1] = lineLength(focusCaret[0]); - } - else - { - focusCaret[1] = lineLen; - } - } - } - savedFocusColumn = 0; - } - else if (canChangeVirtualLines) - { - var vline = virtualLineSpot.vline; - var offset = virtualLineSpot.offset; - if (up) vline--; - else vline++; - if (savedFocusColumn > offset) offset = savedFocusColumn; - else - { - savedFocusColumn = offset; - } - var newSpot = virtualLineView.getCharForVLineAndOffset(vline, offset); - focusCaret[1] = newSpot.lineChar; - } - else if (canChangeLines) - { - if (up) focusCaret[0]--; - else focusCaret[0]++; - var offset = focusCaret[1]; - if (doesWrap) - { - offset = virtualLineSpot.offset; - } - if (savedFocusColumn > offset) offset = savedFocusColumn; - else - { - savedFocusColumn = offset; - } - if (doesWrap) - { - var newLineView = getVirtualLineView(focusCaret[0]); - var vline = (up ? newLineView.getNumVirtualLines() - 1 : 0); - var newSpot = newLineView.getCharForVLineAndOffset(vline, offset); - focusCaret[1] = newSpot.lineChar; - } - else - { - var lineLen = lineLength(focusCaret[0]); - if (offset > lineLen) offset = lineLen; - focusCaret[1] = offset; - } - } - else - { - if (up) focusCaret[1] = 0; - else focusCaret[1] = lineLength(focusCaret[0]); - savedFocusColumn = 0; - } - } - else if (keyCode == K_LEFT || keyCode == K_RIGHT) - { - var left = (keyCode == K_LEFT); - if (left) - { - if (moveMode == "toEnd") focusCaret[1] = 0; - else if (focusCaret[1] > 0) - { - if (moveMode == "byWord") - { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false); - } - else - { - focusCaret[1]--; - } - } - else if (focusCaret[0] > 0) - { - focusCaret[0]--; - focusCaret[1] = lineLength(focusCaret[0]); - if (moveMode == "byWord") - { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false); - } - } - } - else - { - var lineLen = lineLength(focusCaret[0]); - if (moveMode == "toEnd") focusCaret[1] = lineLen; - else if (focusCaret[1] < lineLen) - { - if (moveMode == "byWord") - { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true); - } - else - { - focusCaret[1]++; - } - } - else if (focusCaret[0] < rep.lines.length() - 1) - { - focusCaret[0]++; - focusCaret[1] = 0; - if (moveMode == "byWord") - { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true); - } - } - } - savedFocusColumn = 0; - } - } - - var newSelFocusAtStart = ((focusCaret[0] < anchorCaret[0]) || (focusCaret[0] == anchorCaret[0] && focusCaret[1] < anchorCaret[1])); - var newSelStart = (newSelFocusAtStart ? focusCaret : anchorCaret); - var newSelEnd = (newSelFocusAtStart ? anchorCaret : focusCaret); - updatingSelectionNow = true; - performSelectionChange(markerfulLineAndChar(newSelStart[0], newSelStart[1]), markerfulLineAndChar(newSelEnd[0], newSelEnd[1]), newSelFocusAtStart); - updatingSelectionNow = false; - currentCallStack.userChangedSelection = true; - return true; - } - }; - })()); var lineNumbersShown; var sideDivInner; @@ -5495,7 +5214,7 @@ function Ace2Inner(){ documentAttributeManager: documentAttributeManager }); - scheduler.setTimeout(function() + setTimeout(function() { parent.readyFunc(); // defined in code that sets up the inner iframe }, 0); diff --git a/src/static/js/changesettracker.js b/src/static/js/changesettracker.js index 58ef21cb5..91e854d65 100644 --- a/src/static/js/changesettracker.js +++ b/src/static/js/changesettracker.js @@ -23,7 +23,7 @@ var AttributePool = require('./AttributePool'); var Changeset = require('./Changeset'); -function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) +function makeChangesetTracker(apool, aceCallbacksProvider) { // latest official text from server @@ -51,7 +51,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) // and if there isn't a timeout already scheduled. if (changeCallback && changeCallbackTimeout === null) { - changeCallbackTimeout = scheduler.setTimeout(function() + changeCallbackTimeout = setTimeout(function() { try { diff --git a/src/static/js/domline.js b/src/static/js/domline.js index 1be0f4eee..43b5f21a3 100644 --- a/src/static/js/domline.js +++ b/src/static/js/domline.js @@ -30,8 +30,7 @@ var Security = require('./security'); var hooks = require('./pluginfw/hooks'); var _ = require('./underscore'); var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker; -var Ace2Common = require('./ace2_common'); -var noop = Ace2Common.noop; +var noop = function(){}; var domline = {}; diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index d0d14814b..e1c025c43 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -23,27 +23,27 @@ window.html10n = (function(window, document, undefined) { // fix console - var console = window.console + var console = window.console; function interceptConsole(method){ - if (!console) return function() {} + if (!console) return function() {}; - var original = console[method] + var original = console[method]; // do sneaky stuff if (original.bind){ // Do this for normal browsers - return original.bind(console) + return original.bind(console); }else{ return function() { // Do this for IE - var message = Array.prototype.slice.apply(arguments).join(' ') - original(message) + var message = Array.prototype.slice.apply(arguments).join(' '); + original(message); } } } var consoleLog = interceptConsole('log') , consoleWarn = interceptConsole('warn') - , consoleError = interceptConsole('warn') + , consoleError = interceptConsole('warn'); // fix Array.prototype.instanceOf in, guess what, IE! <3 @@ -84,14 +84,14 @@ window.html10n = (function(window, document, undefined) { * MicroEvent - to make any js object an event emitter (server or browser) */ - var MicroEvent = function(){} + var MicroEvent = function(){} MicroEvent.prototype = { - bind : function(event, fct){ + bind : function(event, fct){ this._events = this._events || {}; this._events[event] = this._events[event] || []; this._events[event].push(fct); }, - unbind : function(event, fct){ + unbind : function(event, fct){ this._events = this._events || {}; if( event in this._events === false ) return; this._events[event].splice(this._events[event].indexOf(fct), 1); @@ -100,7 +100,7 @@ window.html10n = (function(window, document, undefined) { this._events = this._events || {}; if( event in this._events === false ) return; for(var i = 0; i < this._events[event].length; i++){ - this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)) + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); } } }; @@ -122,50 +122,50 @@ window.html10n = (function(window, document, undefined) { * and caching all necessary resources */ function Loader(resources) { - this.resources = resources - this.cache = {} // file => contents - this.langs = {} // lang => strings + this.resources = resources; + this.cache = {}; // file => contents + this.langs = {}; // lang => strings } Loader.prototype.load = function(lang, cb) { - if(this.langs[lang]) return cb() + if(this.langs[lang]) return cb(); if (this.resources.length > 0) { var reqs = 0; for (var i=0, n=this.resources.length; i < n; i++) { this.fetch(this.resources[i], lang, function(e) { reqs++; - if(e) return setTimeout(function(){ throw e }, 0) + if(e) return setTimeout(function(){ throw e }, 0); if (reqs < n) return;// Call back once all reqs are completed - cb && cb() + cb && cb(); }) } } } Loader.prototype.fetch = function(href, lang, cb) { - var that = this + var that = this; if (this.cache[href]) { this.parse(lang, href, this.cache[href], cb) return; } - var xhr = new XMLHttpRequest() - xhr.open('GET', href, /*async: */true) + var xhr = new XMLHttpRequest(); + xhr.open('GET', href, /*async: */true); if (xhr.overrideMimeType) { xhr.overrideMimeType('application/json; charset=utf-8'); } xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200 || xhr.status === 0) { - var data = JSON.parse(xhr.responseText) - that.cache[href] = data + var data = JSON.parse(xhr.responseText); + that.cache[href] = data; // Pass on the contents for parsing - that.parse(lang, href, data, cb) + that.parse(lang, href, data, cb); } else { - cb(new Error('Failed to load '+href)) + cb(new Error('Failed to load '+href)); } } }; @@ -174,39 +174,39 @@ window.html10n = (function(window, document, undefined) { Loader.prototype.parse = function(lang, currHref, data, cb) { if ('object' != typeof data) { - cb(new Error('A file couldn\'t be parsed as json.')) - return + cb(new Error('A file couldn\'t be parsed as json.')); + return; } - if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-')) + if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-')); if (!data[lang]) { - cb(new Error('Couldn\'t find translations for '+lang)) - return + cb(new Error('Couldn\'t find translations for '+lang)); + return; } if ('string' == typeof data[lang]) { // Import rule // absolute path - var importUrl = data[lang] + var importUrl = data[lang]; // relative path if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) { - importUrl = currHref+"/../"+data[lang] + importUrl = currHref+"/../"+data[lang]; } - this.fetch(importUrl, lang, cb) - return + this.fetch(importUrl, lang, cb); + return; } if ('object' != typeof data[lang]) { - cb(new Error('Translations should be specified as JSON objects!')) - return + cb(new Error('Translations should be specified as JSON objects!')); + return; } - this.langs[lang] = data[lang] + this.langs[lang] = data[lang]; // TODO: Also store accompanying langs - cb() + cb(); } @@ -216,11 +216,11 @@ window.html10n = (function(window, document, undefined) { var html10n = { language : null } - MicroEvent.mixin(html10n) + MicroEvent.mixin(html10n); - html10n.macros = {} + html10n.macros = {}; - html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"] + html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]; /** * Get rules for plural forms (shared with JetPack), see: @@ -664,14 +664,14 @@ window.html10n = (function(window, document, undefined) { * @param langs An array of lang codes defining fallbacks */ html10n.localize = function(langs) { - var that = this + var that = this; // if only one string => create an array - if ('string' == typeof langs) langs = [langs] + if ('string' == typeof langs) langs = [langs]; this.build(langs, function(er, translations) { - html10n.translations = translations - html10n.translateElement(translations) - that.trigger('localized') + html10n.translations = translations; + html10n.translateElement(translations); + that.trigger('localized'); }) } @@ -682,78 +682,78 @@ window.html10n = (function(window, document, undefined) { * @param element A DOM element, if omitted, the document element will be used */ html10n.translateElement = function(translations, element) { - element = element || document.documentElement + element = element || document.documentElement; var children = element? getTranslatableChildren(element) : document.childNodes; for (var i=0, n=children.length; i < n; i++) { - this.translateNode(translations, children[i]) + this.translateNode(translations, children[i]); } // translate element itself if necessary - this.translateNode(translations, element) + this.translateNode(translations, element); } function asyncForEach(list, iterator, cb) { var i = 0 - , n = list.length + , n = list.length; iterator(list[i], i, function each(err) { - if(err) consoleLog(err) - i++ + if(err) consoleLog(err); + i++; if (i < n) return iterator(list[i],i, each); - cb() + cb(); }) } function getTranslatableChildren(element) { if(!document.querySelectorAll) { - if (!element) return [] + if (!element) return []; var nodes = element.getElementsByTagName('*') - , l10nElements = [] + , l10nElements = []; for (var i=0, n=nodes.length; i < n; i++) { if (nodes[i].getAttribute('data-l10n-id')) l10nElements.push(nodes[i]); } - return l10nElements + return l10nElements; } - return element.querySelectorAll('*[data-l10n-id]') + return element.querySelectorAll('*[data-l10n-id]'); } html10n.get = function(id, args) { - var translations = html10n.translations - if(!translations) return consoleWarn('No translations available (yet)') - if(!translations[id]) return consoleWarn('Could not find string '+id) + var translations = html10n.translations; + if(!translations) return consoleWarn('No translations available (yet)'); + if(!translations[id]) return consoleWarn('Could not find string '+id); // apply args - var str = substArguments(translations[id], args) + var str = substArguments(translations[id], args); // apply macros - return substMacros(id, str, args) + return substMacros(id, str, args); // replace {{arguments}} with their values or the // associated translation string (based on its key) function substArguments(str, args) { var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/ - , match + , match; while (match = reArgs.exec(str)) { if (!match || match.length < 2) - return str // argument key not found + return str; // argument key not found var arg = match[1] - , sub = '' + , sub = ''; if (arg in args) { - sub = args[arg] + sub = args[arg]; } else if (arg in translations) { - sub = translations[arg] + sub = translations[arg]; } else { - consoleWarn('Could not find argument {{' + arg + '}}') - return str + consoleWarn('Could not find argument {{' + arg + '}}'); + return str; } - str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length) + str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length); } - return str + return str; } // replace {[macros]} with their values @@ -766,21 +766,21 @@ window.html10n = (function(window, document, undefined) { // a macro has been found // Note: at the moment, only one parameter is supported var macroName = reMatch[1] - , paramName = reMatch[2] + , paramName = reMatch[2]; - if (!(macroName in gMacros)) return str + if (!(macroName in gMacros)) return str; - var param + var param; if (args && paramName in args) { - param = args[paramName] + param = args[paramName]; } else if (paramName in translations) { - param = translations[paramName] + param = translations[paramName]; } // there's no macro parser yet: it has to be defined in gMacros - var macro = html10n.macros[macroName] - str = macro(translations, key, str, param) - return str + var macro = html10n.macros[macroName]; + str = macro(translations, key, str, param); + return str; } } @@ -788,26 +788,26 @@ window.html10n = (function(window, document, undefined) { * Applies translations to a DOM node (recursive) */ html10n.translateNode = function(translations, node) { - var str = {} + var str = {}; // get id - str.id = node.getAttribute('data-l10n-id') - if (!str.id) return + str.id = node.getAttribute('data-l10n-id'); + if (!str.id) return; - if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id) + if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id); // get args if(window.JSON) { - str.args = JSON.parse(node.getAttribute('data-l10n-args')) + str.args = JSON.parse(node.getAttribute('data-l10n-args')); }else{ try{ - str.args = eval(node.getAttribute('data-l10n-args')) + str.args = eval(node.getAttribute('data-l10n-args')); }catch(e) { - consoleWarn('Couldn\'t parse args for '+str.id) + consoleWarn('Couldn\'t parse args for '+str.id); } } - str.str = html10n.get(str.id, str.args) + str.str = html10n.get(str.id, str.args); // get attribute name to apply str to var prop @@ -817,31 +817,31 @@ window.html10n = (function(window, document, undefined) { , "innerHTML": 1 , "alt": 1 , "textContent": 1 - } + }; if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified - prop = str.id.substr(index + 1) + prop = str.id.substr(index + 1); } else { // no attribute: assuming text content by default - prop = document.body.textContent ? 'textContent' : 'innerText' + prop = document.body.textContent ? 'textContent' : 'innerText'; } // Apply translation if (node.children.length === 0 || prop != 'textContent') { - node[prop] = str.str + node[prop] = str.str; } else { var children = node.childNodes, - found = false + found = false; for (var i=0, n=children.length; i < n; i++) { if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) { if (!found) { - children[i].nodeValue = str.str - found = true + children[i].nodeValue = str.str; + found = true; } else { - children[i].nodeValue = '' + children[i].nodeValue = ''; } } } if (!found) { - consoleWarn('Unexpected error: could not translate element content for key '+str.id, node) + consoleWarn('Unexpected error: could not translate element content for key '+str.id, node); } } } @@ -852,32 +852,32 @@ window.html10n = (function(window, document, undefined) { */ html10n.build = function(langs, cb) { var that = this - , build = {} + , build = {}; asyncForEach(langs, function (lang, i, next) { if(!lang) return next(); - that.loader.load(lang, next) + that.loader.load(lang, next); }, function() { - var lang - langs.reverse() + var lang; + langs.reverse(); // loop through priority array... for (var i=0, n=langs.length; i < n; i++) { - lang = langs[i] + lang = langs[i]; if(!lang || !(lang in that.loader.langs)) continue; // ... and apply all strings of the current lang in the list // to our build object for (var string in that.loader.langs[lang]) { - build[string] = that.loader.langs[lang][string] + build[string] = that.loader.langs[lang][string]; } // the last applied lang will be exposed as the // lang the page was translated to - that.language = lang + that.language = lang; } - cb(null, build) + cb(null, build); }) } @@ -893,8 +893,8 @@ window.html10n = (function(window, document, undefined) { * Returns the direction of the language returned be html10n#getLanguage */ html10n.getDirection = function() { - var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-')) - return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl' + var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-')); + return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'; } /** @@ -903,28 +903,28 @@ window.html10n = (function(window, document, undefined) { html10n.index = function () { // Find all s var links = document.getElementsByTagName('link') - , resources = [] + , resources = []; for (var i=0, n=links.length; i < n; i++) { if (links[i].type != 'application/l10n+json') continue; - resources.push(links[i].href) + resources.push(links[i].href); } - this.loader = new Loader(resources) - this.trigger('indexed') + this.loader = new Loader(resources); + this.trigger('indexed'); } if (document.addEventListener) // modern browsers and IE9+ document.addEventListener('DOMContentLoaded', function() { - html10n.index() - }, false) + html10n.index(); + }, false); else if (window.attachEvent) window.attachEvent('onload', function() { - html10n.index() - }, false) + html10n.index(); + }, false); // gettext-like shortcut if (window._ === undefined) window._ = html10n.get; - return html10n -})(window, document) \ No newline at end of file + return html10n; +})(window, document); diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index eb10f8afd..15d879409 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -94,11 +94,12 @@ exports.search = function(query, cache, cb) { if (er) return cb(er); var res = {}; var i = 0; + var pattern = query.pattern.toLowerCase(); for (key in data) { // for every plugin in the data from npm if ( key.indexOf(plugins.prefix) == 0 - && key.indexOf(query.pattern) != -1 + && key.indexOf(pattern) != -1 || key.indexOf(plugins.prefix) == 0 - && data[key].description.indexOf(query.pattern) != -1 + && data[key].description.indexOf(pattern) != -1 ) { // If the name contains ep_ and the search string is in the name or description i++; if (i > query.offset diff --git a/src/templates/admin/index.html b/src/templates/admin/index.html index 16ea84279..0e5c16edd 100644 --- a/src/templates/admin/index.html +++ b/src/templates/admin/index.html @@ -9,12 +9,15 @@