Merge branch 'master' into patch-1

pull/1/head
ansuz 7 years ago committed by GitHub
commit 7cc4cca846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,7 +45,8 @@
"bootstrap-tokenfield": "^0.12.1",
"localforage": "^1.5.2",
"html2canvas": "^0.4.1",
"croppie": "^2.5.0"
"croppie": "^2.5.0",
"sortablejs": "#^1.6.0"
},
"resolutions": {
"bootstrap": "v4.0.0-alpha.6"

@ -325,4 +325,12 @@ module.exports = {
// '/etc/apache2/ssl/my_public_cert.crt',
// '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca'
//],
/* You can get a repl for debugging the server if you want it.
* to enable this, specify the debugReplName and then you can
* connect to it with `nc -U /tmp/repl/<your name>.sock`
* If you run multiple cryptpad servers, you need to use different
* repl names.
*/
//debugReplName: "cryptpad"
};

@ -0,0 +1,61 @@
define([
'/bower_components/chainpad/chainpad.dist.js',
], function (ChainPad) {
var Diff = ChainPad.Diff;
var isSpace = function (S, i) {
return /^\s$/.test(S.charAt(i));
};
var leadingBoundary = function (S, offset) {
if (/\s/.test(S.charAt(offset))) { return offset; }
while (offset > 0) {
offset--;
if (isSpace(S, offset)) { offset++; break; }
}
return offset;
};
var trailingBoundary = function (S, offset) {
if (isSpace(S, offset)) { return offset; }
while (offset < S.length && !/\s/.test(S.charAt(offset))) {
offset++;
}
return offset;
};
var opsToWords = function (previous, current) {
var output = [];
Diff.diff(previous, current).forEach(function (op) {
// ignore deleted sections...
var offset = op.offset;
var toInsert = op.toInsert;
// given an operation, check whether it is a word fragment,
// if it is, expand it to its word boundaries
var first = current.slice(leadingBoundary(current, offset), offset);
var last = current.slice(offset + toInsert.length, trailingBoundary(current, offset + toInsert.length));
var result = first + toInsert + last;
// concat-in-place
Array.prototype.push.apply(output, result.split(/\s+/));
});
return output.filter(Boolean);
};
var runningDiff = function (getter, f, time) {
var last = getter();
// first time through, send all the words :D
f(opsToWords("", last));
return setInterval(function () {
var current = getter();
// find inserted words...
var words = opsToWords(last, current);
last = current;
f(words);
}, time);
};
return runningDiff;
});

@ -6,10 +6,17 @@ define([
'/common/outer/network-config.js',
'/customize/credential.js',
'/bower_components/chainpad/chainpad.dist.js',
'/common/common-realtime.js',
'/common/common-constants.js',
'/common/common-interface.js',
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/customize/messages.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad) {
], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI,
Feedback, LocalStore, Messages) {
var Exports = {
Cred: Cred,
};
@ -73,7 +80,7 @@ define([
var rt = opt.rt = Listmap.create(config);
rt.proxy
.on('ready', function () {
cb(void 0, rt);
setTimeout(function () { cb(void 0, rt); });
})
.on('disconnect', function (info) {
cb('E_DISCONNECT', info);
@ -84,7 +91,7 @@ define([
return Object.keys(proxy).length === 0;
};
Exports.loginOrRegister = function (uname, passwd, isRegister, cb) {
Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) {
if (typeof(cb) !== 'function') { return; }
// Usernames are all lowercase. No going back on this one
@ -137,10 +144,140 @@ define([
return void cb('ALREADY_REGISTERED', res);
}
setTimeout(function () { cb(void 0, res); });
if (isRegister) {
var proxy = rt.proxy;
proxy.edPublic = res.edPublic;
proxy.edPrivate = res.edPrivate;
proxy.curvePublic = res.curvePublic;
proxy.curvePrivate = res.curvePrivate;
proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
Feedback.send('REGISTRATION', true);
} else {
Feedback.send('LOGIN', true);
}
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
}
// We have to call whenRealtimeSyncs asynchronously here because in the current
// version of listmap, onLocal calls `chainpad.contentUpdate(newValue)`
// asynchronously.
// The following setTimeout is here to make sure whenRealtimeSyncs is called after
// `contentUpdate` so that we have an update userDoc in chainpad.
setTimeout(function () {
Realtime.whenRealtimeSyncs(rt.realtime, function () {
LocalStore.login(res.userHash, res.userName, function () {
setTimeout(function () { cb(void 0, res); });
});
});
});
});
});
};
Exports.redirect = function () {
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
parser.href = h;
if (parser.origin === window.location.origin) {
delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
}
window.location.href = '/drive/';
};
Exports.loginOrRegisterUI = function (uname, passwd, isRegister, shouldImport, testing, test) {
var hashing = true;
var proceed = function (result) {
hashing = false;
if (test && typeof test === "function" && test()) { return; }
Realtime.whenRealtimeSyncs(result.realtime, function () {
Exports.redirect();
});
};
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen
// pops up
window.setTimeout(function () {
UI.addLoadingScreen({
loadingText: Messages.login_hashing,
hideTips: true,
});
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed
// after hashing the password
window.setTimeout(function () {
Exports.loginOrRegister(uname, passwd, isRegister, shouldImport, function (err, result) {
var proxy;
if (result) { proxy = result.proxy; }
if (err) {
switch (err) {
case 'NO_SUCH_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_noSuchUser, function () {
hashing = false;
});
});
break;
case 'INVAL_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalUser, function () {
hashing = false;
});
});
break;
case 'INVAL_PASS':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalPass, function () {
hashing = false;
});
});
break;
case 'PASS_TOO_SHORT':
UI.removeLoadingScreen(function () {
var warning = Messages._getKey('register_passwordTooShort', [
Cred.MINIMUM_PASSWORD_LENGTH
]);
UI.alert(warning, function () {
hashing = false;
});
});
break;
case 'ALREADY_REGISTERED':
// logMeIn should reset registering = false
UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
proxy.login_name = uname;
if (!proxy[Constants.displayNameKey]) {
proxy[Constants.displayNameKey] = uname;
}
LocalStore.eraseTempSessionValues();
proceed(result);
});
});
break;
default: // UNHANDLED ERROR
hashing = false;
UI.errorLoadingScreen(Messages.login_unhandledError);
}
return;
}
if (testing) { return void proceed(result); }
proceed(result);
});
}, 0);
}, 200);
};
return Exports;
});

