mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-14 11:06:55 -04:00
Merge 7002a6cd42
into 7a0ad3235a
This commit is contained in:
commit
89bef4cd76
32 changed files with 1136 additions and 506 deletions
3
LICENSE
3
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.
|
||||
|
|
3
app.js
Executable file
3
app.js
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var server = require("ep_etherpad-lite/node/server.js");
|
8
package.json
Normal file
8
package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "etherpad-lite",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node" : ">=0.6.0",
|
||||
"npm" : ">=1.0"
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
211
src/node/db/TeamManager.js
Normal file
211
src/node/db/TeamManager.js
Normal file
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
276
src/node/hooks/express/teampad.js
Normal file
276
src/node/hooks/express/teampad.js
Normal file
|
@ -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'));
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -42,6 +42,7 @@ div.innerwrapper {
|
|||
border-radius: 0 0 7px 7px;
|
||||
margin-left:250px;
|
||||
min-width:400px;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
|
|
101
src/static/css/teampad.css
Normal file
101
src/static/css/teampad.css
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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 <link>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)
|
||||
return html10n;
|
||||
})(window, document);
|
||||
|
|
|
@ -275,6 +275,11 @@ function handshake()
|
|||
$('#passwordRequired').show();
|
||||
$("#passwordinput").focus();
|
||||
}
|
||||
else if(obj.accessStatus == "denyTeamPad")
|
||||
{
|
||||
$("#editorloadingbox").html("<b>This is a Team pad</b><br>" +
|
||||
"<a href='/teampad'>Manage teampads</a>");
|
||||
}
|
||||
}
|
||||
|
||||
//if we haven't recieved the clientVars yet, then this message should it be
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,12 +9,15 @@
|
|||
<body>
|
||||
<div id="wrapper">
|
||||
<div class="menu">
|
||||
<h1>Etherpad lite</h1>
|
||||
<li><a href="admin/plugins">Plugin manager</a> </li>
|
||||
<li><a href="admin/settings">Settings</a> </li>
|
||||
<li><a href="admin/plugins/info">Troubleshooting information</a> </li>
|
||||
<h1>Etherpad lite</h1>
|
||||
<ul>
|
||||
<% e.begin_block("adminMenu"); %>
|
||||
<li><a href="admin/plugins">Plugin manager</a> </li>
|
||||
<li><a href="admin/settings">Settings</a> </li>
|
||||
<li><a href="admin/plugins/info">Troubleshooting information</a> </li>
|
||||
<% e.end_block(); %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="topborder"></div>
|
||||
</body>
|
||||
|
|
|
@ -12,9 +12,13 @@
|
|||
<div id="wrapper">
|
||||
<div class="menu">
|
||||
<h1>Etherpad lite</h1>
|
||||
<li><a href="../plugins">Plugin manager</a> </li>
|
||||
<li><a href="../settings">Settings</a> </li>
|
||||
<li><a href="../plugins/info">Troubleshooting information</a> </li>
|
||||
<ul>
|
||||
<% e.begin_block("adminMenu"); %>
|
||||
<li><a href="../plugins">Plugin manager</a> </li>
|
||||
<li><a href="../settings">Settings</a> </li>
|
||||
<li><a href="../plugins/info">Troubleshooting information</a> </li>
|
||||
<% e.end_block(); %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="innerwrapper">
|
||||
|
|
|
@ -19,11 +19,14 @@
|
|||
<% } %>
|
||||
|
||||
<div class="menu">
|
||||
<h1>Etherpad lite</h1>
|
||||
<li><a href="plugins">Plugin manager</a> </li>
|
||||
<li><a href="settings">Settings</a> </li>
|
||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||
|
||||
<h1>Etherpad lite</h1>
|
||||
<ul>
|
||||
<% e.begin_block("adminMenu"); %>
|
||||
<li><a href="plugins">Plugin manager</a> </li>
|
||||
<li><a href="settings">Settings</a> </li>
|
||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||
<% e.end_block(); %>
|
||||
</ul>
|
||||
<div id="progress"><img src="../static/img/loading.gif" alt=""/> <span class="message"></span></div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -24,9 +24,13 @@
|
|||
|
||||
<div class="menu">
|
||||
<h1>Etherpad lite</h1>
|
||||
<li><a href="plugins">Plugin manager</a> </li>
|
||||
<li><a href="settings">Settings</a> </li>
|
||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||
<ul>
|
||||
<% e.begin_block("adminMenu"); %>
|
||||
<li><a href="plugins">Plugin manager</a> </li>
|
||||
<li><a href="settings">Settings</a> </li>
|
||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||
<% e.end_block(); %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="innerwrapper">
|
||||
|
|
|
@ -63,9 +63,24 @@
|
|||
background: -o-linear-gradient(#fff,#ccc);
|
||||
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
#inner {
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
#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: ' »';
|
||||
}
|
||||
#button {
|
||||
margin: 0 auto;
|
||||
|
@ -146,6 +161,9 @@
|
|||
text-align: center;
|
||||
}
|
||||
}
|
||||
#teampads {
|
||||
font: 32px verdana,arial,sans-serif;
|
||||
}
|
||||
</style>
|
||||
<link href="static/custom/index.css" rel="stylesheet">
|
||||
|
||||
|
@ -162,6 +180,10 @@
|
|||
<% e.end_block(); %>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<a id="teampads" href="/teampad">Manage TeamPads</a>
|
||||
</div>
|
||||
|
||||
<script src="static/custom/index.js"></script>
|
||||
<script>
|
||||
|
||||
|
|
72
src/templates/teampad/index.html
Normal file
72
src/templates/teampad/index.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Teampad</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="../../static/css/teampad.css">
|
||||
</head>
|
||||
|
||||
<div id="wrapper">
|
||||
<h1>Etherpad Lite</h1>
|
||||
<% if (signedIn) { %>
|
||||
<div class="separator"></div>
|
||||
|
||||
<h2>Teams you are a member of</h2>
|
||||
<% if (teamsInfo.length == 0) { %>
|
||||
None yet!
|
||||
<% } else { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td id="name">Team name</td>
|
||||
<td id="controls"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (var i=0; i < teamsInfo.length; i++) { %>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/teampad/<%= teamsInfo[i].name %>"><%= teamsInfo[i].name %></a>
|
||||
</td>
|
||||
<td>
|
||||
<% if (teamsInfo[i].admins.indexOf(currentUser) != undefined) { %>
|
||||
[admin]
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } %>
|
||||
<div class="separator"></div>
|
||||
<form action="/teampad/createteam" method="post">
|
||||
<h2>Create new team:<h2>
|
||||
<input name="teamname" placeholder="Team name" type="text">
|
||||
<input type="submit" name="create" value="Create">
|
||||
</form>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<span>Sign in to create, manage and see your team pads →</span>
|
||||
|
||||
<form action="/teampad/verify" method="post" style="display:none;">
|
||||
<input type="text" name="assertion"/>
|
||||
</form>
|
||||
|
||||
<img src="https://browserid.org/i/sign_in_red.png" id="browserid"/>
|
||||
<script src="https://login.persona.org/include.js" type="text/javascript"></script>
|
||||
<script>
|
||||
$('#browserid').click(function(){
|
||||
navigator.id.getVerifiedEmail(function(assertion) {
|
||||
if (assertion) {
|
||||
$('input').val(assertion);
|
||||
$('form').submit();
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% } %>
|
||||
</div>
|
||||
</html>
|
69
src/templates/teampad/team.html
Normal file
69
src/templates/teampad/team.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= teamInfo.name %></title>
|
||||
<link rel="stylesheet" href="../../static/css/teampad.css">
|
||||
</head>
|
||||
<div id="wrapper">
|
||||
<h1>Etherpad Lite</h1>
|
||||
<div class="separator"></div>
|
||||
<h2>Pads belonging to this team:</h2>
|
||||
<% if (teamInfo.pads.length == 0) { %>
|
||||
None yet!
|
||||
<% } else { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td id="name">Pad name</td>
|
||||
<td id="controls"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (var i=0; i < teamInfo.pads.length; i++) { %>
|
||||
<tr>
|
||||
<td>
|
||||
<% var teamName = teamInfo.pads[i].split('+')[1]; %>
|
||||
<a href="/p/<%= teamInfo.pads[i] %>"><%= teamName %></a>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } %>
|
||||
<form action="/teampad/createpad" method="post">
|
||||
<h2>Create pad:</h2>
|
||||
<input name="padname" placeholder="Pad name" type="text">
|
||||
<input name="teamname" type="hidden" value="<%= teamInfo.name %>">
|
||||
<input name="teamID" type="hidden" value="<%= teamInfo.teamID %>">
|
||||
<input type="submit" value="Create">
|
||||
</form>
|
||||
<div class="separator"></div>
|
||||
<h2>Authors belonging to this team:</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td id="name">Account name</td>
|
||||
<td id="controls"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (var i=0; i < teamInfo.accounts.length; i++) { %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= teamInfo.accounts[i] %>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<form action="/teampad/addaccount" method="post">
|
||||
<h2>Add account to team:</h2>
|
||||
<input name="accountname" placeholder="Account name" type="email">
|
||||
<input name="teamname" type="hidden" value="<%= teamInfo.name %>">
|
||||
<input name="teamID" type="hidden" value="<%= teamInfo.teamID %>">
|
||||
<input type="submit" value="Add">
|
||||
</form>
|
||||
</div>
|
||||
</html>
|
5
stackato.yml
Normal file
5
stackato.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: etherpad-lite
|
||||
instances: 1
|
||||
framework:
|
||||
type: node
|
||||
mem: 128
|
Loading…
Add table
Add a link
Reference in a new issue