Merge branch 'staging' into oo

pull/1/head
yflory 7 years ago
commit 5c1e5a3f49

@ -1,5 +1,7 @@
data data
Dockerfile Dockerfile
docker-compose.yml docker-compose.yml
.dockerignore
.git .git
.gitignore .gitignore
node_modules

1
.gitignore vendored

@ -1,4 +1,5 @@
datastore datastore
tasks
www/bower_components/* www/bower_components/*
node_modules node_modules
/config.js /config.js

@ -9,3 +9,4 @@ node_js:
script: script:
- npm run-script lint - npm run-script lint
- npm run-script flow - npm run-script flow
- docker build -t xwiki/cryptpad .

@ -4,7 +4,7 @@ COPY . /cryptpad
WORKDIR /cryptpad WORKDIR /cryptpad
RUN apk add --no-cache git tini \ RUN apk add --no-cache git tini \
&& npm install \ && npm install --production \
&& npm install -g bower \ && npm install -g bower \
&& bower install --allow-root && bower install --allow-root

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

@ -8,7 +8,9 @@ var domain = ' http://localhost:3000/';
// If your system doesn't support dumping, comment this out and install with // If your system doesn't support dumping, comment this out and install with
// `npm install --production` // `npm install --production`
// See: https://strongloop.github.io/strongloop.com/strongblog/how-to-heap-snapshots/ // See: https://strongloop.github.io/strongloop.com/strongblog/how-to-heap-snapshots/
require('heapdump');
// to enable this feature, uncomment the line below:
// require('heapdump');
module.exports = { module.exports = {
@ -124,7 +126,8 @@ module.exports = {
'about', 'about',
'contact', 'contact',
'what-is-cryptpad', 'what-is-cryptpad',
'features' 'features',
'faq'
], ],
/* Limits, Donations, Subscriptions and Contact /* Limits, Donations, Subscriptions and Contact
@ -192,6 +195,18 @@ module.exports = {
*/ */
}, },
/* some features may require that the server be able to schedule tasks
far into the future, such as:
> "three months from now, this channel should expire"
To disable these features, set 'enableTaskScheduling' to false
*/
enableTaskScheduling: true,
/* if you would like the list of scheduled tasks to be stored in
a custom location, change the path below:
*/
taskPath: './tasks',
/* /*
* By default, CryptPad also contacts our accounts server once a day to check for changes in * By default, CryptPad also contacts our accounts server once a day to check for changes in
* the people who have accounts. This check-in will also send the version of your CryptPad * the people who have accounts. This check-in will also send the version of your CryptPad
@ -311,4 +326,12 @@ module.exports = {
// '/etc/apache2/ssl/my_public_cert.crt', // '/etc/apache2/ssl/my_public_cert.crt',
// '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca' // '/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', '/common/outer/network-config.js',
'/customize/credential.js', '/customize/credential.js',
'/bower_components/chainpad/chainpad.dist.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/tweetnacl/nacl-fast.min.js',
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed '/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 = { var Exports = {
Cred: Cred, Cred: Cred,
}; };
@ -73,7 +80,7 @@ define([
var rt = opt.rt = Listmap.create(config); var rt = opt.rt = Listmap.create(config);
rt.proxy rt.proxy
.on('ready', function () { .on('ready', function () {
cb(void 0, rt); setTimeout(function () { cb(void 0, rt); });
}) })
.on('disconnect', function (info) { .on('disconnect', function (info) {
cb('E_DISCONNECT', info); cb('E_DISCONNECT', info);
@ -84,7 +91,7 @@ define([
return Object.keys(proxy).length === 0; 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; } if (typeof(cb) !== 'function') { return; }
// Usernames are all lowercase. No going back on this one // Usernames are all lowercase. No going back on this one
@ -137,10 +144,140 @@ define([
return void cb('ALREADY_REGISTERED', res); 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; return Exports;
}); });

@ -43,7 +43,7 @@ define(req, function(Util, Default, Language) {
messages._checkTranslationState = function (cb) { messages._checkTranslationState = function (cb) {
if (typeof(cb) !== "function") { return; } if (typeof(cb) !== "function") { return; }
var missing = []; var allMissing = [];
var reqs = []; var reqs = [];
Object.keys(map).forEach(function (code) { Object.keys(map).forEach(function (code) {
if (code === defaultLanguage) { return; } if (code === defaultLanguage) { return; }
@ -54,37 +54,60 @@ define(req, function(Util, Default, Language) {
Object.keys(map).forEach(function (code, i) { Object.keys(map).forEach(function (code, i) {
if (code === defaultLanguage) { return; } if (code === defaultLanguage) { return; }
var translation = langs[i]; var translation = langs[i];
var updated = {}; var missing = [];
Object.keys(Default).forEach(function (k) { var checkInObject = function (ref, translated, path) {
if (/^updated_[0-9]+_/.test(k) && !translation[k]) { var updated = {};
var key = k.split('_').slice(2).join('_'); Object.keys(ref).forEach(function (k) {
// Make sure we don't already have an update for that key. It should not happen if (/^updated_[0-9]+_/.test(k) && !translated[k]) {
// but if it does, keep the latest version var key = k.split('_').slice(2).join('_');
if (updated[key]) { // Make sure we don't already have an update for that key. It should not happen
var ek = updated[key]; // but if it does, keep the latest version
if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } 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(ref).forEach(function (k) {
}); if (/^_/.test(k) || k === 'driveReadme') { return; }
Object.keys(Default).forEach(function (k) { var nPath = path.slice();
if (/^_/.test(k) || k === 'driveReadme') { return; } nPath.push(k);
if (!translation[k] || updated[k]) { if (!translated[k] || updated[k]) {
if (updated[k]) { if (updated[k]) {
missing.push([code, k, 2, 'out.' + updated[k]]); var uPath = path.slice();
return; 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]); if (typeof ref[k] !== typeof translated[k]) {
} return void missing.push([code, nPath, 3]);
}); }
Object.keys(translation).forEach(function (k) { if (typeof ref[k] === "object" && !Array.isArray(ref[k])) {
if (/^_/.test(k) || k === 'driveReadme') { return; } checkInObject(ref[k], translated[k], nPath);
if (!Default[k]) { }
missing.push([code, k, 0]); });
} 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)")
]); ]);
}; };
@ -100,6 +100,7 @@ define([
h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [
h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad),
h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog), h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog),
h('a.nav-item.nav-link', { href: '/features.html'}, Msg.features),
h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact), h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact),
h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about), h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about),
].concat(rightLinks)) ].concat(rightLinks))
@ -332,6 +333,11 @@ define([
h('td') h('td')
]), ]),
]) ])
]),
h('div#cp-features-register', [
h('a', {
href: '/register/'
}, h('button.cp-features-register-button', 'Register for free'))
]) ])
]), ]),
infopageFooter() infopageFooter()
@ -367,6 +373,42 @@ define([
]); ]);
}; };
Pages['/faq.html'] = function () {
var categories = [];
var faq = Msg.faq;
Object.keys(faq).forEach(function (c) {
var questions = [];
Object.keys(faq[c]).forEach(function (q) {
var item = faq[c][q];
if (typeof item !== "object") { return; }
var answer = h('p.cp-faq-questions-a');
var question = h('p.cp-faq-questions-q');
$(question).click(function () {
if ($(answer).is(':visible')) {
return void $(answer).slideUp();
}
$(answer).slideDown();
});
questions.push(h('div.cp-faq-questions-items', [
setHTML(question, item.q),
setHTML(answer, item.a)
]));
});
categories.push(h('div.cp-faq-category', [
h('h3', faq[c].title),
h('div.cp-faq-category-questions', questions)
]));
});
return h('div#cp-main', [
infopageTopbar(),
h('div.container.cp-container', [
h('center', h('h1', Msg.faq_title)),
h('div.cp-faq-container', categories)
]),
infopageFooter()
]);
};
Pages['/terms.html'] = function () { Pages['/terms.html'] = function () {
return h('div#cp-main', [ return h('div#cp-main', [
infopageTopbar(), infopageTopbar(),
@ -601,6 +643,9 @@ define([
setHTML(h('p.register-explanation'), Msg.register_explanation) setHTML(h('p.register-explanation'), Msg.register_explanation)
]), ]),
h('div#userForm.form-group.hidden.col-md-6', [ h('div#userForm.form-group.hidden.col-md-6', [
h('a', {
href: '/features.html'
}, Msg.register_whyRegister),
h('input.form-control#username', { h('input.form-control#username', {
type: 'text', type: 'text',
autocomplete: 'off', autocomplete: 'off',

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

@ -12,15 +12,16 @@
background: @colortheme_loading-bg; background: @colortheme_loading-bg;
color: @colortheme_loading-color; color: @colortheme_loading-color;
display: flex; display: flex;
align-items: center; flex-flow: column; /* we need column so that the child can shrink vertically */
justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@media screen and (max-height: 600px), screen and (max-width: 500px) {
align-items: baseline;
}
} }
#cp-creation { #cp-creation {
flex: 0 1 auto; /* allows shrink */
min-height: 0;
overflow: auto;
text-align: center; text-align: center;
font: @colortheme_app-font; font: @colortheme_app-font;
width: 100%; width: 100%;
@ -33,24 +34,35 @@
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
h2, p {
width: 100%;
}
h2 { h2 {
width: 100%;
display: flex; display: flex;
margin-bottom: 20px;
justify-content: space-between; justify-content: space-between;
.cp-creation-help { .cp-creation-help {
display: none; 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) { @media screen and (max-width: 500px) {
width: ~"calc(100% - 30px)"; 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 { h2 .cp-creation-help {
display: inline; display: inline;
} }
p { .cp-creation-help-container {
display: none; display: none;
} }
} }
@ -139,5 +151,20 @@
} }
} }
} }
.cp-creation-settings {
justify-content: left;
a {
color: #0275d8;
&:hover {
color: lighten(#0275d8, 10%);
}
}
}
.cp-creation-deleted {
background: #111;
padding: 10px;
text-align: justify;
font-weight: bold;
}
} }
} }