@ -43,7 +43,7 @@ define(req, function(Util, Default, Language) {
messages._checkTranslationState = function (cb) {
if (typeof(cb) !== "function") { return; }
var missing = [];
var allMissing = [];
var reqs = [];
Object.keys(map).forEach(function (code) {
if (code === defaultLanguage) { return; }
@ -54,37 +54,60 @@ define(req, function(Util, Default, Language) {
Object.keys(map).forEach(function (code, i) {
if (code === defaultLanguage) { return; }
var translation = langs[i];
var updated = {};
Object.keys(Default).forEach(function (k) {
if (/^updated_[0-9]+_/.test(k) && !translation[k]) {
var key = k.split('_').slice(2).join('_');
// Make sure we don't already have an update for that key. It should not happen
// but if it does, keep the latest version
if (updated[key]) {
var ek = updated[key];
if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; }
var missing = [];
var checkInObject = function (ref, translated, path) {
var updated = {};
Object.keys(ref).forEach(function (k) {
if (/^updated_[0-9]+_/.test(k) && !translated[k]) {
var key = k.split('_').slice(2).join('_');
// Make sure we don't already have an update for that key. It should not happen
// but if it does, keep the latest version
if (updated[key]) {
var ek = updated[key];
if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; }
}
updated[key] = k;
}
updated[key] = k;
}
});
Object.keys(Default).forEach(function (k) {
if (/^_/.test(k) || k === 'driveReadme') { return; }
if (!translation[k] || updated[k]) {
if (updated[k]) {
missing.push([code, k, 2, 'out.' + updated[k]]);
return;
});
Object.keys(ref).forEach(function (k) {
if (/^_/.test(k) || k === 'driveReadme') { return; }
var nPath = path.slice();
nPath.push(k);
if (!translated[k] || updated[k]) {
if (updated[k]) {
var uPath = path.slice();
uPath.unshift('out');
missing.push([code, nPath, 2, uPath.join('.') + '.' + updated[k]]);
return;
}
return void missing.push([code, nPath, 1]);
}
missing.push([code, k, 1]);
}
});
Object.keys(translation).forEach(function (k) {
if (/^_/.test(k) || k === 'driveReadme') { return; }
if (typeof Default[k] === "undefined") {
missing.push([code, k, 0]);
}
if (typeof ref[k] !== typeof translated[k]) {
return void missing.push([code, nPath, 3]);
}
if (typeof ref[k] === "object" && !Array.isArray(ref[k])) {
checkInObject(ref[k], translated[k], nPath);
}
});
Object.keys(translated).forEach(function (k) {
if (/^_/.test(k) || k === 'driveReadme') { return; }
var nPath = path.slice();
nPath.push(k);
if (typeof ref[k] === "undefined") {
missing.push([code, nPath, 0]);
}
});
};
checkInObject(Default, translation, []);
// Push the removals at the end
missing.sort(function (a, b) {
if (a[2] === 0 && b[2] !== 0) { return 1; }
if (a[2] !== 0 && b[2] === 0) { return -1; }
return 0;
});
Array.prototype.push.apply(allMissing, missing); // Destructive concat
});
cb(missing);
cb(allMissing);
});
};

@ -72,7 +72,7 @@ define([
])
])
]),
h('div.cp-version-footer', "CryptPad v1.25.0 (Zombie)")
h('div.cp-version-footer', "CryptPad v1.26.0 (undefined)")
]);
};

@ -139,7 +139,7 @@
> * {
width: 100%;
min-width: 300px;
min-width: 260px;
max-width: 500px;
margin: 0 auto;
text-align: left;
@ -184,7 +184,7 @@
}
}
.alertify-tabs-contents {
flex: 1;
flex: 1 1 auto;
min-height: 0;
& > div {
max-height: 100%;

@ -12,15 +12,16 @@
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
display: flex;
align-items: center;
flex-flow: column; /* we need column so that the child can shrink vertically */
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
@media screen and (max-height: 600px), screen and (max-width: 500px) {
align-items: baseline;
}
}
#cp-creation {
flex: 0 1 auto; /* allows shrink */
min-height: 0;
overflow: auto;
text-align: center;
font: @colortheme_app-font;
width: 100%;
@ -33,24 +34,35 @@
flex-wrap: wrap;
justify-content: center;
align-items: center;
h2, p {
width: 100%;
}
h2 {
width: 100%;
display: flex;
margin-bottom: 20px;
justify-content: space-between;
.cp-creation-help {
display: none;
}
}
.cp-creation-help-container {
width: 100%;
display: flex;
justify-content: space-between;
p {
padding: 0 20px;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 50%;
text-align: justify;
}
}
@media screen and (max-width: 500px) {
width: ~"calc(100% - 30px)";
}
@media screen and (max-height: 600px), screen and (max-width: 500px) {
@media screen and (max-height: 800px), screen and (max-width: 500px) {
h2 .cp-creation-help {
display: inline;
}
p {
.cp-creation-help-container {
display: none;
}
}
@ -148,5 +160,11 @@
}
}
}
.cp-creation-deleted {
background: #111;
padding: 10px;
text-align: justify;
font-weight: bold;
}
}
}

