diff --git a/LICENSE b/LICENSE
index a35c25535..8ff473088 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,3 @@
-
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -187,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2012 THE ETHERPAD FOUNDATION
+ Copyright 2012-2013 THE ETHERPAD FOUNDATION
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/app.js b/app.js
new file mode 100755
index 000000000..8c8bf5753
--- /dev/null
+++ b/app.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+var server = require("ep_etherpad-lite/node/server.js");
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..398ba8b19
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "etherpad-lite",
+ "version": "0.0.1",
+ "engines": {
+ "node" : ">=0.6.0",
+ "npm" : ">=1.0"
+ }
+}
diff --git a/src/ep.json b/src/ep.json
index 89c8964aa..3a1246232 100644
--- a/src/ep.json
+++ b/src/ep.json
@@ -23,6 +23,9 @@
{ "name": "adminsettings", "hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings:expressCreateServer",
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings:socketio" }
+ },
+ { "name": "teampad", "hooks": {
+ "expressCreateServer": "ep_etherpad-lite/node/hooks/express/teampad:expressCreateServer" }
}
]
}
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/db/Pad.js b/src/node/db/Pad.js
index 4701e82a3..f89efaf75 100644
--- a/src/node/db/Pad.js
+++ b/src/node/db/Pad.js
@@ -38,6 +38,7 @@ var Pad = function Pad(id) {
this.head = -1;
this.chatHead = -1;
this.publicStatus = false;
+ this.teamStatus = false;
this.passwordHash = null;
this.id = id;
this.savedRevisions = [];
@@ -551,6 +552,15 @@ Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions;
};
+Pad.prototype.getTeamStatus = function getTeamStatus() {
+ return this.teamStatus;
+};
+
+Pad.prototype.setTeamStatus = function setTeamStatus(teamStatus) {
+ this.teamStatus = teamStatus;
+ this.saveToDatabase();
+};
+
/* Crypto helper methods */
function hash(password, salt)
diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js
index 5e0af4643..2bd3d99ee 100644
--- a/src/node/db/PadManager.js
+++ b/src/node/db/PadManager.js
@@ -236,3 +236,23 @@ exports.unloadPad = function(padId)
if(globalPads.get(padId))
globalPads.remove(padId);
}
+
+//checks if a pad is a "team pad"
+exports.isTeamPad = function(padId)
+{
+ var isTeamPad = false;
+ db.get("pad:"+padId, function(err, value)
+ {
+ if(ERR(err)) return;
+
+ if(value != null && value.atext && value.teamStatus){
+ isTeamPad = true;
+ }
+ else
+ {
+ isTeamPad = false;
+ }
+ });
+
+ return isTeamPad;
+}
diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js
index 4289e39ca..c96f9ac30 100644
--- a/src/node/db/SecurityManager.js
+++ b/src/node/db/SecurityManager.js
@@ -49,6 +49,27 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
callback(null, {accessStatus: "deny"});
return;
}
+ }
+ else if(padManager.isTeamPad(padID))
+ {
+ authorManager.getAuthor4Token(token, function(err, author)
+ {
+ if(ERR(err, callback)) return;
+
+ // TODO check session
+ console.log('sessionCookie: ' + sessionCookie);
+ sessionManager.getSessionInfo(sessionCookie, function(err, result) {
+ if (err) {
+ statusObject = {accessStatus: "denyTeamPad", authorID: author};
+ callback(null, statusObject);
+ } else {
+ // TODO figure out how to force authorID to match account name...
+ statusObject = {accessStatus: "grant", authorID: author};
+ callback(null, statusObject);
+ }
+ });
+ });
+
}
// a session is not required, so we'll check if it's a public pad
else
diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js
index 5ce4f7487..b8eb0de4c 100644
--- a/src/node/db/SessionManager.js
+++ b/src/node/db/SessionManager.js
@@ -360,6 +360,66 @@ function listSessionsWithDBKey (dbkey, callback)
});
}
+/**
+ * Creates a new session based on an externally-verified account (e.g. persona)
+ */
+exports.createVerifiedSession = function(sessionID, account, validUntil, callback)
+{
+ async.series([
+
+ //check validUntil and create the session db entry
+ function(callback)
+ {
+ //check if rev is a number
+ if(typeof validUntil != "number")
+ {
+ //try to parse the number
+ if(!isNaN(parseInt(validUntil)))
+ {
+ validUntil = parseInt(validUntil);
+ }
+ else
+ {
+ callback(new customError("validUntil is not a number","apierror"));
+ return;
+ }
+ }
+
+ //ensure this is not a negativ number
+ if(validUntil < 0)
+ {
+ callback(new customError("validUntil is a negativ number","apierror"));
+ return;
+ }
+
+ //ensure this is not a float value
+ if(!is_int(validUntil))
+ {
+ callback(new customError("validUntil is a float value","apierror"));
+ return;
+ }
+
+ //check if validUntil is in the future
+ if(Math.floor(new Date().getTime()/1000) > validUntil)
+ {
+ callback(new customError("validUntil is in the past","apierror"));
+ return;
+ }
+
+ //set the session into the database
+ db.set("session:" + sessionID, {"account": account, "validUntil": validUntil});
+
+ callback();
+ }
+ ], function(err)
+ {
+ if(ERR(err, callback)) return;
+
+ //return error and sessionID
+ callback(null, {sessionID: sessionID});
+ })
+}
+
//checks if a number is an int
function is_int(value)
{
diff --git a/src/node/db/TeamManager.js b/src/node/db/TeamManager.js
new file mode 100644
index 000000000..47798739b
--- /dev/null
+++ b/src/node/db/TeamManager.js
@@ -0,0 +1,211 @@
+/**
+ * The Team Manager provides functions to manage teams in the database
+ */
+
+/*
+ * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
+ *
+ * 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.
+ */
+
+
+var ERR = require("async-stacktrace");
+var customError = require("../utils/customError");
+var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
+var db = require("./DB").db;
+var async = require("async");
+var padManager = require("./PadManager");
+var sessionManager = require("./SessionManager");
+
+exports.listAllTeams = function(callback) {
+ db.get("teams", function (err, teams) {
+ if(ERR(err, callback)) return;
+
+ // there are no teams
+ if(teams == null) {
+ callback(null, {teamIDs: []});
+ return;
+ }
+
+ var teamIDs = [];
+ for ( var teamID in teams) {
+ teamIDs.push(teamID);
+ }
+ callback(null, {teamIDs: teamIDs});
+ });
+}
+
+exports.doesTeamExist = function(teamID, callback)
+{
+ //try to get the team entry
+ db.get("team:" + teamID, function (err, team)
+ {
+ if(ERR(err, callback)) return;
+ callback(null, team != null);
+ });
+}
+
+exports.createTeam = function(teamName, pads, accounts, admins, callback)
+{
+ //search for non existing teamID
+ var teamID = "t." + randomString(16);
+
+ //create the team
+ db.set("team:" + teamID, {name: teamName, pads: pads, accounts: accounts,
+ admins: admins});
+
+ //list the team
+ exports.listAllTeams(function(err, teams) {
+ if(ERR(err, callback)) return;
+ teams = teams? teams.teamIDs : [];
+
+ teams.push(teamID);
+
+ // regenerate team list
+ var newTeams = {};
+ async.forEach(teams, function(team, cb) {
+ newTeams[team] = 1;
+ cb();
+ },function() {
+ db.set("teams", newTeams);
+ callback(null, {teamID: teamID});
+ });
+ });
+}
+
+exports.createTeamPad = function(teamName, teamID, padName, text, callback)
+{
+ //create the padID
+ var padID = teamName + "+" + padName;
+
+ async.series([
+ //ensure team exists
+ function (callback)
+ {
+ exports.doesTeamExist(teamID, function(err, exists)
+ {
+ if(ERR(err, callback)) return;
+
+ //team does not exist
+ if(exists == false)
+ {
+ callback(new customError("teamID does not exist","apierror"));
+ }
+ //team exists, everything is fine
+ else
+ {
+ callback();
+ }
+ });
+ },
+ //ensure pad does not exists
+ function (callback)
+ {
+ padManager.doesPadExists(padID, function(err, exists)
+ {
+ if(ERR(err, callback)) return;
+
+ //pad exists already
+ if(exists == true)
+ {
+ callback(new customError("padName does already exist","apierror"));
+ }
+ //pad does not exist, everything is fine
+ else
+ {
+ callback();
+ }
+ });
+ },
+ //create the pad
+ function (callback)
+ {
+ padManager.getPad(padID, text, function(err, pad)
+ {
+ if(ERR(err, callback)) return;
+
+ pad.setTeamStatus(true);
+
+ callback();
+ });
+ },
+ //add to DB
+ function (callback)
+ {
+ db.get("team:" + teamID, function(err, result)
+ {
+ if(ERR(err, callback)) return;
+
+ result.pads.push(padID);
+ db.set('team:' + teamID, result);
+ });
+ callback();
+ }
+ ], function(err)
+ {
+ if(ERR(err, callback)) return;
+ callback(null, {padID: padID});
+ });
+}
+
+exports.listInfo = function(teamID, callback)
+{
+ exports.doesTeamExist(teamID, function(err, exists)
+ {
+ if(ERR(err, callback)) return;
+
+ //team does not exist
+ if(exists == false)
+ {
+ callback(new customError("teamID does not exist","apierror"));
+ }
+ //team exists, let's get the info
+ else
+ {
+ db.get("team:" + teamID, function(err, result)
+ {
+ if(ERR(err, callback)) return;
+
+ callback(null, result);
+ });
+ }
+ });
+}
+
+exports.addAccountToTeam = function(teamID, account, callback)
+{
+ exports.doesTeamExist(teamID, function(err, exists)
+ {
+ if(ERR(err, callback)) return;
+
+ //team does not exist
+ if(exists == false)
+ {
+ console.log('debug1: ' + teamID);
+ callback(new customError("teamID does not exist","apierror"));
+ }
+ //team exists, let's get the info
+ else
+ {
+ db.get("team:" + teamID, function(err, result)
+ {
+ if(ERR(err, callback)) return;
+
+ result.accounts.push(account);
+ console.log('setting team to: ' + result);
+ db.set("team:" + teamID, result);
+ callback(null, result);
+ });
+ }
+ });
+}
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js
index 6781cd884..25e439585 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -238,7 +238,14 @@ exports.handleMessage = function(client, message)
// our "sessions" "connections".
// FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly
- securityManager.checkAccess(message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
+ // FIXME: always use httponly session cookies
+ var sessionID = null;
+ if (padManager.isTeamPad(message.padID)) {
+ sessionID = message.sessionid;
+ } else {
+ sessionID = client.handshake.sessionID;
+ }
+ securityManager.checkAccess(message.padId, sessionID, message.token, message.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
@@ -874,7 +881,14 @@ function handleClientReady(client, message)
// our "sessions" "connections".
// FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly
- securityManager.checkAccess (padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject)
+ // FIXME: always use httponly session cookies
+ var sessionID = null;
+ if (padManager.isTeamPad(message.padID)) {
+ sessionID = message.sessionid;
+ } else {
+ sessionID = client.handshake.sessionID;
+ }
+ securityManager.checkAccess (padIds.padId, sessionID, message.token, message.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.js
index f3b82b8c7..159e8cee1 100644
--- a/src/node/handler/SocketIORouter.js
+++ b/src/node/handler/SocketIORouter.js
@@ -23,6 +23,7 @@ var ERR = require("async-stacktrace");
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var securityManager = require("../db/SecurityManager");
+var padManager = require("../db/PadManager");
/**
* Saves all components
@@ -108,7 +109,14 @@ exports.setSocketIO = function(_socket)
//this message has everything to try an authorization
if(message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined)
{
- securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
+ // FIXME: always use httponly session cookies
+ var sessionID = null;
+ if (padManager.isTeamPad(message.padID)) {
+ sessionID = message.sessionid;
+ } else {
+ sessionID = client.handshake.sessionID;
+ }
+ securityManager.checkAccess (message.padId, sessionID, message.token, message.password, function(err, statusObject)
{
ERR(err);
diff --git a/src/node/hooks/express/teampad.js b/src/node/hooks/express/teampad.js
new file mode 100644
index 000000000..9598d1fed
--- /dev/null
+++ b/src/node/hooks/express/teampad.js
@@ -0,0 +1,276 @@
+var express = require('express'),
+ async = require('async'),
+ eejs = require('ep_etherpad-lite/node/eejs'),
+ teamManager = require('ep_etherpad-lite/node/db/TeamManager'),
+ sessionManager = require('ep_etherpad-lite/node/db/SessionManager'),
+ padManager = require('ep_etherpad-lite/node/db/PadManager'),
+ https = require('https');
+
+exports.expressCreateServer = function (hook_name, args, cb) {
+ args.app.use(express.bodyParser());
+
+ // TODO use more generic, pluggable auth, hardcoded to persona for now
+ args.app.post('/teampad/verify', function(req, res) {
+ console.log('sign in attempt');
+ var body = JSON.stringify({
+ assertion: req.param('assertion', null),
+ audience: 'http://' + req.headers.host
+ });
+
+ var vreq = https.request({
+ host: 'persona.org',
+ path: '/verify',
+ method: 'POST',
+ headers: {
+ 'Content-Length': body.length,
+ 'Content-Type': 'application/json'
+ }
+ }, function(vres) {
+ var body = '';
+ vres.on('data', function(chunk) { body += chunk; });
+ vres.on('end', function() {
+ try {
+ account = JSON.parse(body).email;
+ validUntil = JSON.parse(body).expires;
+ console.log(body);
+ var sessionID = req.signedCookies.express_sid;
+ sessionManager.createVerifiedSession(
+ sessionID, account, validUntil, function(err, result) {
+ if (err) {
+ console.log(err);
+ return;
+ }
+ });
+ console.log(account + ' logged in');
+ } catch(e) {
+ console.log(e);
+ }
+ });
+ res.redirect('/teampad');
+ });
+ vreq.write(body);
+ vreq.end();
+ });
+
+ args.app.post('/teampad/createteam', function(req, res) {
+ var sessionID = req.signedCookies.express_sid,
+ currentUser = null,
+ signedIn = false,
+ teamName = null,
+ rawTeamName = req.param('teamname', null);
+
+ async.waterfall([
+ function(callback) {
+ sessionManager.getSessionInfo(sessionID, callback);
+ },
+ function(result, callback) {
+ currentUser = result.account;
+ signedIn = true;
+ callback();
+ },
+ function(callback) {
+ console.log('about to sanitize ' + rawTeamName);
+ padManager.sanitizePadId(rawTeamName, function(teamName) {
+ callback(null, teamName);
+ })
+ },
+ function(result, callback) {
+ teamName = result;
+ console.log('sanitized ' + teamName);
+ teamManager.createTeam(teamName, [], [currentUser], [currentUser],
+ callback);
+ },
+ function(teamID, callback) {
+ console.log(teamID + ' created for ' + teamName);
+ res.redirect('/teampad');
+ }
+ ], function(err) {
+ console.log('error: ' + err);
+ res.redirect('/teampad');
+ });
+ });
+
+ args.app.post('/teampad/createpad', function(req, res) {
+ var sessionID = req.signedCookies.express_sid;
+
+ var teamName = null,
+ padName = null,
+ currentUser = null,
+ signedIn = false,
+ teamID = req.param('teamID', null),
+ rawTeamName = req.param('teamname', null),
+ rawPadName = req.param('padname', null);
+
+ async.waterfall([
+ function(callback) {
+ sessionManager.getSessionInfo(sessionID, callback);
+ },
+ function(result, callback) {
+ currentUser = result.account;
+ signedIn = true;
+ padManager.sanitizePadId(rawTeamName, function(teamName) {
+ callback(null, teamName);
+ });
+ },
+ function(result, callback) {
+ teamName = result;
+ padManager.sanitizePadId(rawPadName, function(padName) {
+ callback(null, padName);
+ });
+ },
+ function(result, callback) {
+ padName = result;
+ teamManager.createTeamPad(teamName, teamID, padName, 'super sekrit!',
+ callback);
+ },
+ function(callback) {
+ console.log(padName + ' created for ' + teamName);
+ res.redirect('/teampad/' + teamName);
+ }
+ ], function(err) {
+ console.log(err);
+ res.redirect('/teampad');
+ });
+ });
+
+ args.app.post('/teampad/addaccount', function(req, res) {
+ var sessionID = req.signedCookies.express_sid,
+ currentUser = null,
+ signedIn = false,
+ teamName = null,
+ teamID = req.param('teamID', null),
+ rawTeamName = req.param('teamname', null),
+ account = req.param('accountname', null);
+
+ async.waterfall([
+ function(callback) {
+ sessionManager.getSessionInfo(sessionID, callback);
+ },
+ function(result, callback) {
+ currentUser = result.account;
+ padManager.sanitizePadId(rawTeamName, function(teamName) {
+ callback(null, teamName);
+ });
+ },
+ function(result, callback) {
+ teamName = result;
+ console.log('teamID: ' + teamID);
+ teamManager.addAccountToTeam(teamID, account, callback);
+ },
+ function(result, callback) {
+ teamID = result;
+ console.log(account+ ' added to ' + teamID);
+ res.redirect('/teampad/' + teamName);
+ },
+ ], function(err) {
+ console.log(err);
+ });
+ });
+
+ args.app.get('/teampad', function(req, res) {
+ var sessionID = req.signedCookies.express_sid;
+ var currentUser = null;
+ var signedIn = false;
+
+ sessionManager.getSessionInfo(sessionID, function(err, result) {
+ if (err) {
+ console.log(err);
+ } else {
+ currentUser = result.account;
+ signedIn = true;
+ }
+ });
+
+ var teamsInfo = [];
+
+ // TODO an index for finding teams by account would make this
+ // *way* faster and easier...
+ teamManager.listAllTeams(function(err, teams) {
+ for (var team in teams.teamIDs) {
+ teamID = teams.teamIDs[team];
+ teamManager.listInfo(teamID, function(err, info) {
+ if (info.accounts) {
+ if (info.accounts.indexOf(currentUser) != -1) {
+ teamsInfo.push(info);
+ }
+ }
+ });
+ }
+ res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
+
+ res.send(eejs.require('ep_etherpad-lite/templates/teampad/index.html',
+ { teamsInfo: teamsInfo,
+ signedIn: signedIn,
+ currentUser: currentUser}));
+ });
+ });
+
+ args.app.get('/teampad/:teamName', function(req, res) {
+ var sessionID = req.signedCookies.express_sid;
+ var currentUser = null;
+ var signedIn = false;
+
+ sessionManager.getSessionInfo(sessionID, function(err, result) {
+ if (err) {
+ console.log(err);
+ res.redirect('/teampad');
+ } else {
+ currentUser = result.account;
+ signedIn = true;
+
+ var teamName = req.path.split('/')[2];
+ var teamInfo = {
+ pads: [],
+ accounts: [],
+ name: [],
+ teamID: []
+ };
+
+ // TODO an index for finding pads/accounts by team would make this
+ // *way* faster and easier...
+ teamManager.listAllTeams(function(err, teams) {
+ for (var team in teams.teamIDs) {
+ teamID = teams.teamIDs[team];
+ teamManager.listInfo(teamID, function(err, info) {
+ if (info.name) {
+ if (teamName === info.name) {
+ teamInfo = info;
+ teamInfo.teamID = teamID;
+ }
+ }
+ });
+ }
+
+ res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
+
+ res.send(eejs.require('ep_etherpad-lite/templates/teampad/team.html',
+ {teamInfo: teamInfo,
+ signedIn: false}));
+ });
+ }
+ });
+ });
+
+ // TODO implement, for now we are linking to normal pads via templates
+ args.app.get('/teampad/:teamName/:padName', function(req, res) {
+ var sessionID = req.signedCookies.express_sid;
+ var currentUser = null;
+ var signedIn = false;
+
+ sessionManager.getSessionInfo(sessionID, function(err, result) {
+ if (err) {
+ console.log(err);
+ res.redirect('/teampad');
+ } else {
+ currentUser = result.account;
+ signedIn = true;
+ }
+ });
+
+ var padName = req.path.split('/')[3];
+
+ res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
+
+ res.send(eejs.require('ep_etherpad-lite/templates/teampad/pad.html'));
+ });
+}
diff --git a/src/node/padaccess.js b/src/node/padaccess.js
index d87809149..cc2dc3729 100644
--- a/src/node/padaccess.js
+++ b/src/node/padaccess.js
@@ -1,5 +1,6 @@
var ERR = require("async-stacktrace");
var securityManager = require('./db/SecurityManager');
+var padManager = require("./db/PadManager");
//checks for padAccess
module.exports = function (req, res, callback) {
@@ -7,7 +8,14 @@ module.exports = function (req, res, callback) {
// FIXME: Why is this ever undefined??
if (req.cookies === undefined) req.cookies = {};
- securityManager.checkAccess(req.params.pad, req.cookies.sessionID, req.cookies.token, req.cookies.password, function(err, accessObj) {
+ // FIXME: always use httponly session cookies
+ var sessionID = null;
+ if (padManager.isTeamPad(req.params.pad)) {
+ sessionID = req.cookies.sessionid;
+ } else {
+ sessionID = req.cookies.express_sid;
+ }
+ securityManager.checkAccess(req.params.pad, sessionID, req.cookies.token, req.cookies.password, function(err, accessObj) {
if(ERR(err, callback)) return;
//there is access, continue
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js
index 8435ab2c2..d9692f9b9 100644
--- a/src/node/utils/Settings.js
+++ b/src/node/utils/Settings.js
@@ -45,7 +45,7 @@ exports.faviconTimeslider = "../../" + exports.favicon;
/**
* The IP ep-lite should listen to
*/
-exports.ip = "0.0.0.0";
+exports.ip = process.env.VCAP_APP_HOST || "0.0.0.0";
/**
* The Port ep-lite should listen to
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/css/teampad.css b/src/static/css/teampad.css
new file mode 100644
index 000000000..c52bd277f
--- /dev/null
+++ b/src/static/css/teampad.css
@@ -0,0 +1,101 @@
+body {
+ margin: 0;
+ color: #333;
+ font: 14px helvetica, sans-serif;
+ background: #ddd;
+ background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
+ border-top: 8px solid rgba(51,51,51,.8);
+}
+#wrapper {
+ margin-top: 160px;
+ padding: 15px;
+ background: #fff;
+ opacity: .9;
+ box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
+ max-width: 700px;
+ margin: auto;
+ border-radius: 0 0 7px 7px;
+}
+h1 {
+ font-size: 29px;
+}
+h2 {
+ font-size: 24px;
+}
+.separator {
+ margin: 10px 0;
+ height: 1px;
+ background: #aaa;
+ background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+ background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+}
+form {
+ margin-bottom: 0;
+}
+#inner,
+#footer {
+ margin:0 auto;
+ width: 300px;
+}
+#footer {
+ margin-top: 160px;
+ padding: 15px;
+ text-align: center;
+}
+#teampads {
+ color: inherit;
+ font-size: 24px;
+ text-decoration: none;
+ letter-spacing: -2px;
+}
+#teampads::after {
+ content: ' »';
+}
+input {
+ font-weight: bold;
+ font-size: 15px;
+}
+input[type="button"] {
+ padding: 4px 6px;
+ margin: 0;
+}
+input[type="button"].do-install, input[type="button"].do-uninstall {
+ float: right;
+ width: 100px;
+}
+input[type="button"]#do-search {
+ display: block;
+}
+input[type="text"] {
+ border-radius: 3px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ padding: 10px;
+ *padding: 0; /* IE7 hack */
+ width: 100%;
+ outline: none;
+ border: 1px solid #ddd;
+ margin: 0 0 5px 0;
+ max-width: 500px;
+}
+table {
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ border-spacing: 0;
+ width: 100%;
+ margin: 20px 0;
+}
+table thead tr {
+ background: #eee;
+}
+td, th {
+ padding: 5px;
+}
+.template {
+ display: none;
+}
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..d62b9f685 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,12 @@ 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
+ setTimeout = parent.setTimeout;
+ clearTimeout = parent.clearTimeout;
+ setInterval = parent.setInterval;
+ clearInterval = parent.clearInterval;
var textFace = 'monospace';
var textSize = 12;
@@ -174,7 +179,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 +599,7 @@ function Ace2Inner(){
doesWrap = newVal;
var dwClass = "doesWrap";
setClassPresence(root, "doesWrap", doesWrap);
- scheduler.setTimeout(function()
+ setTimeout(function()
{
inCallStackIfNecessary("setWraps", function()
{
@@ -634,7 +639,7 @@ function Ace2Inner(){
textFace = face;
root.style.fontFamily = textFace;
lineMetricsDiv.style.fontFamily = textFace;
- scheduler.setTimeout(function()
+ setTimeout(function()
{
setUpTrackingCSS();
}, 0);
@@ -647,7 +652,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 +1090,7 @@ function Ace2Inner(){
{
if (scheduledTimeout)
{
- scheduler.clearTimeout(scheduledTimeout);
+ clearTimeout(scheduledTimeout);
scheduledTimeout = null;
}
}
@@ -1096,7 +1101,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 +2822,6 @@ function Ace2Inner(){
rep.selStart = selectStart;
rep.selEnd = selectEnd;
rep.selFocusAtStart = newSelFocusAtStart;
- if (mozillaFakeArrows) mozillaFakeArrows.notifySelectionChanged();
currentCallStack.repChanged = true;
return true;
@@ -3614,7 +3618,7 @@ function Ace2Inner(){
evt.preventDefault();
doReturnKey();
//scrollSelectionIntoView();
- scheduler.setTimeout(function()
+ setTimeout(function()
{
outerWin.scrollBy(-100, 0);
}, 0);
@@ -3690,11 +3694,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 +4153,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 +4761,7 @@ function Ace2Inner(){
});
- scheduler.setTimeout(function()
+ setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe
}, 0);
@@ -5032,331 +5071,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 +5209,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/pad.js b/src/static/js/pad.js
index 27dd3b737..a99e3e148 100644
--- a/src/static/js/pad.js
+++ b/src/static/js/pad.js
@@ -275,6 +275,11 @@ function handshake()
$('#passwordRequired').show();
$("#passwordinput").focus();
}
+ else if(obj.accessStatus == "denyTeamPad")
+ {
+ $("#editorloadingbox").html("This is a Team pad
" +
+ "Manage teampads");
+ }
}
//if we haven't recieved the clientVars yet, then this message should it be
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 @@
Team name | ++ |
+ <%= teamsInfo[i].name %> + | ++ <% if (teamsInfo[i].admins.indexOf(currentUser) != undefined) { %> + [admin] + <% } %> + | +