@ -8,9 +8,10 @@
width: 100%; width: 100%;
margin-top: 20px; margin-top: 20px;
.cp-limit-bar { .cp-limit-bar {
padding: 5px; display: inline-flex;
justify-content: center;
align-items: center;
display: inline-block;
max-width: 100%; max-width: 100%;
margin: 3px; margin: 3px;
box-sizing: border-box; box-sizing: border-box;
@ -18,7 +19,6 @@
background: white; background: white;
position: relative; position: relative;
text-align: center; text-align: center;
vertical-align: middle;
width: ~"calc(100% - 6px)"; width: ~"calc(100% - 6px)";
height: 35px; height: 35px;
line-height: 25px; line-height: 25px;
@ -29,6 +29,7 @@
background: blue; background: blue;
position: absolute; position: absolute;
left: 0; left: 0;
top: 0;
z-index: 1; // .usage z-index: 1; // .usage
&.cp-limit-usage-normal { &.cp-limit-usage-normal {
background: @colortheme_green; background: @colortheme_green;

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

@ -10,6 +10,7 @@ body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; }
body.cp-page-about { @import "./pages/page-about.less"; } body.cp-page-about { @import "./pages/page-about.less"; }
body.cp-page-privacy { @import "./pages/page-privacy.less"; } body.cp-page-privacy { @import "./pages/page-privacy.less"; }
body.cp-page-features { @import "./pages/page-features.less"; } body.cp-page-features { @import "./pages/page-features.less"; }
body.cp-page-faq { @import "./pages/page-faq.less"; }
body.cp-page-terms { @import "./pages/page-terms.less"; } body.cp-page-terms { @import "./pages/page-terms.less"; }
// Set the HTML style for the apps which shouldn't have a body scrollbar // Set the HTML style for the apps which shouldn't have a body scrollbar

@ -50,3 +50,18 @@ table#cp-features-table {
} }
} }
#cp-features-register {
text-align: center;
padding: 20px;
}
.cp-features-register-button {
font-size: 20px;
color: #fff;
background: @cryptpad_color_blue;
border: 2px solid @cryptpad_color_blue;
border-radius: 0;
&:hover {
transform: scale(1.05);
}
}