@ -29,6 +29,7 @@
background: blue;
position: absolute;
left: 0;
top: 0;
z-index: 1; // .usage
&.cp-limit-usage-normal {
background: @colortheme_green;

@ -131,6 +131,7 @@
white-space: nowrap;
display: flex;
flex-flow: column;
height: 100%;
.cp-toolbar-userlist-name {
flex: 1;
overflow: hidden;
@ -759,7 +760,7 @@
display: inline-flex;
align-items: center;
max-width: 100%;
flex: 1;
flex: 1 1 auto;
//margin-bottom: -1px;
.cp-toolbar-users {
pre {

@ -31,12 +31,16 @@ define(function () {
out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page.";
out.padNotPinned = 'Ce pad va expirer dans 3 mois, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.';
out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.";
out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible.";
out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.';
out.loading = "Chargement...";
out.error = "Erreur";
out.saved = "Enregistré";
out.synced = "Tout est enregistré";
out.deleted = "Pad supprimé de votre CryptDrive";
out.deletedFromServer = "Pad supprimé du serveur";
out.realtime_unrecoverableError = "Le moteur temps-réel a rencontré une erreur critique. Cliquez sur OK pour recharger la page.";
@ -847,20 +851,20 @@ define(function () {
out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans <a href='/settings/'>votre page de préférences</a>, où vous trouverez une case à cocher pour désactiver le retour d'expérience.";
// Creation page
out.creation_404 = "Le pad auquel vous souhaitez accéder n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant.";
out.creation_404 = "Ce pad n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant.";
out.creation_ownedTitle = "Type de pad";
out.creation_ownedTrue = "Pad possédé";
out.creation_ownedFalse = "Pad ouvert";
out.creation_owned1 = "Un pad <b>possédé</b> est un pad que vous pouvez supprimer du serveur à n'importe quel moment depuis votre CryptDrive. Une fois supprimé, personne d'autre ne peut y accéder, même si le pad est stocké dans un autre CryptDrive.";
out.creation_owned2 = "Un pad <b>ouvert</b> n'a pas de propriétaire et ne peut donc pas être supprimé du serveur par un utilisateur. Il pourra tout de même être supprimé automatiquement si sa date d'expiration est dépassée.";
out.creation_owned1 = "Un pad <b>possédé</b> peut être supprimé du serveur à tout moment quand son propriétaire le souhaite. Une fois supprimé, il disparaît du CryptDrive des autres utilisateurs.";
out.creation_owned2 = "Un pad <b>ouvert</b> n'a pas de propriétaire et ne peut donc pas être supprimé du serveur à moins d'avoir dépassé sa date d'expiration.";
out.creation_expireTitle = "Durée de vie";
out.creation_expireTrue = "Ajouter durée de vie";
out.creation_expireFalse = "Illimitée";
out.creation_expireHours = "Heures";
out.creation_expireDays = "Jours";
out.creation_expireFalse = "Illimité";
out.creation_expireHours = "Heure(s)";
out.creation_expireDays = "Jour(s)";
out.creation_expireMonths = "Mois";
out.creation_expire1 = "Par défault, un pad stocké dans le CryptDrive d'un utilisateur enregistré ne sera jamais supprimé du serveur, même s'il est inactif (à moins qu'il possède un propriétaire souhaitement le supprimer).";
out.creation_expire2 = "Si vous le souhaitez, vous pouvez ajouter une durée de vie au pad afin d'être sûr qu'il soit supprimé du serveur, de manière permanente, à la date voulue.";
out.creation_expire1 = "Un pad <b>illimité</b> ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
out.creation_expire2 = "Un pad <b>expirant</b> a une durée de vie définie, après laquelle il sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs.";
out.creation_createTitle = "Créer un pad";
out.creation_createFromTemplate = "Depuis un modèle";
out.creation_createFromScratch = "Nouveau pad vide";

@ -22,8 +22,7 @@ define(function () {
out.button_newslide = 'New Presentation';
out.button_newwhiteboard = 'New Whiteboard';
// NOTE: We want to update the 'common_connectionLost' key.
// Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost'
// NOTE: Remove updated_0_ if we need an updated_1_
out.updated_0_common_connectionLost = "<b>Server Connection Lost</b><br>You're now in read-only mode until the connection is back.";
out.common_connectionLost = out.updated_0_common_connectionLost;
@ -33,12 +32,16 @@ define(function () {
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire in 3 months, {0}login{1} or {2}register{3} to preserve it.';
out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive.";
out.expiredError = 'This pad has reached its expiration time and is no longer available.';
out.expiredErrorCopy = ' You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!';
out.deletedError = 'This pad has been deleted by its owner and is no longer available.';
out.loading = "Loading...";
out.error = "Error";
out.saved = "Saved";
out.synced = "Everything is saved";
out.deleted = "Pad deleted from your CryptDrive";
out.deletedFromServer = "Pad deleted from the server";
out.realtime_unrecoverableError = "The realtime engine has encountered an unrecoverable error. Click OK to reload.";
@ -862,20 +865,20 @@ define(function () {
out.feedback_optout = "If you would like to opt out, visit <a href='/settings/'>your user settings page</a>, where you'll find a checkbox to enable or disable user feedback";
// Creation page
out.creation_404 = "This pad not longer exists. Use the following form to create a new pad";
out.creation_404 = "This pad not longer exists. Use the following form to create a new pad.";
out.creation_ownedTitle = "Type of pad";
out.creation_ownedTrue = "Owned pad";
out.creation_ownedFalse = "Open pad";
out.creation_owned1 = "An <b>owned</b> pad is a pad that you can delete from the server whenever you want. Once it is deleted, no one else can access it, even if it is stored in their CryptDrive.";
out.creation_owned1 = "An <b>owned</b> pad can be deleted from the server whenever the owner wants. Deleting an owned pad removes it from other users' CryptDrives.";
out.creation_owned2 = "An <b>open</b> pad doesn't have any owner and thus, it can't be deleted from the server unless it has reached its expiration time.";
out.creation_expireTitle = "Life time";
out.creation_expireTrue = "Add a life time";
out.creation_expireFalse = "Unlimited";
out.creation_expireHours = "Hours";
out.creation_expireDays = "Days";
out.creation_expireMonths = "Months";
out.creation_expire1 = "By default, a pad stored by a registered user will never be removed from the server, unless it is requested by its owner.";
out.creation_expire2 = "If you prefer, you can set a life time to make sure the pad will be permanently deleted from the server and unavailable after the specified date.";
out.creation_expireHours = "Hour(s)";
out.creation_expireDays = "Day(s)";
out.creation_expireMonths = "Month(s)";
out.creation_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it.";
out.creation_expire2 = "An <b>expiring</b> pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
out.creation_createTitle = "Create a pad";
out.creation_createFromTemplate = "From template";
out.creation_createFromScratch = "From scratch";
@ -887,7 +890,7 @@ define(function () {
out.creation_expiration = "Expiration time";
out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pad, expiration pad, etc.).";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
// New share modal

@ -0,0 +1,115 @@
var Fs = require("fs");
var Path = require("path");
var nThen = require("nthen");
var config;
try {
config = require('./config');
} catch (e) {
console.log("You can customize the configuration by copying config.example.js to config.js");
config = require('./config.example');
}
var FileStorage = require(config.storage || './storage/file');
var root = Path.resolve(config.taskPath || './tasks');
var dirs;
var nt;
var store;
var queue = function (f) {
nt = nt.nThen(f);
};
var tryParse = function (s) {
try { return JSON.parse(s); }
catch (e) { return null; }
};
var CURRENT = +new Date();
var handleTask = function (str, path, cb) {
var task = tryParse(str);
if (!Array.isArray(task)) {
console.error('invalid task: not array');
return cb();
}
if (task.length < 2) {
console.error('invalid task: too small');
return cb();
}
var time = task[0];
var command = task[1];
var args = task.slice(2);
if (time > CURRENT) {
// not time for this task yet
console.log('not yet time');
return cb();
}
nThen(function (waitFor) {
switch (command) {
case 'EXPIRE':
console.log("expiring: %s", args[0]);
store.removeChannel(args[0], waitFor());
break;
default:
console.log("unknown command", command);
}
}).nThen(function () {
// remove the task file...
Fs.unlink(path, function (err) {
if (err) { console.error(err); }
cb();
});
});
};
nt = nThen(function (w) {
Fs.readdir(root, w(function (e, list) {
if (e) { throw e; }
dirs = list;
if (dirs.length === 0) {
w.abort();
return;
}
}));
}).nThen(function (waitFor) {
FileStorage.create(config, waitFor(function (_store) {
store = _store;
}));
}).nThen(function () {
dirs.forEach(function (dir, dIdx) {
queue(function (w) {
console.log('recursing into %s', dir);
Fs.readdir(Path.join(root, dir), w(function (e, list) {
list.forEach(function (fn) {
queue(function (w) {
var filePath = Path.join(root, dir, fn);
var cb = w();
console.log("processing file at %s", filePath);
Fs.readFile(filePath, 'utf8', function (e, str) {
if (e) {
console.error(e);
return void cb();
}
handleTask(str, filePath, cb);
});
});
});
if (dIdx === (dirs.length - 1)) {
queue(function () {
store.shutdown();
});
}
}));
});
});
});

@ -8,6 +8,7 @@
"express": "~4.10.1",
"nthen": "~0.1.0",
"pull-stream": "^3.6.1",
"replify": "^1.2.0",
"saferphore": "0.0.1",
"stream-to-pull-stream": "^1.7.2",
"tweetnacl": "~0.12.2",

@ -428,8 +428,7 @@ var getHash = function (Env, publicKey, cb) {
// The limits object contains storage limits for all the publicKey that have paid
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var limits = {};
var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) {
var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) {
if (config.adminEmail === false) {
if (config.allowSubscriptions === false) { return; }
throw new Error("allowSubscriptions must be false if adminEmail is false");
@ -494,15 +493,15 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
response.on('end', function () {
try {
var json = JSON.parse(str);
limits = json;
Env.limits = json;
Object.keys(customLimits).forEach(function (k) {
if (!isLimit(customLimits[k])) { return; }
limits[k] = customLimits[k];
Env.limits[k] = customLimits[k];
});
var l;
if (userId) {
var limit = limits[userId];
var limit = Env.limits[userId];
l = limit && typeof limit.limit === "number" ?
[limit.limit, limit.plan, limit.note] : [defaultLimit, '', ''];
}
@ -523,7 +522,7 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
var getLimit = function (Env, publicKey, cb) {
var unescapedKey = unescapeKeyCharacters(publicKey);
var limit = limits[unescapedKey];
var limit = Env.limits[unescapedKey];
var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'?
Env.defaultStorageLimit: DEFAULT_LIMIT;
@ -1097,7 +1096,11 @@ type NetfluxWebsocketSrvContext_t = {
)=>void
};
*/
RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) {
RPC.create = function (
config /*:Config_t*/,
debuggable /*:<T>(string, T)=>T*/,
cb /*:(?Error, ?Function)=>void*/
) {
// load pin-store...
console.log('loading rpc module...');
@ -1115,8 +1118,10 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
msgStore: (undefined /*:any*/),
pinStore: (undefined /*:any*/),
pinnedPads: {},
evPinnedPadsReady: mkEvent(true)
evPinnedPadsReady: mkEvent(true),
limits: {}
};
debuggable('rpc_env', Env);
var Sessions = Env.Sessions;
var paths = Env.paths;
@ -1176,7 +1181,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
});
case 'IS_NEW_CHANNEL':
return void isNewChannel(Env, msg[1], function (e, isNew) {
respond(null, [null, isNew, null]);
respond(e, [null, isNew, null]);
});
default:
console.error("unsupported!");
@ -1306,7 +1311,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
Respond(e, size);
});
case 'UPDATE_LIMITS':
return void updateLimits(config, safeKey, function (e, limit) {
return void updateLimits(Env, config, safeKey, function (e, limit) {
if (e) {
WARN(e, limit);
return void Respond(e);
@ -1341,9 +1346,9 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
});
case 'REMOVE_OWNED_CHANNEL':
return void removeOwnedChannel(Env, msg[1], publicKey, function (e, response) {
return void removeOwnedChannel(Env, msg[1], publicKey, function (e) {
if (e) { return void Respond(e); }
Respond(void 0, response);
Respond(void 0, "OK");
});
// restricted to privileged users...
case 'UPLOAD':
@ -1418,7 +1423,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
};
var updateLimitDaily = function () {
updateLimits(config, undefined, function (e) {
updateLimits(Env, config, undefined, function (e) {
if (e) {
WARN('limitUpdate', e);
}

@ -21,10 +21,25 @@ try {
var websocketPort = config.websocketPort || config.httpPort;
var useSecureWebsockets = config.useSecureWebsockets || false;
// This is stuff which will become available to replify
const debuggableStore = new WeakMap();
const debuggable = function (name, x) {
if (name in debuggableStore) {
try { throw new Error(); } catch (e) {
console.error('cannot add ' + name + ' more than once [' + e.stack + ']');
}
} else {
debuggableStore[name] = x;
}
return x;
};
debuggable('global', global);
debuggable('config', config);
// support multiple storage back ends
var Storage = require(config.storage||'./storage/file');
var app = Express();
var app = debuggable('app', Express());
var httpsOpts;
@ -102,6 +117,7 @@ Fs.exists(__dirname + "/customize", function (e) {
var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'contact'];
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static(__dirname + '/customize'));
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
@ -204,7 +220,6 @@ var rpc;
var nt = nThen(function (w) {
if (!config.enableTaskScheduling) { return; }
var Tasks = require("./storage/tasks");
console.log("loading task scheduler");
Tasks.create(config, w(function (e, tasks) {
config.tasks = tasks;
@ -214,7 +229,7 @@ var nt = nThen(function (w) {
if (typeof(config.rpc) !== 'string') { return; }
// load pin store...
var Rpc = require(config.rpc);
Rpc.create(config, w(function (e, _rpc) {
Rpc.create(config, debuggable, w(function (e, _rpc) {
if (e) {
w.abort();
throw e;
@ -233,3 +248,6 @@ var nt = nThen(function (w) {
});
});
if (config.debugReplName) {
require('replify')({ name: config.debugReplName, app: debuggableStore });
}

@ -418,6 +418,7 @@ module.exports.create = function (
openFileLimit: conf.openFileLimit || 2048,
};
// 0x1ff -> 777
var it;
Fs.mkdir(env.root, 0x1ff, function (err) {
if (err && err.code !== 'EEXIST') {
// TODO: somehow return a nice error
@ -465,9 +466,12 @@ module.exports.create = function (
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
clearChannel(env, channelName, cb);
},
shutdown: function () {
clearInterval(it);
}
});
});
setInterval(function () {
it = setInterval(function () {
flushUnusedChannels(env, function () { });
}, 5000);
};

@ -1,8 +1,9 @@
define([
'jquery',
'/common/cryptpad-common.js',
'/common/common-util.js',
'/customize/messages.js',
'/customize/translations/messages.js',
], function ($, Cryptpad, English) {
], function ($, Util, Messages, English) {
var $body = $('body');
@ -11,38 +12,40 @@ define([
};
var todo = function (missing) {
var str = "";
var need = 1;
var currentLang = "";
var currentState = 1;
if (missing.length) {
$body.append(pre(missing.map(function (msg) {
var res = "";
var code = msg[0];
var key = msg[1];
var needed = msg[2];
var lang = msg[0];
var key = msg[1]; // Array
var state = msg[2]; // 0 === toDelete, 1 === missing, 2 === updated, 3 === invalid (wrong type)
var value = msg[3] || '""';
if (str !== code) {
if (str !== "")
if (currentLang !== lang) {
if (currentLang !== "")
{
res += '\n';
}
str = code;
res += '/*\n *\n * ' + code + '\n *\n */\n\n';
currentLang = lang;
res += '/*\n *\n * ' + lang + '\n *\n */\n\n';
}
if (need !== needed) {
need = needed;
if (need === 0)
if (currentState !== state) {
currentState = state;
if (currentState === 0)
{
res += '\n// TODO: These keys are not needed anymore and should be removed ('+ code + ')\n\n';
res += '\n// TODO: These keys are not needed anymore and should be removed ('+ lang + ')\n\n';
}
}
res += (need ? '' : '// ') + 'out.' + key + ' = ' + value + ';';
if (need === 1) {
res += ' // ' + JSON.stringify(English[key]);
} else if (need === 2) {
res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before that one.';
res += (currentState ? '' : '// ') + 'out.' + key.join('.') + ' = ' + value + ';';
if (currentState === 1) {
res += ' // ' + JSON.stringify(Util.find(English, key));
} else if (currentState === 2) {
res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before this one.';
} else if (currentState === 3) {
res += ' // NOTE: this key has an invalid type! Original value: ' + JSON.stringify(Util.find(English, key));
}
return res;
}).join('\n')));
@ -50,5 +53,5 @@ define([
$body.text('// All keys are present in all translations');
}
};
Cryptpad.Messages._checkTranslationState(todo);
Messages._checkTranslationState(todo);
});

@ -83,8 +83,9 @@ define(function() {
contacts: 'fa-users',
};
// EXPERIMENTAL: Enabling "displayCreationScreen" may cause UI issues and possible loss of data
config.displayCreationScreen = false;
// Ability to create owned pads and expiring pads through a new pad creation screen.
// The new screen can be disabled by the users in their settings page
config.displayCreationScreen = true;
// Prevent anonymous users from storing pads in their drive
config.disableAnonymousStore = false;

@ -553,6 +553,7 @@ define([
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
@ -600,11 +601,20 @@ define([
}, 3750);
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen({hideTips: true}); }
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) { $('#' + LOADING).hide(); }
});
}
};
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});

@ -141,7 +141,7 @@ define([
};
var getPadProperties = function (common, data, cb) {
var $d = $('<div>');
if (!data || !data.href) { return void cb(void 0, $d); }
if (!data || (!data.href && !data.roHref)) { return void cb(void 0, $d); }
if (data.href) {
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
@ -283,10 +283,11 @@ define([
present: present
});
};
var getLinkValue = function () {
var edit = $(link).find('#cp-share-editable-true').is(':checked');
var embed = $(link).find('#cp-share-embed').is(':checked');
var present = $(link).find('#cp-share-present').is(':checked');
var getLinkValue = function (initValue) {
var val = initValue || {};
var edit = initValue ? val.edit : $(link).find('#cp-share-editable-true').is(':checked');
var embed = initValue ? val.embed : $(link).find('#cp-share-embed').is(':checked');
var present = initValue ? val.present : $(link).find('#cp-share-present').is(':checked');
var hash = (edit && hashes.editHash) ? hashes.editHash : hashes.viewHash;
var href = origin + pathname + '#' + hash;
@ -375,6 +376,11 @@ define([
}
if (val.embed) { $(link).find('#cp-share-embed').attr('checked', true); }
if (val.present) { $(link).find('#cp-share-present').attr('checked', true); }
$(link).find('#cp-share-link-preview').val(getLinkValue(val));
});
common.getMetadataMgr().onChange(function () {
hashes = common.getMetadataMgr().getPrivateData().availableHashes;
$(link).find('#cp-share-link-preview').val(getLinkValue());
});
return tabs;
};
@ -1655,8 +1661,6 @@ define([
var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getMetadataLazy().type;
// XXX check text for pad creation screen + translate it in French
var $body = $('body');
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
var $creation = $('<div>', { id: 'cp-creation' }).appendTo($creationContainer);
@ -1687,7 +1691,10 @@ define([
Messages.creation_ownedTitle,
createHelper(Messages.creation_owned1 + '\n' + Messages.creation_owned2)
]),
setHTML(h('p'), Messages.creation_owned1 + '<br>' + Messages.creation_owned2),
h('div.cp-creation-help-container', [
setHTML(h('p'), Messages.creation_owned1),
setHTML(h('p'), Messages.creation_owned2)
]),
h('input#cp-creation-owned-true.cp-creation-owned-value', {
type: 'radio',
name: 'cp-creation-owned',
@ -1715,7 +1722,10 @@ define([
Messages.creation_expireTitle,
createHelper(Messages.creation_expire1, Messages.creation_expire2)
]),
setHTML(h('p'), Messages.creation_expire1 + '<br>' + Messages.creation_expire2),
h('div.cp-creation-help-container', [
setHTML(h('p'), Messages.creation_expire1),
setHTML(h('p'), Messages.creation_expire2)
]),
h('input#cp-creation-expire-false.cp-creation-expire-value', {
type: 'radio',
name: 'cp-creation-expire',
@ -1825,5 +1835,24 @@ define([
}, Messages.creation_settings))).appendTo($creation);
};
UIElements.onServerError = function (common, err, toolbar, cb) {
if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; }
var msg = err.type;
if (err.type === 'EEXPIRED') {
msg = Messages.expiredError;
if (err.loaded) {
msg += Messages.expiredErrorCopy;
}
} else if (err.type === 'EDELETED') {
msg = Messages.deletedError;
if (err.loaded) {
msg += Messages.expiredErrorCopy;
}
}
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
UI.errorLoadingScreen(msg, true, true);
(cb || function () {})();
};
return UIElements;
});

@ -538,6 +538,7 @@ define([
pad.onJoinEvent = Util.mkEvent();
pad.onLeaveEvent = Util.mkEvent();
pad.onDisconnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent();
common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb);
@ -679,6 +680,9 @@ define([
case 'PAD_DISCONNECT': {
common.padRpc.onDisconnectEvent.fire(data); break;
}
case 'PAD_ERROR': {
common.padRpc.onErrorEvent.fire(data); break;
}
// Drive
case 'DRIVE_LOG': {
common.drive.onLog.fire(data); break;

@ -115,6 +115,12 @@ define(['json.sortify'], function (Sortify) {
if (!meta.user) { return; }
change(true);
});
sframeChan.on('EV_RT_ERROR', function (err) {
if (err.type !== 'EEXPIRED' && err.type !== 'EDELETED') { return; }
members = [];
if (!meta.user) { return; }
change(true);
});
return Object.freeze({
updateMetadata: function (m) {

@ -815,6 +815,9 @@ define([
onDisconnect: function () {
postMessage("PAD_DISCONNECT");
}, // post EV_PAD_DISCONNECT
onError: function (err) {
postMessage("PAD_ERROR", err);
}, // post EV_PAD_ERROR
channel: data.channel,
validateKey: data.validateKey,
owners: data.owners,
@ -894,7 +897,7 @@ define([
case 'addFolder':
store.userObject.addFolder(data.path, data.name, cb); break;
case 'delete':
store.userObject.delete(data.paths, cb, data.nocheck); break;
store.userObject.delete(data.paths, cb, data.nocheck, data.isOwnPadRemoved); break;
case 'emptyTrash':
store.userObject.emptyTrash(cb); break;
case 'rename':

@ -33,6 +33,7 @@ define([], function () {
var onLeave = conf.onLeave;
var onReady = conf.onReady;
var onDisconnect = conf.onDisconnect;
var onError = conf.onError;
var owners = conf.owners;
var password = conf.password;
var expire = conf.expire;
@ -44,6 +45,17 @@ define([], function () {
var messageFromOuter = function () {};
var error = function (err, wc) {
if (onError) {
onError({
type: err,
loaded: !initializing
});
if (wc && (err === "EEXPIRED" || err === "EDELETED")) { wc.leave(); }
}
else { console.error(err); }
};
var onRdy = function (padData) {
// Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
// message through "network" when it is synced, and it triggers onReady for each channel joined.
@ -96,11 +108,17 @@ define([], function () {
if (peer === hk) {
// if the peer is the 'history keeper', extract their message
var parsed1 = JSON.parse(msg);
// First check if it is an error message (EXPIRED/DELETED)
if (parsed1.channel === wc.id && parsed1.error) {
return void error(parsed1.error, wc);
}
msg = parsed1[4];
// Check that this is a message for our channel
if (parsed1[3] !== wc.id) { return; }
}
lastKnownHash = msg.slice(0,64);
var message = msgIn(peer, msg);
@ -177,7 +195,12 @@ define([], function () {
};
var msg = ['GET_HISTORY', wc.id, cfg];
// Add the validateKey if we are the channel creator and we have a validateKey
if (hk) { network.sendto(hk, JSON.stringify(msg)); }
if (hk) {
network.sendto(hk, JSON.stringify(msg)).then(function () {
}, function (err) {
console.error(err);
});
}
} else {
onRdy();
}
@ -204,8 +227,8 @@ define([], function () {
// join the netflux network, promise to handle opening of the channel
network.join(channel || null).then(function(wc) {
onOpen(wc, network, firstConnection);
}, function(error) {
console.error(error);
}, function(err) {
console.error(err);
});
};

@ -3,8 +3,9 @@ define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-feedback.js',
'/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Messages) {
], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) {
var module = {};
var clone = function (o) {
@ -85,8 +86,11 @@ define([
delete files[FILES_DATA][id];
};
exp.checkDeletedFiles = function () {
// Nothing in OLD_FILES_DATA for workgroups
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells
// us they're already removed
exp.checkDeletedFiles = function (isOwnPadRemoved) {
// Nothing in FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
@ -96,9 +100,19 @@ define([
var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
// If trying to remove an owned pad, remove it from server also
if (fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) { console.error(obj.error); }
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if (obj.error.code === 'ENOENT') { return; }
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
}
});
}
if (channelId) { toClean.push(channelId); }
@ -123,7 +137,7 @@ define([
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck) {
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
@ -179,7 +193,7 @@ define([
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(); }
if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
};
// Move

@ -157,8 +157,8 @@ define([
}
rpc.send('REMOVE_OWNED_CHANNEL', channel, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length) {
cb(void 0, response[0]); // I haven't tested this...
if (response && response.length && response[0] === "OK") {
cb();
} else {
cb('INVALID_RESPONSE');
}

@ -102,7 +102,7 @@ types of messages:
}
// HACK to hide messages from the anon rpc
if (parsed.length !== 4) {
if (parsed.length !== 4 && parsed[1] !== 'ERROR') {
console.log(parsed);
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
}
@ -217,6 +217,15 @@ types of messages:
});
});
if (network.onHistoryKeeperChange) {
network.onHistoryKeeperChange(function () {
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
ctx.connected = true;
});
});
}
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it

@ -7,6 +7,7 @@ define([
'/common/sframe-common.js',
'/customize/messages.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-thumbnail.js',
'/common/common-feedback.js',
@ -27,6 +28,7 @@ define([
SFCommon,
Messages,
Util,
Hash,
UI,
Thumb,
Feedback,
@ -41,6 +43,7 @@ define([
var STATE = Object.freeze({
DISCONNECTED: 'DISCONNECTED',
FORGOTTEN: 'FORGOTTEN',
DELETED: 'DELETED',
INFINITE_SPINNER: 'INFINITE_SPINNER',
INITIALIZING: 'INITIALIZING',
HISTORY_MODE: 'HISTORY_MODE',
@ -84,6 +87,7 @@ define([
});
});
var textContentGetter;
var titleRecommender = function () { return false; };
var contentGetter = function () { return UNINITIALIZED; };
var normalize0 = function (x) { return x; };
@ -116,8 +120,9 @@ define([
var stateChange = function (newState) {
var wasEditable = (state === STATE.READY);
if (state === STATE.DELETED) { return; }
if (state === STATE.INFINITE_SPINNER && newState !== STATE.READY) { return; }
if (newState === STATE.INFINITE_SPINNER) {
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
state = newState;
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
throw new Error("Cannot transition from DISCONNECTED to " + newState);
@ -146,6 +151,10 @@ define([
evStart.reg(function () { toolbar.forgotten(); });
break;
}
case STATE.DELETED: {
evStart.reg(function () { toolbar.deleted(); });
break;
}
default:
}
if (wasEditable !== (state === STATE.READY)) {
@ -254,6 +263,7 @@ define([
var onReady = function () {
var newContentStr = cpNfInner.chainpad.getUserDoc();
if (state === STATE.DELETED) { return; }
var newPad = false;
if (newContentStr === '') { newPad = true; }
@ -287,11 +297,17 @@ define([
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) {
var channelId = Hash.hrefToHexChannelId(href);
AppConfig.textAnalyzer(textContentGetter, channelId);
}
if (options.thumbnail && privateDat.thumbnails) {
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
if (hash) {
options.thumbnail.href = privateDat.pathname + '#' + hash;
options.thumbnail.href = href;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();
@ -307,6 +323,7 @@ define([
}
};
var onConnectionChange = function (info) {
if (state === STATE.DELETED) { return; }
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
if (info.state) {
UI.findOKButton().click();
@ -315,6 +332,12 @@ define([
}
};
var onError = function (err) {
common.onServerError(err, toolbar, function () {
stateChange(STATE.DELETED);
});
};
var setFileExporter = function (extension, fe, async) {
var $export = common.createButton('export', true, {}, function () {
var ext = (typeof(extension) === 'function') ? extension() : extension;
@ -407,7 +430,9 @@ define([
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) { return void common.createPad(c, waitFor()); }
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (waitFor) {
@ -432,7 +457,8 @@ define([
onLocal: onLocal,
onInit: function () { stateChange(STATE.INITIALIZING); },
onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange
onConnectionChange: onConnectionChange,
onError: onError
});
var privReady = Util.once(waitFor());
@ -448,6 +474,7 @@ define([
var infiniteSpinnerModal = false;
window.setInterval(function () {
if (state === STATE.DISCONNECTED) { return; }
if (state === STATE.DELETED) { return; }
var l;
try {
l = cpNfInner.chainpad.getLag();
@ -567,6 +594,10 @@ define([
// in the pad when requested by the framework.
setContentGetter: function (cg) { contentGetter = cg; },
// Set a text content supplier, this is a function which will give a text
// representation of the pad content if a text analyzer is configured
setTextContentGetter: function (tcg) { textContentGetter = tcg; },
// Inform the framework that the content of the pad has been changed locally.
localChange: onLocal,

@ -34,6 +34,7 @@ define([
var onLocal = config.onLocal || function () { };
var setMyID = config.setMyID || function () { };
var onReady = config.onReady || function () { };
var onError = config.onError || function () { };
var userName = config.userName;
var initialState = config.initialState;
if (config.transformFunction) { throw new Error("transformFunction is nolonger allowed"); }
@ -83,6 +84,11 @@ define([
chainpad.abort();
onConnectionChange({ state: false });
});
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;
chainpad.abort();
onError(err);
});
sframeChan.on('EV_RT_CONNECT', function (content) {
//content.members.forEach(userList.onJoin);
isReady = false;

@ -102,6 +102,10 @@ define([], function () {
sframeChan.event('EV_RT_DISCONNECT');
});
padRpc.onErrorEvent.reg(function (err) {
sframeChan.event('EV_RT_ERROR', err);
});
// join the netflux network, promise to handle opening of the channel
padRpc.joinPad({
channel: channel || null,

@ -44,12 +44,12 @@ define([
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
var to = window.setTimeout(function () {
/*var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 30000);
}, 30000);*/
common.getFullHistory(realtime, function () {
window.clearTimeout(to);
//window.clearTimeout(to);
cb(null, realtime);
});
};

@ -132,10 +132,12 @@ define([
// Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; }
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
if (e) { return console.error(e); }
isNewFile = Boolean(isNew);
}));
if (realtime) {
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
if (e) { return console.error(e); }
isNewFile = Boolean(isNew);
}));
}
}).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr;
var isNewHash = true;
@ -623,6 +625,7 @@ define([
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret.channel, secret);
readOnly = false;
updateMeta();

@ -92,6 +92,7 @@ define([
funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar);
funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen);
funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal);
funcs.onServerError = callWithCommon(UIElements.onServerError);
// Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);

@ -31,6 +31,8 @@ define({
'EV_RT_CONNECT': true,
// Called after the history is finished synchronizing, no arguments.
'EV_RT_READY': true,
// Called when the server returns an error in a pad (EEXPIRED, EDELETED).
'EV_RT_ERROR': true,
// Called from both outside and inside, argument is a (string) chainpad message.
'Q_RT_MESSAGE': true,

@ -709,6 +709,7 @@ define([
typing = 1;
$spin.text(Messages.typing);
$spin.interval = window.setInterval(function () {
if (toolbar.isErrorState) { return; }
var dots = Array(typing+1).join('.');
$spin.text(Messages.typing + dots);
typing++;
@ -718,6 +719,7 @@ define([
var onSynced = function () {
if ($spin.timeout) { clearTimeout($spin.timeout); }
$spin.timeout = setTimeout(function () {
if (toolbar.isErrorState) { return; }
window.clearInterval($spin.interval);
typing = -1;
$spin.text(Messages.saved);
@ -1094,6 +1096,16 @@ define([
}
};
// When the pad is deleted from the server
toolbar.deleted = function (/*userId*/) {
toolbar.isErrorState = true;
toolbar.connected = false;
updateUserList(toolbar, config);
if (toolbar.spinner) {
toolbar.spinner.text(Messages.deletedFromServer);
}
};
// On log out, remove permanently the realtime elements of the toolbar
Common.onLogout(function () {
failed();

@ -556,17 +556,18 @@ define([
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
exp.delete = function (paths, cb, nocheck) {
exp.delete = function (paths, cb, nocheck, isOwnPadRemoved) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "delete",
data: {
paths: paths,
nocheck: nocheck
nocheck: nocheck,
isOwnPadRemoved: isOwnPadRemoved
}
}, cb);
}
exp.deleteMultiplePermanently(paths, nocheck);
exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved);
if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {

@ -512,7 +512,11 @@ span {
}
input {
width: 100%;
margin-top: 5px;
margin: 0;
padding: 0;
border-radius: 0;
border: 1px solid #ddd;
font-size: 14px;
}
.cp-app-drive-element-state {
position: absolute;
@ -568,6 +572,11 @@ span {
}
li {
display: table-row;
input {
border: 1px solid #ddd;
margin: 0;
padding: 0 4px;
}
&> span {
padding: 0 5px;
display: table-cell;

@ -2232,7 +2232,7 @@ define([
// Only Trash and Root are available in not-owned files manager
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError);
log(Messages.fm_categoryError);
currentPath = [ROOT];
_displayDirectory(currentPath);
return;
@ -2673,11 +2673,14 @@ define([
var data = JSON.parse(JSON.stringify(filesOp.getFileData(el)));
if (!data || !data.href) { return void cb('INVALID_FILE'); }
data.href = base + data.href;
var roUrl;
if (ro) {
data.roHref = data.href;
delete data.href;
} else {
data.roHref = base + getReadOnlyUrl(el);
roUrl = getReadOnlyUrl(el);
if (roUrl) { data.roHref = base + roUrl; }
}
UIElements.getProperties(common, data, cb);
@ -2712,6 +2715,8 @@ define([
UI.confirm(msgD, function(res) {
$(window).focus();
if (!res) { return; }
filesOp.delete(pathsList, refresh);
/*
// Try to delete each selected pad from server, and delete from drive if no error
var n = nThen(function () {});
pathsList.forEach(function (p) {
@ -2723,10 +2728,12 @@ define([
sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel,
waitFor(function (e) {
if (e) { return void console.error(e); }
filesOp.delete([p], refresh);
filesOp.delete([p], function () {}, false, true);
}));
});
});
n.nThen(function () { refresh(); });
*/
});
};
$contextMenu.on("click", "a", function(e) {

@ -13,7 +13,6 @@ define([
$(function () {
var $main = $('#mainBlock');
var $checkImport = $('#import-recent');
var Messages = Cryptpad.Messages;
// main block is hidden in case javascript is disabled
$main.removeClass('hidden');
@ -61,90 +60,15 @@ define([
hashing = true;
var shouldImport = $checkImport[0].checked;
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
window.setTimeout(function () {
UI.addLoadingScreen({
loadingText: Messages.login_hashing,
hideTips: true,
});
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
window.setTimeout(function () {
loginReady(function () {
var uname = $uname.val();
var passwd = $passwd.val();
Login.loginOrRegister(uname, passwd, false, function (err, result) {
if (!err) {
var proxy = result.proxy;
// successful validation and user already exists
// set user hash in localStorage and redirect to drive
if (!proxy.login_name) {
result.proxy.login_name = result.userName;
}
proxy.edPrivate = result.edPrivate;
proxy.edPublic = result.edPublic;
proxy.curvePrivate = result.curvePrivate;
proxy.curvePublic = result.curvePublic;
Feedback.send('LOGIN', true);
Realtime.whenRealtimeSyncs(result.realtime, function() {
LocalStore.login(result.userHash, result.userName, function () {
hashing = false;
if (test) {
localStorage.clear();
test.pass();
return;
}
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
}
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
parser.href = h;
if (parser.origin === window.location.origin) {
delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
}
window.location.href = '/drive/';
});
});
return;
}
switch (err) {
case 'NO_SUCH_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_noSuchUser, function () {
hashing = false;
});
});
break;
case 'INVAL_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalUser, function () {
hashing = false;
});
});
break;
case 'INVAL_PASS':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalPass, function () {
hashing = false;
});
});
break;
default: // UNHANDLED ERROR
UI.errorLoadingScreen(Messages.login_unhandledError);
}
});
});
}, 0);
}, 100);
var uname = $uname.val();
var passwd = $passwd.val();
Login.loginOrRegisterUI(uname, passwd, false, shouldImport, Test.testing, function () {
if (test) {
localStorage.clear();
test.pass();
return true;
}
});
});
$('#register').on('click', function () {
if (sessionStorage) {

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html class="cp">
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
<head>
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script async data-main="main" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
</head>
<body class="html">
<noscript>
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
</noscript>
</html>

@ -0,0 +1,5 @@
define(['/bower_components/localforage/dist/localforage.min.js'], function (localForage) {
localForage.clear();
sessionStorage.clear();
localStorage.clear();
});

@ -402,6 +402,17 @@ define([
}
});
framework.setTextContentGetter(function () {
var innerCopy = inner.cloneNode(true);
displayMediaTags(framework, innerCopy, mediaTagMap);
innerCopy.normalize();
$(innerCopy).find('*').each(function (i, el) {
$(el).append(' ');
});
var str = $(innerCopy).text();
str = str.replace(/\s\s+/g, ' ');
return str;
});
framework.setContentGetter(function () {
displayMediaTags(framework, inner, mediaTagMap);
inner.normalize();

@ -6,6 +6,7 @@
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/creation.less';
.toolbar_main(
@bg-color: @colortheme_poll-bg,
@ -15,6 +16,7 @@
.fileupload_main();
.alertify_main();
.tokenfield_main();
.creation_main();
@poll-fore: #555;

@ -1119,17 +1119,31 @@ define([
}
UI.removeLoadingScreen();
if (isNew) {
var privateDat = metadataMgr.getPrivateData();
var skipTemp = Util.find(privateDat,
['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
};
var onDisconnect = function () {
// Manage disconnections because of network or error
var onDisconnect = function (info) {
if (APP.unrecoverable) { return; }
if (info && info.type) {
// Server error
return void common.onServerError(info, APP.toolbar, function () {
APP.unrecoverable = true;
setEditable(false);
});
}
setEditable(false);
UI.alert(Messages.common_connectionLost, undefined, true);
};
var onReconnect = function () {
if (APP.unrecoverable) { return; }
setEditable(true);
UI.findOKButton().click();
};
@ -1175,6 +1189,7 @@ define([
Title.setToolbar(APP.toolbar);
var $rightside = APP.toolbar.$rightside;
var $drawer = APP.toolbar.$drawer;
metadataMgr.onChange(function () {
var md = copyObject(metadataMgr.getMetadata());
@ -1189,6 +1204,9 @@ define([
var $forgetPad = common.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var $properties = common.createButton('properties', true);
$drawer.append($properties);
/* save as template */
if (!metadataMgr.getPrivateData().isTemplate) {
var templateObj = {
@ -1201,7 +1219,7 @@ define([
/* add an export button */
var $export = common.createButton('export', true, {}, exportFile);
$rightside.append($export);
$drawer.append($export);
var $help = common.createButton('', true).click(function () { showHelp(); })
.appendTo($rightside);
@ -1255,6 +1273,16 @@ define([
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
if (!AppConfig.displayCreationScreen) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (/* waitFor */) {
Test.registerInner(common.getSframeChannel());
var metadataMgr = common.getMetadataMgr();

@ -36,6 +36,8 @@ define([
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start();
SFCommonO.start({
useCreationScreen: true
});
});
});

@ -55,39 +55,6 @@ define([
var registering = false;
var test;
var logMeIn = function (result) {
LocalStore.setUserHash(result.userHash);
var proxy = result.proxy;
proxy.edPublic = result.edPublic;
proxy.edPrivate = result.edPrivate;
proxy.curvePublic = result.curvePublic;
proxy.curvePrivate = result.curvePrivate;
Feedback.send('REGISTRATION', true);
Realtime.whenRealtimeSyncs(result.realtime, function () {
LocalStore.login(result.userHash, result.userName, function () {
registering = false;
if (test) {
localStorage.clear();
test.pass();
return;
}
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
parser.href = h;
if (parser.origin === window.location.origin) {
delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
}
window.location.href = '/drive/';
});
});
};
$register.click(function () {
if (registering) {
@ -125,89 +92,14 @@ define([
function (yes) {
if (!yes) { return; }
Login.loginOrRegisterUI(uname, passwd, true, shouldImport, Test.testing, function () {
if (test) {
localStorage.clear();
test.pass();
return true;
}
});
registering = true;
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
window.setTimeout(function () {
UI.addLoadingScreen({
loadingText: Messages.login_hashing,
hideTips: true,
});
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
window.setTimeout(function () {
Login.loginOrRegister(uname, passwd, true, function (err, result) {
var proxy;
if (result) { proxy = result.proxy; }
if (err) {
switch (err) {
case 'NO_SUCH_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_noSuchUser, function () {
registering = false;
});
});
break;
case 'INVAL_USER':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalUser, function () {
registering = false;
});
});
break;
case 'INVAL_PASS':
UI.removeLoadingScreen(function () {
UI.alert(Messages.login_invalPass, function () {
registering = false;
});
});
break;
case 'PASS_TOO_SHORT':
UI.removeLoadingScreen(function () {
var warning = Messages._getKey('register_passwordTooShort', [
Cred.MINIMUM_PASSWORD_LENGTH
]);
UI.alert(warning, function () {
registering = false;
});
});
break;
case 'ALREADY_REGISTERED':
// logMeIn should reset registering = false
UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
proxy.login_name = uname;
if (!proxy[Constants.displayNameKey]) {
proxy[Constants.displayNameKey] = uname;
}
LocalStore.eraseTempSessionValues();
logMeIn(result);
});
});
break;
default: // UNHANDLED ERROR
registering = false;
UI.errorLoadingScreen(Messages.login_unhandledError);
}
return;
}
if (Test.testing) { return void logMeIn(result); }
LocalStore.eraseTempSessionValues();
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
}
proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
logMeIn(result);
});
}, 0);
}, 200);
}, {
ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel,

@ -88,6 +88,13 @@
color: #777;
}
.cp-app-todo-task-input {
margin: @spacing;
flex: 1;
min-width: 0;
font-weight: bold;
display: none;
}
.cp-app-todo-task-text {
margin: @spacing;
flex: 1;

@ -9,6 +9,7 @@ define([
'/common/common-hash.js',
'/todo/todo.js',
'/customize/messages.js',
'/bower_components/sortablejs/Sortable.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
@ -23,7 +24,8 @@ define([
UI,
Hash,
Todo,
Messages
Messages,
Sortable
)
{
var APP = window.APP = {};
@ -47,6 +49,17 @@ define([
var onReady = function () {
var todo = Todo.init(APP.lm.proxy);
Sortable.create($list[0], {
store: {
get: function () {
return todo.getOrder();
},
set: function (sortable) {
todo.reorder(sortable.toArray());
}
}
});
var deleteTask = function(id) {
todo.remove(id);
@ -70,6 +83,10 @@ define([
var makeCheckbox = function (id, cb) {
var entry = APP.lm.proxy.data[id];
if (!entry || typeof(entry) !== 'object') {
return void console.log('entry undefined');
}
var checked = entry.state === 1 ?
'cp-app-todo-task-checkbox-checked fa-check-square-o':
'cp-app-todo-task-checkbox-unchecked fa-square-o';
@ -92,6 +109,7 @@ define([
};
var addTaskUI = function (el, animate) {
if (!el) { return; }
var $taskDiv = $('<div>', {
'class': 'cp-app-todo-task'
});
@ -101,6 +119,7 @@ define([
$taskDiv.appendTo($list);
}
$taskDiv.data('id', el);
$taskDiv.attr('data-id', el);
makeCheckbox(el, function (/*state*/) {
APP.display();
@ -108,14 +127,34 @@ define([
.appendTo($taskDiv);
var entry = APP.lm.proxy.data[el];
if (!entry || typeof(entry) !== 'object') {
return void console.log('entry undefined');
}
if (entry.state) {
$taskDiv.addClass('cp-app-todo-task-complete');
}
$('<span>', { 'class': 'cp-app-todo-task-text' })
.text(entry.task)
.appendTo($taskDiv);
var $span = $('<span>', { 'class': 'cp-app-todo-task-text' });
var $input = $('<input>', {
type: 'text',
'class': 'cp-app-todo-task-input'
}).val(entry.task).keydown(function (e) {
if (e.which === 13) {
todo.val(el, 'task', $input.val().trim());
$input.hide();
$span.text($input.val().trim());
$span.show();
}
}).appendTo($taskDiv);
$span.text(entry.task)
.appendTo($taskDiv)
.click(function () {
$input.show();
$span.hide();
});
/*$('<span>', { 'class': 'cp-app-todo-task-date' })
.text(new Date(entry.ctime).toLocaleString())
.appendTo($taskDiv);*/

@ -39,6 +39,24 @@ define([
if (typeof(proxy.data) !== 'object') { proxy.data = {}; }
if (!Array.isArray(proxy.order)) { proxy.order = []; }
if (typeof(proxy.type) !== 'string') { proxy.type = 'todo'; }
// if a key exists in order, but there is no data for it...
// remove that key
var i = proxy.order.length - 1;
for (;i >= 0; i--) {
if (typeof(proxy.data[proxy.order[i]]) === 'undefined') {
console.log('removing todo entry with no data at [%s]', i);
proxy.order.splice(i, 1);
}
}
// if you have data, but it's not in the order array...
// add it to the order array...
Object.keys(proxy.data).forEach(function (key) {
if (proxy.order.indexOf(key) > -1) { return; }
console.log("restoring entry with missing key");
proxy.order.unshift(key);
});
};
/* add (id, obj) push id to order, add object to data */
@ -59,6 +77,17 @@ define([
if (proxy.data[id]) { delete proxy.data[id]; }
};
/* change the order in the proxy (with a check to make sure that nothing is missing */
var reorder = function (proxy, order) {
var existingOrder = proxy.order.slice().sort();
var newOrder = order.slice().sort();
if (JSON.stringify(existingOrder) === JSON.stringify(newOrder)) {
proxy.order = order.slice();
} else {
console.error("Can't reorder the tasks. Some tasks are missing or added");
}
};
Todo.init = function (proxy) {
var api = {};
initialize(proxy);
@ -72,6 +101,12 @@ define([
api.remove = function (id) {
return remove(proxy, id);
};
api.getOrder = function () {
return proxy.order.slice();
};
api.reorder = function (order) {
return reorder(proxy, order);
};
return api;
};

@ -5,6 +5,7 @@
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) '../../customize/src/less2/include/creation.less';
.toolbar_main(
@bg-color: @colortheme_whiteboard-bg,
@ -14,6 +15,7 @@
.fileupload_main();
.alertify_main();
.tokenfield_main();
.creation_main();
// body
&.cp-app-whiteboard {

@ -415,6 +415,7 @@ define([
Title.setToolbar(toolbar);
var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
/* save as template */
if (!metadataMgr.getPrivateData().isTemplate) {
@ -428,7 +429,7 @@ define([
/* add an export button */
var $export = common.createButton('export', true, {}, saveImage);
$rightside.append($export);
$drawer.append($export);
if (common.isLoggedIn()) {
common.createButton('savetodrive', true, {}, function () {})
@ -449,6 +450,9 @@ define([
});
$rightside.append($forget);
var $properties = common.createButton('properties', true);
toolbar.$drawer.append($properties);
if (!readOnly) {
makeColorButton($rightside);
@ -562,7 +566,12 @@ define([
if (readOnly) { return; }
if (isNew) {
var privateDat = metadataMgr.getPrivateData();
var skipTemp = Util.find(privateDat,
['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
});
@ -589,6 +598,7 @@ define([
};
config.onAbort = function () {
if (APP.unrecoverable) { return; }
// inform of network disconnect
setEditable(false);
toolbar.failed();
@ -596,6 +606,7 @@ define([
};
config.onConnectionChange = function (info) {
if (APP.unrecoverable) { return; }
setEditable(info.state);
if (info.state) {
initializing = true;
@ -605,10 +616,18 @@ define([
}
};
config.onError = function (err) {
common.onServerError(err, toolbar, function () {
APP.unrecoverable = true;
setEditable(false);
});
};
cpNfInner = common.startRealtime(config);
metadataMgr = cpNfInner.metadataMgr;
cpNfInner.onInfiniteSpinner(function () {
if (APP.unrecoverable) { return; }
setEditable(false);
UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
@ -640,6 +659,18 @@ define([
$('body').append($div.html());
}));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
if (!AppConfig.displayCreationScreen) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
if (c.skip && !priv.forceCreationScreen) {
return void common.createPad(c, waitFor());
}
common.getPadCreationScreen(c, waitFor());
}
}).nThen(function (/*waitFor*/) {
andThen(common);
});

@ -36,6 +36,8 @@ define([
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start();
SFCommonO.start({
useCreationScreen: true
});
});
});

Loading…
Cancel
Save