mirror of https://github.com/xwiki-labs/cryptpad
Merge branch 'master' into patch-1
This commit is contained in:
commit
7cc4cca846
|
@ -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",
|
||||
|
|
31
rpc.js
31
rpc.js
|
@ -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);
|
||||
}
|
||||
|
|
24
server.js
24
server.js
|
@ -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…
Reference in New Issue