@ -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.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.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.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.loading = "Chargement...";
out.error = "Erreur"; out.error = "Erreur";
out.saved = "Enregistré"; out.saved = "Enregistré";
out.synced = "Tout est enregistré"; out.synced = "Tout est enregistré";
out.deleted = "Pad supprimé de votre CryptDrive"; 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."; out.realtime_unrecoverableError = "Le moteur temps-réel a rencontré une erreur critique. Cliquez sur OK pour recharger la page.";
@ -377,7 +381,8 @@ define(function () {
out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?";
out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?";
out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?";
out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?";
out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?";
out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?";
out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?"; out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?";
out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?"; out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?";
@ -392,7 +397,8 @@ define(function () {
out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive"; out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive";
out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_trash = out.updated_0_fm_info_trash;
out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here
out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">découvrez pourquoi</a>). ' + out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads seront donc supprimés après 3 mois d\'inactivité (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">découvrez pourquoi</a>). ' +
'Ils sont stockés dans votre navigateur donc nettoyer votre historique peut les faire disparaître.<br>' +
'<a href="/register/">Inscrivez-vous</a> ou <a href="/login/">connectez-vous</a> pour les maintenir en vie.'; '<a href="/register/">Inscrivez-vous</a> ou <a href="/login/">connectez-vous</a> pour les maintenir en vie.';
out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs."; out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs.";
out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.<br>" + out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.<br>" +
@ -415,6 +421,7 @@ define(function () {
"Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur."; "Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur.";
out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad"; out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad";
out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur"; out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur";
out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}";
// File - Context menu // File - Context menu
out.fc_newfolder = "Nouveau dossier"; out.fc_newfolder = "Nouveau dossier";
out.fc_rename = "Renommer"; out.fc_rename = "Renommer";
@ -474,6 +481,7 @@ define(function () {
out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants."; out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants.";
out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?"; out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?";
out.register_whyRegister = "Pourquoi s'inscrire ?";
out.register_header = "Bienvenue dans CryptPad"; out.register_header = "Bienvenue dans CryptPad";
out.register_explanation = [ out.register_explanation = [
"<p>Faisons d'abord le point sur certaines choses</p>", "<p>Faisons d'abord le point sur certaines choses</p>",
@ -490,6 +498,7 @@ define(function () {
out.settings_cat_code = "Code"; out.settings_cat_code = "Code";
out.settings_cat_pad = "Documents texte"; out.settings_cat_pad = "Documents texte";
out.settings_cat_creation = "Nouveau pad"; out.settings_cat_creation = "Nouveau pad";
out.settings_cat_subscription = "Abonnement";
out.settings_title = "Préférences"; out.settings_title = "Préférences";
out.settings_save = "Sauver"; out.settings_save = "Sauver";
@ -566,7 +575,9 @@ define(function () {
out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?";
out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive.";
out.upload_notEnoughSpace = "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier."; out.upload_notEnoughSpace = "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier.";
out.upload_notEnoughSpaceBrief = "Pas assez d'espace";
out.upload_tooLarge = "Ce fichier dépasse la taille maximale autorisée."; out.upload_tooLarge = "Ce fichier dépasse la taille maximale autorisée.";
out.upload_tooLargeBrief = 'Fichier trop volumineux';
out.upload_choose = "Choisir un fichier"; out.upload_choose = "Choisir un fichier";
out.upload_pending = "En attente"; out.upload_pending = "En attente";
out.upload_cancelled = "Annulé"; out.upload_cancelled = "Annulé";
@ -576,6 +587,7 @@ define(function () {
out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier"; out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier";
out.download_button = "Déchiffrer et télécharger"; out.download_button = "Déchiffrer et télécharger";
out.download_mt_button = "Télécharger"; out.download_mt_button = "Télécharger";
out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible...";
out.todo_title = "CryptTodo"; out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Décrivez votre tâche..."; out.todo_newTodoNamePlaceholder = "Décrivez votre tâche...";
@ -706,6 +718,7 @@ define(function () {
// features.html // features.html
out.features = "Fonctionnalités";
out.features_title = "Tableau des fonctionnalités"; out.features_title = "Tableau des fonctionnalités";
out.features_feature = "Fonctionnalité"; out.features_feature = "Fonctionnalité";
out.features_anon = "Utilisateur anonyme"; out.features_anon = "Utilisateur anonyme";
@ -838,6 +851,24 @@ 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."; 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 // Creation page
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> 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é";
out.creation_expireHours = "Heure(s)";
out.creation_expireDays = "Jour(s)";
out.creation_expireMonths = "Mois";
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";
out.creation_settings = "Préférences des nouveaux pads";
// Properties about creation data // Properties about creation data
out.creation_owners = "Propriétaires"; out.creation_owners = "Propriétaires";
out.creation_ownedByOther = "Possédé par un autre utilisateur"; out.creation_ownedByOther = "Possédé par un autre utilisateur";

@ -22,8 +22,7 @@ define(function () {
out.button_newslide = 'New Presentation'; out.button_newslide = 'New Presentation';
out.button_newwhiteboard = 'New Whiteboard'; out.button_newwhiteboard = 'New Whiteboard';
// NOTE: We want to update the 'common_connectionLost' key. // NOTE: Remove updated_0_ if we need an updated_1_
// Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost'
out.updated_0_common_connectionLost = "<b>Server Connection Lost</b><br>You're now in read-only mode until the connection is back."; 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; 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.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.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.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.loading = "Loading...";
out.error = "Error"; out.error = "Error";
out.saved = "Saved"; out.saved = "Saved";
out.synced = "Everything is saved"; out.synced = "Everything is saved";
out.deleted = "Pad deleted from your CryptDrive"; 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."; out.realtime_unrecoverableError = "The realtime engine has encountered an unrecoverable error. Click OK to reload.";
@ -381,7 +384,8 @@ define(function () {
out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?";
out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?";
out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?";
out.fm_deleteOwnedPads = "Are you sure you want to remove permanently this pad from the server?"; out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?";
out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?";
out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?";
out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder...";
out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page.";
@ -395,7 +399,8 @@ define(function () {
out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_trash = out.updated_0_fm_info_trash;
out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here
out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">find out more</a>). ' + out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">find out more</a>). ' +
'<a href="/register/">Sign up</a> or <a href="/login/">Log in</a> to keep them alive.'; 'They are stored in your browser so clearing history may make them disappear.<br>' +
'<a href="/register/">Sign up</a> or <a href="/login/">Log in</a> to keep them alive.<br>';
out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore."; out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore.";
out.fm_alert_backupUrl = "Backup link for this drive.<br>" + out.fm_alert_backupUrl = "Backup link for this drive.<br>" +
"It is <strong>highly recommended</strong> that you keep it secret.<br>" + "It is <strong>highly recommended</strong> that you keep it secret.<br>" +
@ -417,6 +422,7 @@ define(function () {
"This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server."; "This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server.";
out.fm_padIsOwned = "You are the owner of this pad"; out.fm_padIsOwned = "You are the owner of this pad";
out.fm_padIsOwnedOther = "This pad is owned by another user"; out.fm_padIsOwnedOther = "This pad is owned by another user";
out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}";
// File - Context menu // File - Context menu
out.fc_newfolder = "New folder"; out.fc_newfolder = "New folder";
out.fc_rename = "Rename"; out.fc_rename = "Rename";
@ -472,6 +478,7 @@ define(function () {
out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustAcceptTerms = "You must accept the terms of service.";
out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm.";
out.register_whyRegister = "Why sign up?";
out.register_header = "Welcome to CryptPad"; out.register_header = "Welcome to CryptPad";
out.register_explanation = [ out.register_explanation = [
"<h3>Lets go over a couple things first:</h3>", "<h3>Lets go over a couple things first:</h3>",
@ -495,6 +502,7 @@ define(function () {
out.settings_cat_code = "Code"; out.settings_cat_code = "Code";
out.settings_cat_pad = "Rich text"; out.settings_cat_pad = "Rich text";
out.settings_cat_creation = "New pad"; out.settings_cat_creation = "New pad";
out.settings_cat_subscription = "Subscription";
out.settings_title = "Settings"; out.settings_title = "Settings";
out.settings_save = "Save"; out.settings_save = "Save";
@ -716,6 +724,7 @@ define(function () {
// features.html // features.html
out.features = "Features";
out.features_title = "Features table"; out.features_title = "Features table";
out.features_feature = "Feature"; out.features_feature = "Feature";
out.features_anon = "Anonymous user"; out.features_anon = "Anonymous user";
@ -737,6 +746,7 @@ define(function () {
out.features_f_multiple = "Use on multiple devices"; out.features_f_multiple = "Use on multiple devices";
out.features_f_multiple_notes = "Easy way to access your pads from any device"; out.features_f_multiple_notes = "Easy way to access your pads from any device";
out.features_f_logoutEverywhere = "Log out from other devices"; out.features_f_logoutEverywhere = "Log out from other devices";
out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain
out.features_f_templates = "Use templates"; out.features_f_templates = "Use templates";
out.features_f_templates_notes = "Create templates and create new pads from your templates"; out.features_f_templates_notes = "Create templates and create new pads from your templates";
out.features_f_profile = "Create a profile"; out.features_f_profile = "Create a profile";
@ -749,6 +759,30 @@ define(function () {
out.features_f_storage_anon = "Pads deleted after 3 months"; out.features_f_storage_anon = "Pads deleted after 3 months";
out.features_f_storage_registered = "Free: 50MB<br>Premium: 5GB/20GB/50GB"; out.features_f_storage_registered = "Free: 50MB<br>Premium: 5GB/20GB/50GB";
// faq.html
out.faq_link = "FAQ";
out.faq_title = "Frequently Asked Questions";
out.faq = {};
out.faq.cat1 = {
title: 'Category 1',
q1: {
q: 'What is a pad?',
a: 'A realtime collaborative document...'
},
q2: {
q: 'Question 2?',
a: '42'
}
};
out.faq.cat2 = {
title: 'Category 2',
q1: {
q: 'A new question?',
a: 'The answer'
}
};
// terms.html // terms.html
out.tos_title = "CryptPad Terms of Service"; out.tos_title = "CryptPad Terms of Service";
@ -855,23 +889,24 @@ 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"; 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 // 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_ownedTitle = "Type of pad";
out.creation_ownedTrue = "Owned pad"; out.creation_ownedTrue = "Owned pad";
out.creation_ownedFalse = "Open 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_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_expireTitle = "Life time";
out.creation_expireTrue = "Add a life time"; out.creation_expireTrue = "Add a life time";
out.creation_expireFalse = "Unlimited"; out.creation_expireFalse = "Unlimited";
out.creation_expireHours = "Hours"; out.creation_expireHours = "Hour(s)";
out.creation_expireDays = "Days"; out.creation_expireDays = "Day(s)";
out.creation_expireMonths = "Months"; out.creation_expireMonths = "Month(s)";
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_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it.";
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_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_createTitle = "Create a pad";
out.creation_createFromTemplate = "From template"; out.creation_createFromTemplate = "From template";
out.creation_createFromScratch = "From scratch"; out.creation_createFromScratch = "From scratch";
out.creation_settings = "New Pad settings";
// Properties about creation data // Properties about creation data
out.creation_owners = "Owners"; out.creation_owners = "Owners";
out.creation_ownedByOther = "Owned by another user"; out.creation_ownedByOther = "Owned by another user";
@ -879,7 +914,7 @@ define(function () {
out.creation_expiration = "Expiration time"; out.creation_expiration = "Expiration time";
out.creation_propertiesTitle = "Availability"; out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)"; 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"; out.creation_newPadModalAdvanced = "Display the pad creation screen";
// New share modal // 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();
});
}
}));
});
});
});

@ -1,12 +1,13 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.25.0", "version": "1.26.0",
"dependencies": { "dependencies": {
"chainpad-server": "^2.0.0", "chainpad-server": "^2.0.0",
"express": "~4.10.1", "express": "~4.10.1",
"nthen": "~0.1.0", "nthen": "~0.1.0",
"pull-stream": "^3.6.1", "pull-stream": "^3.6.1",
"replify": "^1.2.0",
"saferphore": "0.0.1", "saferphore": "0.0.1",
"stream-to-pull-stream": "^1.7.2", "stream-to-pull-stream": "^1.7.2",
"tweetnacl": "~0.12.2", "tweetnacl": "~0.12.2",

@ -11,6 +11,8 @@ var Path = require("path");
var Https = require("https"); var Https = require("https");
const Package = require('./package.json'); const Package = require('./package.json');
const Pinned = require('./pinned'); const Pinned = require('./pinned');
const Saferphore = require("saferphore");
const nThen = require("nthen");
var RPC = module.exports; var RPC = module.exports;
@ -355,6 +357,37 @@ var getMultipleFileSize = function (Env, channels, cb) {
}); });
}; };
/* accepts a list, and returns a sublist of channel or file ids which seem
to have been deleted from the server (file size 0)
we might consider that we should only say a file is gone if fs.stat returns
ENOENT, but for now it's simplest to just rely on getFileSize...
*/
var getDeletedPads = function (Env, channels, cb) {
if (!Array.isArray(channels)) { return cb('INVALID_LIST'); }
var L = channels.length;
var sem = Saferphore.create(10);
var absentees = [];
var job = function (channel, wait) {
return function (give) {
getFileSize(Env, channel, wait(give(function (e, size) {
if (e) { return; }
if (size === 0) { absentees.push(channel); }
})));
};
};
nThen(function (w) {
for (var i = 0; i < L; i++) {
sem.take(job(channels[i], w));
}
}).nThen(function () {
cb(void 0, absentees);
});
};
var getTotalSize = function (Env, publicKey, cb) { var getTotalSize = function (Env, publicKey, cb) {
var bytes = 0; var bytes = 0;
return void getChannelList(Env, publicKey, function (channels) { return void getChannelList(Env, publicKey, function (channels) {
@ -395,8 +428,7 @@ var getHash = function (Env, publicKey, cb) {
// The limits object contains storage limits for all the publicKey that have paid // 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 // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var limits = {}; var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) {
var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) {
if (config.adminEmail === false) { if (config.adminEmail === false) {
if (config.allowSubscriptions === false) { return; } if (config.allowSubscriptions === false) { return; }
throw new Error("allowSubscriptions must be false if adminEmail is false"); throw new Error("allowSubscriptions must be false if adminEmail is false");
@ -461,15 +493,15 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
response.on('end', function () { response.on('end', function () {
try { try {
var json = JSON.parse(str); var json = JSON.parse(str);
limits = json; Env.limits = json;
Object.keys(customLimits).forEach(function (k) { Object.keys(customLimits).forEach(function (k) {
if (!isLimit(customLimits[k])) { return; } if (!isLimit(customLimits[k])) { return; }
limits[k] = customLimits[k]; Env.limits[k] = customLimits[k];
}); });
var l; var l;
if (userId) { if (userId) {
var limit = limits[userId]; var limit = Env.limits[userId];
l = limit && typeof limit.limit === "number" ? l = limit && typeof limit.limit === "number" ?
[limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; [limit.limit, limit.plan, limit.note] : [defaultLimit, '', ''];
} }
@ -490,7 +522,7 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
var getLimit = function (Env, publicKey, cb) { var getLimit = function (Env, publicKey, cb) {
var unescapedKey = unescapeKeyCharacters(publicKey); var unescapedKey = unescapeKeyCharacters(publicKey);
var limit = limits[unescapedKey]; var limit = Env.limits[unescapedKey];
var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'? var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'?
Env.defaultStorageLimit: DEFAULT_LIMIT; Env.defaultStorageLimit: DEFAULT_LIMIT;
@ -1005,7 +1037,8 @@ var isUnauthenticatedCall = function (call) {
'GET_MULTIPLE_FILE_SIZE', 'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED', 'IS_CHANNEL_PINNED',
'IS_NEW_CHANNEL', 'IS_NEW_CHANNEL',
'GET_HISTORY_OFFSET' 'GET_HISTORY_OFFSET',
'GET_DELETED_PADS',
].indexOf(call) !== -1; ].indexOf(call) !== -1;
}; };
@ -1063,7 +1096,11 @@ type NetfluxWebsocketSrvContext_t = {
)=>void )=>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... // load pin-store...
console.log('loading rpc module...'); console.log('loading rpc module...');
@ -1081,8 +1118,10 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
msgStore: (undefined /*:any*/), msgStore: (undefined /*:any*/),
pinStore: (undefined /*:any*/), pinStore: (undefined /*:any*/),
pinnedPads: {}, pinnedPads: {},
evPinnedPadsReady: mkEvent(true) evPinnedPadsReady: mkEvent(true),
limits: {}
}; };
debuggable('rpc_env', Env);
var Sessions = Env.Sessions; var Sessions = Env.Sessions;
var paths = Env.paths; var paths = Env.paths;
@ -1128,13 +1167,21 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
} }
respond(e, [null, dict, null]); respond(e, [null, dict, null]);
}); });
case 'GET_DELETED_PADS':
return void getDeletedPads(Env, msg[1], function (e, list) {
if (e) {
WARN(e, msg[1]);
return respond(e);
}
respond(e, [null, list, null]);
});
case 'IS_CHANNEL_PINNED': case 'IS_CHANNEL_PINNED':
return void isChannelPinned(Env, msg[1], function (isPinned) { return void isChannelPinned(Env, msg[1], function (isPinned) {
respond(null, [null, isPinned, null]); respond(null, [null, isPinned, null]);
}); });
case 'IS_NEW_CHANNEL': case 'IS_NEW_CHANNEL':
return void isNewChannel(Env, msg[1], function (e, isNew) { return void isNewChannel(Env, msg[1], function (e, isNew) {
respond(null, [null, isNew, null]); respond(e, [null, isNew, null]);
}); });
default: default:
console.error("unsupported!"); console.error("unsupported!");
@ -1264,7 +1311,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
Respond(e, size); Respond(e, size);
}); });
case 'UPDATE_LIMITS': case 'UPDATE_LIMITS':
return void updateLimits(config, safeKey, function (e, limit) { return void updateLimits(Env, config, safeKey, function (e, limit) {
if (e) { if (e) {
WARN(e, limit); WARN(e, limit);
return void Respond(e); return void Respond(e);
@ -1299,9 +1346,9 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
}); });
case 'REMOVE_OWNED_CHANNEL': 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); } if (e) { return void Respond(e); }
Respond(void 0, response); Respond(void 0, "OK");
}); });
// restricted to privileged users... // restricted to privileged users...
case 'UPLOAD': case 'UPLOAD':
@ -1376,7 +1423,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/)
}; };
var updateLimitDaily = function () { var updateLimitDaily = function () {
updateLimits(config, undefined, function (e) { updateLimits(Env, config, undefined, function (e) {
if (e) { if (e) {
WARN('limitUpdate', e); WARN('limitUpdate', e);
} }

@ -10,6 +10,7 @@ var NetfluxSrv = require('./node_modules/chainpad-server/NetfluxWebsocketSrv');
var Package = require('./package.json'); var Package = require('./package.json');
var Path = require("path"); var Path = require("path");
var OOServer = require('./ooserver.js'); var OOServer = require('./ooserver.js');
var nThen = require("nthen");
var config; var config;
try { try {
@ -21,10 +22,25 @@ try {
var websocketPort = config.websocketPort || config.httpPort; var websocketPort = config.websocketPort || config.httpPort;
var useSecureWebsockets = config.useSecureWebsockets || false; 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 // support multiple storage back ends
var Storage = require(config.storage||'./storage/file'); var Storage = require(config.storage||'./storage/file');
var app = Express(); var app = debuggable('app', Express());
var httpsOpts; var httpsOpts;
@ -102,6 +118,7 @@ Fs.exists(__dirname + "/customize", function (e) {
var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'contact']; var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'contact'];
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static(__dirname + '/customize'));
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
@ -251,32 +268,39 @@ if (config.httpSafePort) {
var wsConfig = { server: httpServer }; var wsConfig = { server: httpServer };
var createSocketServer = function (err, rpc) { var rpc;
if(!config.useExternalWebsocket) {
if (websocketPort !== config.httpPort) {
console.log("setting up a new websocket server");
wsConfig = { port: websocketPort};
}
var wsSrv = new WebSocketServer(wsConfig);
Storage.create(config, function (store) {
NetfluxSrv.run(store, wsSrv, config, rpc);
});
}
};
var loadRPC = function (cb) { 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;
}));
}).nThen(function (w) {
config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc;
if (typeof(config.rpc) !== 'string') { return; }
if (typeof(config.rpc) === 'string') { // load pin store...
// load pin store... var Rpc = require(config.rpc);
var Rpc = require(config.rpc); Rpc.create(config, debuggable, w(function (e, _rpc) {
Rpc.create(config, function (e, rpc) { if (e) {
if (e) { throw e; } w.abort();
cb(void 0, rpc); throw e;
}); }
} else { rpc = _rpc;
cb(); }));
}).nThen(function () {
if(config.useExternalWebsocket) { return; }
if (websocketPort !== config.httpPort) {
console.log("setting up a new websocket server");
wsConfig = { port: websocketPort};
} }
}; var wsSrv = new WebSocketServer(wsConfig);
Storage.create(config, function (store) {
NetfluxSrv.run(store, wsSrv, config, rpc);
});
});
loadRPC(createSocketServer); if (config.debugReplName) {
require('replify')({ name: config.debugReplName, app: debuggableStore });
}

@ -9,7 +9,7 @@ const Pull = require('pull-stream');
const isValidChannelId = function (id) { const isValidChannelId = function (id) {
return typeof(id) === 'string' && return typeof(id) === 'string' &&
[32, 48].indexOf(id.length) > -1 && id.length >= 32 && id.length < 50 &&
/^[a-zA-Z0-9=+-]*$/.test(id); /^[a-zA-Z0-9=+-]*$/.test(id);
}; };
@ -418,6 +418,7 @@ module.exports.create = function (
openFileLimit: conf.openFileLimit || 2048, openFileLimit: conf.openFileLimit || 2048,
}; };
// 0x1ff -> 777 // 0x1ff -> 777
var it;
Fs.mkdir(env.root, 0x1ff, function (err) { Fs.mkdir(env.root, 0x1ff, function (err) {
if (err && err.code !== 'EEXIST') { if (err && err.code !== 'EEXIST') {
// TODO: somehow return a nice error // TODO: somehow return a nice error
@ -465,9 +466,12 @@ module.exports.create = function (
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
clearChannel(env, channelName, cb); clearChannel(env, channelName, cb);
}, },
shutdown: function () {
clearInterval(it);
}
}); });
}); });
setInterval(function () { it = setInterval(function () {
flushUnusedChannels(env, function () { }); flushUnusedChannels(env, function () { });
}, 5000); }, 5000);
}; };

@ -0,0 +1,94 @@
var Fs = require("fs");
var Path = require("path");
var nacl = require("tweetnacl");
var nThen = require("nthen");
var Tasks = module.exports;
var encode = function (time, command, args) {
if (typeof(time) !== 'number') { return null; }
if (typeof(command) !== 'string') { return null; }
if (!Array.isArray(args)) { return [time, command]; }
return [time, command].concat(args);
};
var randomId = function () {
var bytes = Array.prototype.slice.call(nacl.randomBytes(16));
return bytes.map(function (b) {
var n = Number(b & 0xff).toString(16);
return n.length === 1? '0' + n: n;
}).join('');
};
var mkPath = function (env, id) {
return Path.join(env.root, id.slice(0, 2), id) + '.ndjson';
};
var getFreeId = function (env, cb, tries) {
if (tries > 5) { return void cb('ETOOMANYTRIES'); }
// generate a unique id
var id = randomId();
// derive a path from that id
var path = mkPath(env, id);
Fs.stat(path, function (err) {
if (err && err.code === "ENOENT") {
cb(void 0, id);
} else {
getFreeId(env, cb);
}
});
};
var write = function (env, task, cb) {
var str = JSON.stringify(task) + '\n';
var id = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(str))).replace(/\//g, '-');
var path = mkPath(env, id);
nThen(function (w) {
// check if the file already exists...
Fs.stat(path, w(function (err) {
if (err && err.code === 'ENOENT') { return; }
w.abort(); cb();
}));
}).nThen(function (w) {
// create the parent directory if it does not exist
var dir = id.slice(0, 2);
var dirpath = Path.join(env.root, dir);
Fs.mkdir(dirpath, 0x1ff, w(function (err) {
if (err && err.code !== 'EEXIST') {
return void cb(err);
}
}));
}).nThen(function () {
// write the file to the path
Fs.writeFile(mkPath(env, id), str, function (e) {
if (e) { return void cb(e); }
cb();
});
});
};
Tasks.create = function (config, cb) {
var env = {
root: config.taskPath || './tasks',
};
// make sure the path exists...
Fs.mkdir(env.root, 0x1ff, function (err) {
if (err && err.code !== 'EEXIST') {
throw err;
}
cb(void 0, {
write: function (time, command, args, cb) {
var task = encode(time, command, args);
write(env, task, cb);
},
});
});
};

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

@ -42,6 +42,11 @@ define(function() {
'#800080', // purple '#800080', // purple
]; ];
// Background color in the apps with centered content:
// - file app in view mode
// - rich text app when editor's width reduced in settings
config.appBackgroundColor = '#666';
// Set enableTemplates to false to remove the button allowing users to save a pad as a template // Set enableTemplates to false to remove the button allowing users to save a pad as a template
// and remove the template category in CryptDrive // and remove the template category in CryptDrive
config.enableTemplates = true; config.enableTemplates = true;

@ -37,6 +37,15 @@ define([
window.alert("CryptPad needs localStorage to work, try a different browser"); window.alert("CryptPad needs localStorage to work, try a different browser");
}; };
window.onerror = function (e) {
if (/requirejs\.org/.test(e)) {
console.log();
console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers");
return void console.log();
}
throw e;
};
try { try {
var test_key = 'localStorage_test'; var test_key = 'localStorage_test';
var testval = Math.random().toString(); var testval = Math.random().toString();

@ -6,7 +6,7 @@ define([
], function (Util, Messages, Crypto) { ], function (Util, Messages, Crypto) {
var Nacl = window.nacl; var Nacl = window.nacl;
var Hash = {}; var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex; var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64; var hexToBase64 = Util.hexToBase64;
@ -176,7 +176,7 @@ Version 1
secret.keys = Crypto.createEditCryptor(); secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr; secret.key = Crypto.createEditCryptor().editKeyStr;
}; };
if (!secretHash && !/#/.test(window.location.href)) { if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate(); generate();
return secret; return secret;
} else { } else {
@ -300,6 +300,8 @@ Version 1
var rHref = href || getRelativeHref(window.location.href); var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref); var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; } if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
var stronger; var stronger;
Object.keys(recents).some(function (id) { Object.keys(recents).some(function (id) {
var pad = recents[id]; var pad = recents[id];

@ -111,12 +111,14 @@ define([
return input; return input;
}; };
dialog.okButton = function (content) { dialog.okButton = function (content, classString) {
return h('button.ok.primary', { tabindex: '2', }, content || Messages.okButton); var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary';
return h(sel, { tabindex: '2', }, content || Messages.okButton);
}; };
dialog.cancelButton = function (content) { dialog.cancelButton = function (content, classString) {
return h('button.cancel', { tabindex: '1'}, content || Messages.cancelButton); var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel';
return h(sel, { tabindex: '1'}, content || Messages.cancelButton);
}; };
dialog.message = function (text) { dialog.message = function (text) {
@ -315,11 +317,11 @@ define([
message = dialog.message(msg); message = dialog.message(msg);
} }
var close = Util.once(function (el) { var close = function (el) {
var $el = $(el).fadeOut(150, function () { var $el = $(el).fadeOut(150, function () {
$el.remove(); $el.detach();
}); });
}); };
var navs = []; var navs = [];
opt.buttons.forEach(function (b) { opt.buttons.forEach(function (b) {
@ -327,7 +329,7 @@ define([
var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name); var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name);
$(button).click(function () { $(button).click(function () {
b.onClick(); b.onClick();
close($(this).parents('.alertify').first()); close($(button).parents('.alertify').first());
}); });
if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); } if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); }
navs.push(button); navs.push(button);
@ -464,8 +466,8 @@ define([
message = dialog.message(msg); message = dialog.message(msg);
} }
var ok = dialog.okButton(opt.ok); var ok = dialog.okButton(opt.ok, opt.okClass);
var cancel = dialog.cancelButton(opt.cancel); var cancel = dialog.cancelButton(opt.cancel, opt.cancelClass);
var frame = dialog.frame([ var frame = dialog.frame([
message, message,
@ -551,6 +553,7 @@ define([
var $loading, $container; var $loading, $container;
if ($('#' + LOADING).length) { if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show(); $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden'); $loading.removeClass('cp-loading-hidden');
if (loadingText) { if (loadingText) {
$('#' + LOADING).find('p').text(loadingText); $('#' + LOADING).find('p').text(loadingText);
@ -598,11 +601,20 @@ define([
}, 3750); }, 3750);
// jquery.fadeout can get stuck // jquery.fadeout can get stuck
}; };
UI.errorLoadingScreen = function (error, transparent) { UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen({hideTips: true}); } if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
$('.cp-loading-spinner-container').hide(); $('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); } if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error); $('#' + 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"}); var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});

@ -375,8 +375,8 @@ define([
} }
if (val.embed) { $(link).find('#cp-share-embed').attr('checked', true); } if (val.embed) { $(link).find('#cp-share-embed').attr('checked', true); }
if (val.present) { $(link).find('#cp-share-present').attr('checked', true); } if (val.present) { $(link).find('#cp-share-present').attr('checked', true); }
UI.openCustomModal(UI.dialog.tabs(tabs));
}); });
return tabs;
}; };
UIElements.createFileShareModal = function (config) { UIElements.createFileShareModal = function (config) {
var origin = config.origin; var origin = config.origin;
@ -451,7 +451,7 @@ define([
pathname: pathname pathname: pathname
}); });
} }
UI.openCustomModal(UI.dialog.tabs(tabs)); return tabs;
}; };
UIElements.createButton = function (common, type, rightside, data, callback) { UIElements.createButton = function (common, type, rightside, data, callback) {
@ -1655,8 +1655,6 @@ define([
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getMetadataLazy().type; var type = metadataMgr.getMetadataLazy().type;
// XXX check text for pad creation screen + translate it in French
var $body = $('body'); var $body = $('body');
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body); var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
var $creation = $('<div>', { id: 'cp-creation' }).appendTo($creationContainer); var $creation = $('<div>', { id: 'cp-creation' }).appendTo($creationContainer);
@ -1687,7 +1685,10 @@ define([
Messages.creation_ownedTitle, Messages.creation_ownedTitle,
createHelper(Messages.creation_owned1 + '\n' + Messages.creation_owned2) 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', { h('input#cp-creation-owned-true.cp-creation-owned-value', {
type: 'radio', type: 'radio',
name: 'cp-creation-owned', name: 'cp-creation-owned',
@ -1715,7 +1716,10 @@ define([
Messages.creation_expireTitle, Messages.creation_expireTitle,
createHelper(Messages.creation_expire1, Messages.creation_expire2) 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', { h('input#cp-creation-expire-false.cp-creation-expire-value', {
type: 'radio', type: 'radio',
name: 'cp-creation-expire', name: 'cp-creation-expire',
@ -1816,6 +1820,32 @@ define([
$button.click(function () { $button.click(function () {
create(); create();
}); });
// Settings button
var origin = common.getMetadataMgr().getPrivateData().origin;
$(h('div.cp-creation-settings', h('a', {
href: origin + '/settings/#creation',
target: '_blank'
}, 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; return UIElements;

@ -1,6 +1,6 @@
(function (window) { (function (window) {
define([], function () { define([], function () {
var Util = {}; var Util = window.CryptPad_Util = {};
// If once is true, after the event has been fired, any further handlers which are // If once is true, after the event has been fired, any further handlers which are
// registered will fire immediately, and this type of event cannot be fired twice. // registered will fire immediately, and this type of event cannot be fired twice.

@ -195,6 +195,16 @@ define([
common.clearOwnedChannel = function (channel, cb) { common.clearOwnedChannel = function (channel, cb) {
postMessage("CLEAR_OWNED_CHANNEL", channel, cb); postMessage("CLEAR_OWNED_CHANNEL", channel, cb);
}; };
common.removeOwnedChannel = function (channel, cb) {
postMessage("REMOVE_OWNED_CHANNEL", channel, cb);
};
common.getDeletedPads = function (cb) {
postMessage("GET_DELETED_PADS", null, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
common.uploadComplete = function (cb) { common.uploadComplete = function (cb) {
postMessage("UPLOAD_COMPLETE", null, function (obj) { postMessage("UPLOAD_COMPLETE", null, function (obj) {
@ -528,6 +538,7 @@ define([
pad.onJoinEvent = Util.mkEvent(); pad.onJoinEvent = Util.mkEvent();
pad.onLeaveEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent();
pad.onDisconnectEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent();
common.getFullHistory = function (data, cb) { common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb); postMessage("GET_FULL_HISTORY", data, cb);
@ -550,6 +561,11 @@ define([
return void cb(null, hashes); return void cb(null, hashes);
} }
if (hashes.editHash) {
// no need to find stronger if we already have edit hash
return void cb(null, hashes);
}
postMessage("GET_STRONGER_HASH", { postMessage("GET_STRONGER_HASH", {
href: window.location.href href: window.location.href
}, function (hash) { }, function (hash) {
@ -664,6 +680,9 @@ define([
case 'PAD_DISCONNECT': { case 'PAD_DISCONNECT': {
common.padRpc.onDisconnectEvent.fire(data); break; common.padRpc.onDisconnectEvent.fire(data); break;
} }
case 'PAD_ERROR': {
common.padRpc.onErrorEvent.fire(data); break;
}
// Drive // Drive
case 'DRIVE_LOG': { case 'DRIVE_LOG': {
common.drive.onLog.fire(data); break; common.drive.onLog.fire(data); break;

@ -115,6 +115,12 @@ define(['json.sortify'], function (Sortify) {
if (!meta.user) { return; } if (!meta.user) { return; }
change(true); 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({ return Object.freeze({
updateMetadata: function (m) { updateMetadata: function (m) {

@ -94,9 +94,27 @@ define([
return list; return list;
}; };
var getCanonicalChannelList = function () { var getExpirableChannelList = function () {
return Util.deduplicateString(getUserChannelList()).sort(); var list = [];
store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) {
var data = store.userObject.getFileData(id);
var edPublic = store.proxy.edPublic;
// Push channels owned by someone else or channel that should have expired
// because of the expiration time
if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) ||
(data.expire && data.expire < (+new Date()))) {
list.push(Hash.hrefToHexChannelId(data.href));
}
});
return list;
};
var getCanonicalChannelList = function (expirable) {
var list = expirable ? getExpirableChannelList() : getUserChannelList();
return Util.deduplicateString(list).sort();
}; };
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
/////////////////////// RPC ////////////////////////////////////// /////////////////////// RPC //////////////////////////////////////
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -172,6 +190,34 @@ define([
}); });
}; };
Store.removeOwnedChannel = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.removeOwnedChannel(data, function (err) {
cb({error:err});
});
};
var arePinsSynced = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList(false);
var local = Hash.hashChannelList(list);
store.rpc.getServerHash(function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash === local);
});
};
var resetPins = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList(false);
store.rpc.reset(list, function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash);
});
};
Store.uploadComplete = function (data, cb) { Store.uploadComplete = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadComplete(function (err, res) { store.rpc.uploadComplete(function (err, res) {
@ -196,27 +242,6 @@ define([
}); });
}; };
var arePinsSynced = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList();
var local = Hash.hashChannelList(list);
store.rpc.getServerHash(function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash === local);
});
};
var resetPins = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList();
store.rpc.reset(list, function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash);
});
};
Store.uploadChunk = function (data, cb) { Store.uploadChunk = function (data, cb) {
store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
cb({ cb({
@ -309,6 +334,23 @@ define([
}); });
}; };
Store.getDeletedPads = function (data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var list = getCanonicalChannelList(true);
if (!Array.isArray(list)) {
return void cb({error: 'INVALID_FILE_LIST'});
}
store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) {
if (e) { return void cb({error: e}); }
if (res && res.length && Array.isArray(res[0])) {
cb(res[0]);
} else {
cb({error: 'UNEXPECTED_RESPONSE'});
}
});
};
Store.initAnonRpc = function (data, cb) { Store.initAnonRpc = function (data, cb) {
require([ require([
'/common/rpc.js', '/common/rpc.js',
@ -321,8 +363,6 @@ define([
}); });
}; };
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
/////////////////////// Store //////////////////////////////////// /////////////////////// Store ////////////////////////////////////
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -579,6 +619,8 @@ define([
contains = true; contains = true;
pad.atime = +new Date(); pad.atime = +new Date();
pad.title = title; pad.title = title;
pad.owners = owners;
pad.expire = expire;
// If the href is different, it means we have a stronger one // If the href is different, it means we have a stronger one
if (href !== pad.href) { isStronger = true; } if (href !== pad.href) { isStronger = true; }
@ -773,6 +815,9 @@ define([
onDisconnect: function () { onDisconnect: function () {
postMessage("PAD_DISCONNECT"); postMessage("PAD_DISCONNECT");
}, // post EV_PAD_DISCONNECT }, // post EV_PAD_DISCONNECT
onError: function (err) {
postMessage("PAD_ERROR", err);
}, // post EV_PAD_ERROR
channel: data.channel, channel: data.channel,
validateKey: data.validateKey, validateKey: data.validateKey,
owners: data.owners, owners: data.owners,
@ -852,7 +897,7 @@ define([
case 'addFolder': case 'addFolder':
store.userObject.addFolder(data.path, data.name, cb); break; store.userObject.addFolder(data.path, data.name, cb); break;
case 'delete': case 'delete':
store.userObject.delete(data.paths, cb, data.nocheck); break; store.userObject.delete(data.paths, cb, data.nocheck, data.isOwnPadRemoved); break;
case 'emptyTrash': case 'emptyTrash':
store.userObject.emptyTrash(cb); break; store.userObject.emptyTrash(cb); break;
case 'rename': case 'rename':
@ -871,6 +916,8 @@ define([
var userObject = store.userObject = UserObject.init(proxy.drive, { var userObject = store.userObject = UserObject.init(proxy.drive, {
pinPads: Store.pinPads, pinPads: Store.pinPads,
unpinPads: Store.unpinPads, unpinPads: Store.unpinPads,
removeOwnedChannel: Store.removeOwnedChannel,
edPublic: store.proxy.edPublic,
loggedIn: store.loggedIn, loggedIn: store.loggedIn,
log: function (msg) { log: function (msg) {
postMessage("DRIVE_LOG", msg); postMessage("DRIVE_LOG", msg);

@ -33,6 +33,7 @@ define([], function () {
var onLeave = conf.onLeave; var onLeave = conf.onLeave;
var onReady = conf.onReady; var onReady = conf.onReady;
var onDisconnect = conf.onDisconnect; var onDisconnect = conf.onDisconnect;
var onError = conf.onError;
var owners = conf.owners; var owners = conf.owners;
var password = conf.password; var password = conf.password;
var expire = conf.expire; var expire = conf.expire;
@ -44,6 +45,17 @@ define([], function () {
var messageFromOuter = 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) { var onRdy = function (padData) {
// Trigger onReady only if not ready yet. This is important because the history keeper sends a direct // 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. // message through "network" when it is synced, and it triggers onReady for each channel joined.
@ -57,27 +69,11 @@ define([], function () {
// shim between chainpad and netflux // shim between chainpad and netflux
var msgIn = function (peerId, msg) { var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, ''); return msg.replace(/^cp\|/, '');
/*try {
var decryptedMsg = Crypto.decrypt(msg, validateKey);
return decryptedMsg;
} catch (err) {
console.error(err);
return msg;
}*/
}; };
var msgOut = function (msg) { var msgOut = function (msg) {
if (readOnly) { return; } if (readOnly) { return; }
return msg; return msg;
/*try {
var cmsg = Crypto.encrypt(msg);
if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; }
return cmsg;
} catch (err) {
console.log(msg);
throw err;
}*/
}; };
var onMsg = function(peer, msg, wc, network, direct) { var onMsg = function(peer, msg, wc, network, direct) {
@ -93,7 +89,9 @@ define([], function () {
if (parsed.channel === wc.id && !validateKey) { if (parsed.channel === wc.id && !validateKey) {
validateKey = parsed.validateKey; validateKey = parsed.validateKey;
} }
padData = parsed; if (parsed.channel === wc.id) {
padData = parsed;
}
// We have to return even if it is not the current channel: // We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here // we don't want to continue with other channels messages here
return; return;
@ -110,11 +108,17 @@ define([], function () {
if (peer === hk) { if (peer === hk) {
// if the peer is the 'history keeper', extract their message // if the peer is the 'history keeper', extract their message
var parsed1 = JSON.parse(msg); 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]; msg = parsed1[4];
// Check that this is a message for our channel // Check that this is a message for our channel
if (parsed1[3] !== wc.id) { return; } if (parsed1[3] !== wc.id) { return; }
} }
lastKnownHash = msg.slice(0,64); lastKnownHash = msg.slice(0,64);
var message = msgIn(peer, msg); var message = msgIn(peer, msg);
@ -191,7 +195,12 @@ define([], function () {
}; };
var msg = ['GET_HISTORY', wc.id, cfg]; var msg = ['GET_HISTORY', wc.id, cfg];
// Add the validateKey if we are the channel creator and we have a validateKey // 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 { } else {
onRdy(); onRdy();
} }
@ -218,8 +227,8 @@ define([], function () {
// join the netflux network, promise to handle opening of the channel // join the netflux network, promise to handle opening of the channel
network.join(channel || null).then(function(wc) { network.join(channel || null).then(function(wc) {
onOpen(wc, network, firstConnection); onOpen(wc, network, firstConnection);
}, function(error) { }, function(err) {
console.error(error); console.error(err);
}); });
}; };

@ -31,6 +31,9 @@ define([
case 'CLEAR_OWNED_CHANNEL': { case 'CLEAR_OWNED_CHANNEL': {
Store.clearOwnedChannel(data, cb); break; Store.clearOwnedChannel(data, cb); break;
} }
case 'REMOVE_OWNED_CHANNEL': {
Store.removeOwnedChannel(data, cb); break;
}
case 'UPLOAD_CHUNK': { case 'UPLOAD_CHUNK': {
Store.uploadChunk(data, cb); break; Store.uploadChunk(data, cb); break;
} }
@ -49,6 +52,9 @@ define([
case 'UNPIN_PADS': { case 'UNPIN_PADS': {
Store.unpinPads(data, cb); break; Store.unpinPads(data, cb); break;
} }
case 'GET_DELETED_PADS': {
Store.getDeletedPads(data, cb); break;
}
case 'GET_PINNED_USAGE': { case 'GET_PINNED_USAGE': {
Store.getPinnedUsage(data, cb); break; Store.getPinnedUsage(data, cb); break;
} }

@ -3,8 +3,9 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-feedback.js',
'/customize/messages.js' '/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Messages) { ], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) {
var module = {}; var module = {};
var clone = function (o) { var clone = function (o) {
@ -17,8 +18,12 @@ define([
console.error("unpinPads was not provided"); console.error("unpinPads was not provided");
}; };
var pinPads = config.pinPads; var pinPads = config.pinPads;
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn; var loggedIn = config.loggedIn;
var workgroup = config.workgroup; var workgroup = config.workgroup;
var edPublic = config.edPublic;
var ROOT = exp.ROOT; var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA; var FILES_DATA = exp.FILES_DATA;
@ -81,8 +86,11 @@ define([
delete files[FILES_DATA][id]; delete files[FILES_DATA][id];
}; };
exp.checkDeletedFiles = function () { // Find files in FILES_DATA that are not anymore in the drive, and remove them from
// Nothing in OLD_FILES_DATA for workgroups // 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; } if (workgroup || (!loggedIn && !config.testMode)) { return; }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
@ -90,9 +98,20 @@ define([
exp.getFiles([FILES_DATA]).forEach(function (id) { exp.getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) { if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id); var fd = exp.getFileData(id);
if (fd && fd.href) { var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
toClean.push(Hash.hrefToHexChannelId(fd.href)); // If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
console.error(obj.error);
// RPC may not be responding
// Send a report that can be handled manually
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
}
});
} }
if (channelId) { toClean.push(channelId); }
spliceFileData(id); spliceFileData(id);
} }
}); });
@ -114,7 +133,7 @@ define([
files[TRASH][obj.name].splice(idx, 1); 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 hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); }); var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); }); var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
@ -170,7 +189,7 @@ define([
// In some cases, we want to remove pads from a location without removing them from // In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref) // OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(); } if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
}; };
// Move // Move

@ -145,7 +145,22 @@ define([
if (response && response.length) { if (response && response.length) {
cb(void 0, response[0]); cb(void 0, response[0]);
} else { } else {
cb('INVALID_RESPONSE');
}
});
};
exp.removeOwnedChannel = function (channel, cb) {
if (typeof(channel) !== 'string' || channel.length !== 32) {
// can't use this on files because files can't be owned...
return void cb('INVALID_ARGUMENTS');
}
rpc.send('REMOVE_OWNED_CHANNEL', channel, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb(); cb();
} else {
cb('INVALID_RESPONSE');
} }
}); });
}; };

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

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

@ -36,5 +36,14 @@ define([
// This test is completed in common-interface.js // This test is completed in common-interface.js
Test(function (t) { Test.__ASYNC_BLOCKER__ = t; }); Test(function (t) { Test.__ASYNC_BLOCKER__ = t; });
window.onerror = function (e) {
if (/requirejs\.org/.test(e)) {
console.log();
console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers");
return void console.log();
}
throw e;
};
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]); require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
}); });

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

@ -102,6 +102,10 @@ define([], function () {
sframeChan.event('EV_RT_DISCONNECT'); 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 // join the netflux network, promise to handle opening of the channel
padRpc.joinPad({ padRpc.joinPad({
channel: channel || null, channel: channel || null,

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

@ -132,10 +132,12 @@ define([
// Check if the pad exists on server // Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; } if (!window.location.hash) { isNewFile = true; return; }
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { if (realtime) {
if (e) { return console.error(e); } Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
isNewFile = Boolean(isNew); if (e) { return console.error(e); }
})); isNewFile = Boolean(isNew);
}));
}
}).nThen(function () { }).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr; var readOnly = secret.keys && !secret.keys.editKeyStr;
var isNewHash = true; var isNewHash = true;
@ -501,6 +503,9 @@ define([
sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) {
Cryptpad.clearOwnedChannel(channel, cb); Cryptpad.clearOwnedChannel(channel, cb);
}); });
sframeChan.on('Q_REMOVE_OWNED_CHANNEL', function (channel, cb) {
Cryptpad.removeOwnedChannel(channel, cb);
});
if (cfg.addRpc) { if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils); cfg.addRpc(sframeChan, Cryptpad, Utils);

@ -92,6 +92,7 @@ define([
funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar); funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar);
funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen); funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen);
funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal);
funcs.onServerError = callWithCommon(UIElements.onServerError);
// Thumb // Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail); funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
@ -392,15 +393,12 @@ define([
UI.log(data.logText); UI.log(data.logText);
}); });
ctx.metadataMgr.onChange(function () {
try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback);
} catch (e) { Feedback.init(false); }
});
ctx.metadataMgr.onReady(waitFor()); ctx.metadataMgr.onReady(waitFor());
}).nThen(function () { }).nThen(function () {
try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback);
} catch (e) { Feedback.init(false); }
ctx.sframeChan.ready(); ctx.sframeChan.ready();
cb(funcs); cb(funcs);
}); });

@ -31,6 +31,8 @@ define({
'EV_RT_CONNECT': true, 'EV_RT_CONNECT': true,
// Called after the history is finished synchronizing, no arguments. // Called after the history is finished synchronizing, no arguments.
'EV_RT_READY': true, '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. // Called from both outside and inside, argument is a (string) chainpad message.
'Q_RT_MESSAGE': true, 'Q_RT_MESSAGE': true,
@ -201,12 +203,17 @@ define({
// Inner drive needs to send command and receive updates from the async store // Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true, 'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true, 'Q_DRIVE_GETOBJECT': true,
// Get the pads deleted from the server by other users to remove them from the drive
'Q_DRIVE_GETDELETED': true,
// Store's userObject need to send log messages to inner to display them in the UI // Store's userObject need to send log messages to inner to display them in the UI
'EV_DRIVE_LOG': true, 'EV_DRIVE_LOG': true,
// Refresh the drive when the drive has changed ('change' or 'remove' events) // Refresh the drive when the drive has changed ('change' or 'remove' events)
'EV_DRIVE_CHANGE': true, 'EV_DRIVE_CHANGE': true,
'EV_DRIVE_REMOVE': true, 'EV_DRIVE_REMOVE': true,
// Remove an owned pad from the server
'Q_REMOVE_OWNED_CHANNEL': true,
// Notifications about connection and disconnection from the network // Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true, 'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true, 'EV_NETWORK_RECONNECT': true,

@ -444,13 +444,14 @@ define([
'class': 'fa fa-share-alt cp-toolbar-share-button', 'class': 'fa fa-share-alt cp-toolbar-share-button',
title: Messages.shareButton title: Messages.shareButton
}); });
var modal = UIElements.createShareModal({
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
});
$shareBlock.click(function () { $shareBlock.click(function () {
UIElements.createShareModal({ UI.openCustomModal(UI.dialog.tabs(modal));
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
});
}); });
toolbar.$leftside.append($shareBlock); toolbar.$leftside.append($shareBlock);
@ -472,13 +473,14 @@ define([
'class': 'fa fa-share-alt cp-toolbar-share-button', 'class': 'fa fa-share-alt cp-toolbar-share-button',
title: Messages.shareButton title: Messages.shareButton
}); });
var modal = UIElements.createFileShareModal({
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
});
$shareBlock.click(function () { $shareBlock.click(function () {
UIElements.createFileShareModal({ UI.openCustomModal(UI.dialog.tabs(modal));
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
});
}); });
toolbar.$leftside.append($shareBlock); toolbar.$leftside.append($shareBlock);
@ -707,6 +709,7 @@ define([
typing = 1; typing = 1;
$spin.text(Messages.typing); $spin.text(Messages.typing);
$spin.interval = window.setInterval(function () { $spin.interval = window.setInterval(function () {
if (toolbar.isErrorState) { return; }
var dots = Array(typing+1).join('.'); var dots = Array(typing+1).join('.');
$spin.text(Messages.typing + dots); $spin.text(Messages.typing + dots);
typing++; typing++;
@ -716,6 +719,7 @@ define([
var onSynced = function () { var onSynced = function () {
if ($spin.timeout) { clearTimeout($spin.timeout); } if ($spin.timeout) { clearTimeout($spin.timeout); }
$spin.timeout = setTimeout(function () { $spin.timeout = setTimeout(function () {
if (toolbar.isErrorState) { return; }
window.clearInterval($spin.interval); window.clearInterval($spin.interval);
typing = -1; typing = -1;
$spin.text(Messages.saved); $spin.text(Messages.saved);
@ -1092,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 // On log out, remove permanently the realtime elements of the toolbar
Common.onLogout(function () { Common.onLogout(function () {
failed(); failed();

@ -380,6 +380,18 @@ define([
var trashpaths = _findFileInTrash([TRASH], file); var trashpaths = _findFileInTrash([TRASH], file);
return rootpaths.concat(templatepaths, trashpaths); return rootpaths.concat(templatepaths, trashpaths);
}; };
// Get drive ids of files from their channel ids
exp.findChannels = function (channels) {
var allFilesList = files[FILES_DATA];
var channels64 = channels.slice().map(Util.hexToBase64);
return getFiles([FILES_DATA]).filter(function (k) {
var data = allFilesList[k];
var parsed = Hash.parsePadUrl(data.href);
return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1;
});
};
exp.search = function (value) { exp.search = function (value) {
if (typeof(value) !== "string") { return []; } if (typeof(value) !== "string") { return []; }
value = value.trim(); value = value.trim();
@ -544,17 +556,18 @@ define([
// DELETE // DELETE
// Permanently delete multiple files at once using a list of paths // 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) // 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) { if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", { return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "delete", cmd: "delete",
data: { data: {
paths: paths, paths: paths,
nocheck: nocheck nocheck: nocheck,
isOwnPadRemoved: isOwnPadRemoved
} }
}, cb); }, cb);
} }
exp.deleteMultiplePermanently(paths, nocheck); exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved);
if (typeof cb === "function") { cb(); } if (typeof cb === "function") { cb(); }
}; };
exp.emptyTrash = function (cb) { exp.emptyTrash = function (cb) {

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

@ -802,6 +802,7 @@ define([
hide.push('properties'); hide.push('properties');
hide.push('rename'); hide.push('rename');
hide.push('openparent'); hide.push('openparent');
hide.push('hashtag');
} }
if (containsFolder && paths.length > 1) { if (containsFolder && paths.length > 1) {
// Cannot open multiple folders // Cannot open multiple folders
@ -911,6 +912,7 @@ define([
//var actions = []; //var actions = [];
var toShow = filterContextMenu(menuType, paths); var toShow = filterContextMenu(menuType, paths);
var $actions = $contextMenu.find('a'); var $actions = $contextMenu.find('a');
$contextMenu.data('paths', paths);
$actions = $actions.filter(function (i, el) { $actions = $actions.filter(function (i, el) {
return toShow.some(function (className) { return $(el).is(className); }); return toShow.some(function (className) { return $(el).is(className); });
}); });
@ -922,9 +924,6 @@ define([
} else { } else {
$a.text($(el).text()); $a.text($(el).text());
} }
$(el).data('paths', paths);
//$(el).data('path', path);
//:$(el).data('element', $element);
$container.append($a); $container.append($a);
$a.click(function() { $(el).click(); }); $a.click(function() { $(el).click(); });
}); });
@ -1435,6 +1434,7 @@ define([
case FILES_DATA: pName = FILES_DATA_NAME; break; case FILES_DATA: pName = FILES_DATA_NAME; break;
case SEARCH: pName = SEARCH_NAME; break; case SEARCH: pName = SEARCH_NAME; break;
case RECENT: pName = RECENT_NAME; break; case RECENT: pName = RECENT_NAME; break;
case OWNED: pName = OWNED_NAME; break;
default: pName = name; default: pName = name;
} }
return pName; return pName;
@ -1509,6 +1509,11 @@ define([
if (!APP.loggedIn) { if (!APP.loggedIn) {
msg = Messages.fm_info_anonymous; msg = Messages.fm_info_anonymous;
$box.html(msg); $box.html(msg);
$box.find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
var href = $(this).attr('href');
common.gotoURL(href);
});
return $box; return $box;
} }
if (!msg || APP.store['hide-info-' + path[0]] === '1') { if (!msg || APP.store['hide-info-' + path[0]] === '1') {
@ -2227,7 +2232,7 @@ define([
// Only Trash and Root are available in not-owned files manager // Only Trash and Root are available in not-owned files manager
if (!path || displayedCategories.indexOf(path[0]) === -1) { if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError); log(Messages.fm_categoryError);
currentPath = [ROOT]; currentPath = [ROOT];
_displayDirectory(currentPath); _displayDirectory(currentPath);
return; return;
@ -2668,11 +2673,14 @@ define([
var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); var data = JSON.parse(JSON.stringify(filesOp.getFileData(el)));
if (!data || !data.href) { return void cb('INVALID_FILE'); } if (!data || !data.href) { return void cb('INVALID_FILE'); }
data.href = base + data.href; data.href = base + data.href;
var roUrl;
if (ro) { if (ro) {
data.roHref = data.href; data.roHref = data.href;
delete data.href; delete data.href;
} else { } else {
data.roHref = base + getReadOnlyUrl(el); roUrl = getReadOnlyUrl(el);
if (roUrl) { data.roHref = base + roUrl; }
} }
UIElements.getProperties(common, data, cb); UIElements.getProperties(common, data, cb);
@ -2682,11 +2690,13 @@ define([
$contextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove) $contextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove)
.attr('data-icon', 'fa-eraser'); .attr('data-icon', 'fa-eraser');
} }
var deletePaths = function (paths) { var deletePaths = function (paths, pathsList) {
var pathsList = []; pathsList = pathsList || [];
paths.forEach(function (p) { pathsList.push(p.path); }); if (paths) {
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); paths.forEach(function (p) { pathsList.push(p.path); });
if (paths.length === 1) { }
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) {
msg = Messages.fm_removePermanentlyDialog; msg = Messages.fm_removePermanentlyDialog;
} }
UI.confirm(msg, function(res) { UI.confirm(msg, function(res) {
@ -2695,6 +2705,37 @@ define([
filesOp.delete(pathsList, refresh); filesOp.delete(pathsList, refresh);
}); });
}; };
var deleteOwnedPaths = function (paths, pathsList) {
pathsList = pathsList || [];
if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); });
}
var msgD = pathsList.length === 1 ? Messages.fm_deleteOwnedPad :
Messages.fm_deleteOwnedPads;
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) {
var el = filesOp.find(p);
var data = filesOp.getFileData(el);
var parsed = Hash.parsePadUrl(data.href);
var channel = Util.base64ToHex(parsed.hashData.channel);
n = n.nThen(function (waitFor) {
sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel,
waitFor(function (e) {
if (e) { return void console.error(e); }
filesOp.delete([p], function () {}, false, true);
}));
});
});
n.nThen(function () { refresh(); });
*/
});
};
$contextMenu.on("click", "a", function(e) { $contextMenu.on("click", "a", function(e) {
e.stopPropagation(); e.stopPropagation();
var paths = $contextMenu.data('paths'); var paths = $contextMenu.data('paths');
@ -2720,27 +2761,7 @@ define([
moveElements(pathsList, [TRASH], false, refresh); moveElements(pathsList, [TRASH], false, refresh);
} }
else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { else if ($(this).hasClass('cp-app-drive-context-deleteowned')) {
var msgD = Messages.fm_deleteOwnedPads; deleteOwnedPaths(paths);
UI.confirm(msgD, function(res) {
$(window).focus();
if (!res) { return; }
// Try to delete each selected pad from server, and delete from drive if no error
var n = nThen(function () {});
paths.forEach(function (p) {
var el = filesOp.find(p.path);
var data = filesOp.getFileData(el);
var parsed = Hash.parsePadUrl(data.href);
var channel = Util.base64ToHex(parsed.hashData.channel);
n = n.nThen(function (waitFor) {
sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel,
waitFor(function (e) {
if (e) { return void console.error(e); }
filesOp.delete([p.path], refresh);
}));
});
});
});
return;
} }
else if ($(this).hasClass('cp-app-drive-context-open')) { else if ($(this).hasClass('cp-app-drive-context-open')) {
paths.forEach(function (p) { paths.forEach(function (p) {
@ -2878,18 +2899,11 @@ define([
if (!$(elmt).data('path')) { return; } if (!$(elmt).data('path')) { return; }
paths.push($(elmt).data('path')); paths.push($(elmt).data('path'));
}); });
// If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently, if (!paths.length) { return; }
// If we are in the trash or anon pad or if we are holding the "shift" key,
// delete permanently
if (!APP.loggedIn || isTrash || e.shiftKey) { if (!APP.loggedIn || isTrash || e.shiftKey) {
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); deletePaths(null, paths);
if (paths.length === 1) {
msg = Messages.fm_removePermanentlyDialog;
}
UI.confirm(msg, function(res) {
$(window).focus();
if (!res) { return; }
filesOp.delete(paths, refresh);
});
return; return;
} }
// else move to trash // else move to trash
@ -2975,6 +2989,19 @@ define([
refresh(); refresh();
UI.removeLoadingScreen(); UI.removeLoadingScreen();
sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) {
var ids = filesOp.findChannels(data);
var titles = [];
ids.forEach(function (id) {
var title = filesOp.getTitle(id);
titles.push(title);
var paths = filesOp.findFile(id);
filesOp.delete(paths, refresh);
});
if (!titles.length) { return; }
UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')]));
});
}; };
var setHistory = function (bool, update) { var setHistory = function (bool, update) {
@ -3091,7 +3118,6 @@ define([
throw new Error("Corrupted drive"); throw new Error("Corrupted drive");
} }
andThen(common, proxy); andThen(common, proxy);
UI.removeLoadingScreen();
var onDisconnect = APP.onDisconnect = function (noAlert) { var onDisconnect = APP.onDisconnect = function (noAlert) {
setEditable(false); setEditable(false);

@ -56,6 +56,12 @@ define([
cb(obj); cb(obj);
}); });
}); });
sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) {
Cryptpad.getDeletedPads(function (err, obj) {
if (err) { return void console.error(err); }
cb(obj);
});
});
Cryptpad.onNetworkDisconnect.reg(function () { Cryptpad.onNetworkDisconnect.reg(function () {
sframeChan.event('EV_NETWORK_DISCONNECT'); sframeChan.event('EV_NETWORK_DISCONNECT');
}); });

@ -224,6 +224,8 @@ define([
if (decrypting) { return; } if (decrypting) { return; }
decrypting = true; decrypting = true;
displayFile(ev, sizeMb, function (err) { displayFile(ev, sizeMb, function (err) {
$appContainer.css('background-color',
common.getAppConfig().appBackgroundColor);
if (err) { UI.alert(err); } if (err) { UI.alert(err); }
}); });
}; };

@ -13,7 +13,6 @@ define([
$(function () { $(function () {
var $main = $('#mainBlock'); var $main = $('#mainBlock');
var $checkImport = $('#import-recent'); var $checkImport = $('#import-recent');
var Messages = Cryptpad.Messages;
// main block is hidden in case javascript is disabled // main block is hidden in case javascript is disabled
$main.removeClass('hidden'); $main.removeClass('hidden');
@ -61,90 +60,15 @@ define([
hashing = true; hashing = true;
var shouldImport = $checkImport[0].checked; var shouldImport = $checkImport[0].checked;
var uname = $uname.val();
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up var passwd = $passwd.val();
window.setTimeout(function () { Login.loginOrRegisterUI(uname, passwd, false, shouldImport, Test.testing, function () {
UI.addLoadingScreen({ if (test) {
loadingText: Messages.login_hashing, localStorage.clear();
hideTips: true, test.pass();
}); return 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);
}); });
$('#register').on('click', function () { $('#register').on('click', function () {
if (sessionStorage) { 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();
});

@ -31,6 +31,7 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
'/customize/application_config.js',
'/bower_components/diff-dom/diffDOM.js', '/bower_components/diff-dom/diffDOM.js',
@ -50,7 +51,8 @@ define([
ApiConfig, ApiConfig,
Hash, Hash,
Util, Util,
ChainPad) ChainPad,
AppConfig)
{ {
var DiffDom = window.diffDOM; var DiffDom = window.diffDOM;
@ -400,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 () { framework.setContentGetter(function () {
displayMediaTags(framework, inner, mediaTagMap); displayMediaTags(framework, inner, mediaTagMap);
inner.normalize(); inner.normalize();
@ -592,7 +605,8 @@ define([
} }
// Used in ckeditor-config.js // Used in ckeditor-config.js
Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs; Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs;
var newCss = '.cke_body_width { background: #666; height: 100%; }' + var backColor = AppConfig.appBackgroundColor;
var newCss = '.cke_body_width { background: '+ backColor +'; height: 100%; }' +
'.cke_body_width body {' + '.cke_body_width body {' +
'max-width: 50em; padding: 10px 30px; margin: 0 auto; min-height: 100%;'+ 'max-width: 50em; padding: 10px 30px; margin: 0 auto; min-height: 100%;'+
'box-sizing: border-box;'+ 'box-sizing: border-box;'+

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

@ -1119,17 +1119,31 @@ define([
} }
UI.removeLoadingScreen(); 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(); 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); setEditable(false);
UI.alert(Messages.common_connectionLost, undefined, true); UI.alert(Messages.common_connectionLost, undefined, true);
}; };
var onReconnect = function () { var onReconnect = function () {
if (APP.unrecoverable) { return; }
setEditable(true); setEditable(true);
UI.findOKButton().click(); UI.findOKButton().click();
}; };
@ -1175,6 +1189,7 @@ define([
Title.setToolbar(APP.toolbar); Title.setToolbar(APP.toolbar);
var $rightside = APP.toolbar.$rightside; var $rightside = APP.toolbar.$rightside;
var $drawer = APP.toolbar.$drawer;
metadataMgr.onChange(function () { metadataMgr.onChange(function () {
var md = copyObject(metadataMgr.getMetadata()); var md = copyObject(metadataMgr.getMetadata());
@ -1189,6 +1204,9 @@ define([
var $forgetPad = common.createButton('forget', true, {}, forgetCb); var $forgetPad = common.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $rightside.append($forgetPad);
var $properties = common.createButton('properties', true);
$drawer.append($properties);
/* save as template */ /* save as template */
if (!metadataMgr.getPrivateData().isTemplate) { if (!metadataMgr.getPrivateData().isTemplate) {
var templateObj = { var templateObj = {
@ -1201,7 +1219,7 @@ define([
/* add an export button */ /* add an export button */
var $export = common.createButton('export', true, {}, exportFile); var $export = common.createButton('export', true, {}, exportFile);
$rightside.append($export); $drawer.append($export);
var $help = common.createButton('', true).click(function () { showHelp(); }) var $help = common.createButton('', true).click(function () { showHelp(); })
.appendTo($rightside); .appendTo($rightside);
@ -1255,6 +1273,16 @@ define([
SFCommon.create(waitFor(function (c) { APP.common = common = c; })); SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
common.getSframeChannel().onReady(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 */) { }).nThen(function (/* waitFor */) {
Test.registerInner(common.getSframeChannel()); Test.registerInner(common.getSframeChannel());
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();

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

@ -55,39 +55,6 @@ define([
var registering = false; var registering = false;
var test; 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 () { $register.click(function () {
if (registering) { if (registering) {
@ -125,89 +92,14 @@ define([
function (yes) { function (yes) {
if (!yes) { return; } if (!yes) { return; }
Login.loginOrRegisterUI(uname, passwd, true, shouldImport, Test.testing, function () {
if (test) {
localStorage.clear();
test.pass();
return true;
}
});
registering = 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, ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel, cancel: Messages.register_cancel,

@ -10,6 +10,7 @@ define([
'/customize/messages.js', '/customize/messages.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/application_config.js', '/customize/application_config.js',
'/api/config',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -26,7 +27,8 @@ define([
Hash, Hash,
Messages, Messages,
h, h,
AppConfig AppConfig,
ApiConfig
) )
{ {
var saveAs = window.saveAs; var saveAs = window.saveAs;
@ -64,10 +66,16 @@ define([
'code': [ 'code': [
'cp-settings-code-indent-unit', 'cp-settings-code-indent-unit',
'cp-settings-code-indent-type' 'cp-settings-code-indent-type'
] ],
'subscription': {
onClick: function () {
var urls = common.getMetadataMgr().getPrivateData().accounts;
window.open(urls.upgradeURL);
}
}
}; };
if (!AppConfig.dislayCreationScreen) { if (!AppConfig.displayCreationScreen) {
delete categories.creation; delete categories.creation;
} }
if (AppConfig.disableFeedback) { if (AppConfig.disableFeedback) {
@ -78,6 +86,9 @@ define([
var displaynameIdx = categories.account.indexOf('cp-settings-displayname'); var displaynameIdx = categories.account.indexOf('cp-settings-displayname');
categories.account.splice(displaynameIdx, 1); categories.account.splice(displaynameIdx, 1);
} }
if (!ApiConfig.allowSubscriptions) {
delete categories.subscription;
}
var create = {}; var create = {};
@ -778,7 +789,7 @@ define([
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'}) var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
.appendTo(APP.$leftside); .appendTo(APP.$leftside);
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside); APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
var active = 'account'; var active = privateData.category || 'account';
Object.keys(categories).forEach(function (key) { Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
if (key === 'account') { $category.append($('<span>', {'class': 'fa fa-user-o'})); } if (key === 'account') { $category.append($('<span>', {'class': 'fa fa-user-o'})); }
@ -786,12 +797,17 @@ define([
if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); } if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); }
if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); } if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); }
if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); } if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); }
if (key === 'subscription') { $category.append($('<span>', {'class': 'fa fa-star-o' })); }
if (key === active) { if (key === active) {
$category.addClass('cp-leftside-active'); $category.addClass('cp-leftside-active');
} }
$category.click(function () { $category.click(function () {
if (!Array.isArray(categories[key]) && categories[key].onClick) {
categories[key].onClick();
return;
}
active = key; active = key;
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active'); $categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
$category.addClass('cp-leftside-active'); $category.addClass('cp-leftside-active');

@ -66,9 +66,18 @@ define([
Cryptpad.mergeAnonDrive(cb); Cryptpad.mergeAnonDrive(cb);
}); });
}; };
var category;
if (window.location.hash) {
category = window.location.hash.slice(1);
window.location.hash = '';
}
var addData = function (obj) {
if (category) { obj.category = category; }
};
SFCommonO.start({ SFCommonO.start({
noRealtime: true, noRealtime: true,
addRpc: addRpc addRpc: addRpc,
addData: addData
}); });
}); });
}); });

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

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

@ -39,6 +39,24 @@ define([
if (typeof(proxy.data) !== 'object') { proxy.data = {}; } if (typeof(proxy.data) !== 'object') { proxy.data = {}; }
if (!Array.isArray(proxy.order)) { proxy.order = []; } if (!Array.isArray(proxy.order)) { proxy.order = []; }
if (typeof(proxy.type) !== 'string') { proxy.type = 'todo'; } 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 */ /* add (id, obj) push id to order, add object to data */
@ -59,6 +77,17 @@ define([
if (proxy.data[id]) { delete proxy.data[id]; } 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) { Todo.init = function (proxy) {
var api = {}; var api = {};
initialize(proxy); initialize(proxy);
@ -72,6 +101,12 @@ define([
api.remove = function (id) { api.remove = function (id) {
return remove(proxy, id); return remove(proxy, id);
}; };
api.getOrder = function () {
return proxy.order.slice();
};
api.reorder = function (order) {
return reorder(proxy, order);
};
return api; return api;
}; };

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

@ -415,6 +415,7 @@ define([
Title.setToolbar(toolbar); Title.setToolbar(toolbar);
var $rightside = toolbar.$rightside; var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
/* save as template */ /* save as template */
if (!metadataMgr.getPrivateData().isTemplate) { if (!metadataMgr.getPrivateData().isTemplate) {
@ -428,7 +429,7 @@ define([
/* add an export button */ /* add an export button */
var $export = common.createButton('export', true, {}, saveImage); var $export = common.createButton('export', true, {}, saveImage);
$rightside.append($export); $drawer.append($export);
if (common.isLoggedIn()) { if (common.isLoggedIn()) {
common.createButton('savetodrive', true, {}, function () {}) common.createButton('savetodrive', true, {}, function () {})
@ -449,6 +450,9 @@ define([
}); });
$rightside.append($forget); $rightside.append($forget);
var $properties = common.createButton('properties', true);
toolbar.$drawer.append($properties);
if (!readOnly) { if (!readOnly) {
makeColorButton($rightside); makeColorButton($rightside);
@ -562,7 +566,12 @@ define([
if (readOnly) { return; } 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(); common.openTemplatePicker();
} }
}); });
@ -589,6 +598,7 @@ define([
}; };
config.onAbort = function () { config.onAbort = function () {
if (APP.unrecoverable) { return; }
// inform of network disconnect // inform of network disconnect
setEditable(false); setEditable(false);
toolbar.failed(); toolbar.failed();
@ -596,6 +606,7 @@ define([
}; };
config.onConnectionChange = function (info) { config.onConnectionChange = function (info) {
if (APP.unrecoverable) { return; }
setEditable(info.state); setEditable(info.state);
if (info.state) { if (info.state) {
initializing = true; 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); cpNfInner = common.startRealtime(config);
metadataMgr = cpNfInner.metadataMgr; metadataMgr = cpNfInner.metadataMgr;
cpNfInner.onInfiniteSpinner(function () { cpNfInner.onInfiniteSpinner(function () {
if (APP.unrecoverable) { return; }
setEditable(false); setEditable(false);
UI.confirm(Messages.realtime_unrecoverableError, function (yes) { UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; } if (!yes) { return; }
@ -640,6 +659,18 @@ define([
$('body').append($div.html()); $('body').append($div.html());
})); }));
SFCommon.create(waitFor(function (c) { APP.common = common = c; })); 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*/) { }).nThen(function (/*waitFor*/) {
andThen(common); andThen(common);
}); });

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

@ -32,6 +32,7 @@ define([
sFrameChan.onReady(waitFor()); sFrameChan.onReady(waitFor());
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var $container = $('#cp-app-worker-container'); var $container = $('#cp-app-worker-container');
$('<a>', {href:'http://localhost:3000/worker/', target:'_blank'}).text('other').appendTo($container);
var $bar = $('.cp-toolbar-container'); var $bar = $('.cp-toolbar-container');
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle']; var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];

@ -29,16 +29,17 @@ require.config({
}); });
var i = 0; var i = 0;
var id = Math.floor(Math.random()*100000);
onconnect = function(e) { onconnect = function(e) {
console.log(e); console.log(e);
console.log(i); console.log(i);
var port = e.ports[0]; var port = e.ports[0];
console.log('here'); console.log('here');
require([ //require([
'/common/outer/async-store.js' // '/common/outer/async-store.js'
], function (Store) { //], function (Store) {
console.log(Store); //console.log(Store);
console.log(self.Proxy); console.log(self.Proxy);
var n = i; var n = i;
port.postMessage({state: 'READY'}); port.postMessage({state: 'READY'});
@ -46,8 +47,9 @@ onconnect = function(e) {
console.log('worker received'); console.log('worker received');
console.log(e.data); console.log(e.data);
port.postMessage('hello CryptPad'+n); port.postMessage('hello CryptPad'+n);
port.postMessage('This is '+id);
}; };
var data = { /*var data = {
query: function (cmd, data, cb) { query: function (cmd, data, cb) {
console.log(cmd, data); console.log(cmd, data);
}, },
@ -64,7 +66,7 @@ onconnect = function(e) {
}); });
port.postMessage('Store is connected!'); port.postMessage('Store is connected!');
port.postMessage('Your username is ' + ret.store.proxy['cryptpad.username']); port.postMessage('Your username is ' + ret.store.proxy['cryptpad.username']);
}); });*/
i++; i++;
}); //});
}; };

Loading…
Cancel
Save