Merge branch 'staging' into newCk

pull/1/head
yflory 8 years ago
commit 5a248db267

@ -37,7 +37,6 @@
"diff-dom": "^2.1.1",
"alertifyjs": "^1.0.11",
"scrypt-async": "^1.2.0",
"bootstrap": "#v4.0.0-alpha.6",
"pdfjs-dist": "^1.8.398"
"bootstrap": "#v4.0.0-alpha.6"
}
}

@ -33,9 +33,9 @@ module.exports = {
* it is recommended that you configure these fields to match the
* domain which will serve your CryptPad instance.
*/
"child-src 'self' *",
"child-src 'self' blob: *",
"media-src *",
"media-src * blob:",
/* this allows connections over secure or insecure websockets
if you are deploying to production, you'll probably want to remove

@ -117,7 +117,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -10,7 +10,19 @@ CKEDITOR.editorConfig = function( config ) {
// document itself and causes problems when it's sent across the wire and reflected back
config.removePlugins= 'resize';
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify';
config.toolbarGroups= [{"name":"clipboard","groups":["clipboard","undo"]},{"name":"editing","groups":["find","selection"]},{"name":"links"},{"name":"insert"},{"name":"forms"},{"name":"tools"},{"name":"document","groups":["mode","document","doctools"]},{"name":"others"},{"name":"basicstyles","groups":["basicstyles","cleanup"]},{"name":"paragraph","groups":["list","indent","blocks","align","bidi"]},{"name":"styles"},{"name":"colors"}];
config.toolbarGroups= [
// {"name":"clipboard","groups":["clipboard","undo"]},
//{"name":"editing","groups":["find","selection"]},
{"name":"links"},
{"name":"insert"},
{"name":"forms"},
{"name":"tools"},
{"name":"document","groups":["mode","document","doctools"]},
{"name":"others"},
{"name":"basicstyles","groups":["basicstyles","cleanup"]},
{"name":"paragraph","groups":["list","indent","blocks","align","bidi"]},
{"name":"styles"},
{"name":"colors"}];
config.font_defaultLabel = 'Arial';
config.fontSize_defaultLabel = '16';

@ -114,7 +114,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -236,7 +236,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -1164,6 +1164,57 @@ html.cp,
.limit-container .upgrade {
margin-left: 10px;
}
/* Upload status table */
#uploadStatusContainer {
position: absolute;
left: 10vw;
right: 10vw;
bottom: 100px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
opacity: 0.7;
font-family: Ubuntu,Georgia,Cambria,serif;
box-sizing: border-box;
z-index: 10;
display: none;
}
#uploadStatusContainer #uploadStatus {
width: 80vw;
border: 1px solid black;
border-collapse: collapse;
}
#uploadStatusContainer #uploadStatus tr:nth-child(1) {
background-color: #888;
border: 1px solid #999;
}
#uploadStatusContainer #uploadStatus tr:nth-child(1) td {
text-align: center;
}
#uploadStatusContainer #uploadStatus td {
border-left: 1px solid #BBB;
border-right: 1px solid #BBB;
padding: 0 10px;
}
#uploadStatusContainer #uploadStatus .upProgress {
width: 200px;
position: relative;
text-align: center;
box-sizing: border-box;
}
#uploadStatusContainer #uploadStatus .progressContainer {
position: absolute;
width: 0px;
left: 5px;
top: 1px;
bottom: 1px;
background-color: rgba(0, 0, 255, 0.3);
}
#uploadStatusContainer #uploadStatus .upCancel {
text-align: center;
}
#uploadStatusContainer #uploadStatus .fa.cancel {
color: #ff0073;
}
#cors-store {
display: none;
}

@ -135,7 +135,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -39,5 +39,5 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>

@ -600,6 +600,52 @@ noscript {
}
}
/* Upload status table */
#uploadStatusContainer {
position: absolute;
left: 10vw; right: 10vw;
bottom: 100px;
background-color: rgba(0,0,0,0.5);
color: white;
opacity: 0.7;
font-family: Ubuntu,Georgia,Cambria,serif;
box-sizing: border-box;
z-index:10;
display: none;
#uploadStatus {
width: 80vw;
border: 1px solid black;
border-collapse: collapse;
tr:nth-child(1) {
background-color: #888;
border: 1px solid #999;
td { text-align: center; }
}
td {
border-left: 1px solid #BBB;
border-right: 1px solid #BBB;
padding: 0 10px;
}
.upProgress {
width: 200px;
position: relative;
text-align: center;
box-sizing: border-box;
}
.progressContainer {
position: absolute;
width: 0px;
left: 5px;
top: 1px; bottom: 1px;
background-color: rgba(0,0,255,0.3);
}
.upCancel { text-align: center; }
.fa.cancel {
color: rgb(255, 0, 115);
}
}
}
// hack for our cross-origin iframe
#cors-store {
display: none;

@ -118,7 +118,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -235,7 +235,7 @@ define(function () {
out.login_invalPass = "Contraseña requirida";
out.login_unhandledError = "Un error inesperado se produjo :(";
out.register_importRecent = "Importar historial (recomendado)";
out.register_acceptTerms = "Accepto los <a href='/terms.html'>términos de servicio</a>";
out.register_acceptTerms = "Accepto los <a href='/terms.html' tabindex='-1'>términos de servicio</a>";
out.register_passwordsDontMatch = "Las contraseñas no corresponden";
out.register_mustAcceptTerms = "Tienes que acceptar los términos de servicio";
out.register_mustRememberPass = "No podemos reiniciar tu contraseña si la olvidas. ¡Es muy importante que la recuerdes! Marca la casilla para confirmarlo.";
@ -351,6 +351,7 @@ define(function () {
out.tips.title = "Puedes cambiar el título de tus pads en la parte superior de la pantalla.";
out.tips.store = "Cada vez que visitas un pad con una sesión iniciada se guardará a tu CryptDrive.";
out.tips.marker = "Puedes resaltar texto en un pad utilizando el \"marcador\" en el menú de estílo.";
out.tips.driveUpload = "Usuarios registrados pueden subir archivos cifrados arrastrandolos hacia CryptDrive.";
out.feedback_about = "Si estas leyendo esto, quizas estés curioso de saber porqué CryptPad solicita esta página cuando haces algunas acciones";
out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos esta página para conocer las funcionalidades que importan a nuestros usuarios, pidiendolo con un parametro que nos dice que accion fue realizada.";
@ -452,6 +453,9 @@ define(function () {
out.poll_locked = "Cerrado";
out.poll_unlocked = "Abierto";
out.poll_show_help_button = "Mostrar ayuda";
out.poll_hide_help_button = "Esconder ayuda";
// 1.8.0 - Idopogo
out.common_connectionLost = "<b>Connexión perdida</b><br>El documento está ahora en modo solo lectura hasta que la conexión vuelva.";
@ -467,5 +471,19 @@ define(function () {
out.login_notRegistered = "¿No estás registrado?";
out.upload_mustLogin = "Tienes que estar conectado para subir archivos";
out.uploadButton = "Subir";
out.uploadButtonTitle = "Subir un archivo a la carpeta";
out.filePickerButton = "Incrustar un archivo";
out.filePicker_close = "Cerrar";
out.filePicker_description = "Elige un archivo de tu CryptDrive para incrustarlo o sube uno nuevo";
out.filePicker_filter = "Filtrar por nombre";
out.or = "o";
out.languageButton = "Lenguaje";
out.languageButtonTitle = "Elige el lenguaje para resaltado de sintaxis";
out.themeButton = "Tema";
out.themeButtonTitle = "Selecciona el tema de color para los editores de código y presentación";
out.canvas_opacityLabel = "Opacidad: {0}";
out.canvas_widthLabel = "Talla: {0}";
return out;
});

@ -108,6 +108,8 @@ define(function () {
out.newButton = 'Nouveau';
out.newButtonTitle = 'Créer un nouveau pad';
out.uploadButton = 'Upload';
out.uploadButtonTitle = 'Uploader un nouveau fichier dans le dossier actuel';
out.saveTemplateButton = "Sauver en tant que modèle";
out.saveTemplatePrompt = "Choisir un titre pour ce modèle";
@ -131,6 +133,12 @@ define(function () {
out.printCSS = "Personnaliser l'apparence (CSS):";
out.printTransition = "Activer les animations de transition";
out.filePickerButton = "Intégrer un fichier";
out.filePicker_close = "Fermer";
out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou uploadez-en un nouveau";
out.filePicker_filter = "Filtrez les fichiers par leur nom";
out.or = 'ou';
out.slideOptionsTitle = "Personnaliser la présentation";
out.slideOptionsButton = "Enregistrer (Entrée)";
@ -211,6 +219,9 @@ define(function () {
out.poll_locked = "Verrouillé";
out.poll_unlocked = "Déverrouillé";
out.poll_show_help_button = "Afficher l'aide";
out.poll_hide_help_button = "Cacher l'aide";
// Canvas
out.canvas_clear = "Nettoyer";
out.canvas_delete = "Supprimer la sélection";
@ -218,6 +229,8 @@ define(function () {
out.canvas_enable = "Activer le dessin";
out.canvas_width = "Épaisseur";
out.canvas_opacity = "Opacité";
out.canvas_opacityLabel = "opacité: {0}";
out.canvas_widthLabel = "taille: {0}";
// File manager
@ -322,7 +335,7 @@ define(function () {
out.login_notRegistered = 'Pas encore inscrit ?';
out.register_importRecent = "Importer l'historique (Recommendé)";
out.register_acceptTerms = "J'accepte <a href='/terms.html'>les conditions d'utilisation</a>";
out.register_acceptTerms = "J'accepte <a href='/terms.html' tabindex='-1'>les conditions d'utilisation</a>";
out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!";
out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation.";
out.register_mustRememberPass = "Nous ne pouvons pas réinitialiser votre mot de passe si vous l'oubliez. C'est important que vous vous en souveniez! Veuillez cocher la case pour confirmer.";
@ -545,6 +558,7 @@ define(function () {
out.tips.title = "Vous pouvez changer le titre de votre pad en cliquant au centre en haut de la page.";
out.tips.store = "Dés que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés.";
out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles.";
out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive.";
out.feedback_about = "Si vous lisez ceci, vous vous demandez probablement pourquoi CryptPad envoie des requêtes vers des pages web quand vous realisez certaines actions.";
out.feedback_privacy = "Nous prenons au sérieux le respect de votre vie privée, et en même temps nous souhaitons rendre CryptPad très simple à utiliser. Nous utilisons cette page pour comprendre quelles fonctionnalités dans l'interface comptent le plus pour les utilisateurs, en l'appelant avec un paramètre spécifiant quelle action a été réalisée.";

@ -110,6 +110,8 @@ define(function () {
out.newButton = 'New';
out.newButtonTitle = 'Create a new pad';
out.uploadButton = 'Upload';
out.uploadButtonTitle = 'Upload a new file to the current folder';
out.saveTemplateButton = "Save as template";
out.saveTemplatePrompt = "Choose a title for the template";
@ -133,6 +135,12 @@ define(function () {
out.printCSS = "Custom style rules (CSS):";
out.printTransition = "Enable transition animations";
out.filePickerButton = "Embed a file";
out.filePicker_close = "Close";
out.filePicker_description = "Choose a file from your CryptDrive to embed it or upload a new one";
out.filePicker_filter = "Filter files by name";
out.or = 'or';
out.slideOptionsTitle = "Customize your slides";
out.slideOptionsButton = "Save (enter)";
@ -213,6 +221,9 @@ define(function () {
out.poll_locked = "Locked";
out.poll_unlocked = "Unlocked";
out.poll_show_help_button = "Show help";
out.poll_hide_help_button = "Hide help";
// Canvas
out.canvas_clear = "Clear";
out.canvas_delete = "Delete selection";
@ -220,6 +231,9 @@ define(function () {
out.canvas_enable = "Enable draw";
out.canvas_width = "Width";
out.canvas_opacity = "Opacity";
out.canvas_opacityLabel = "opacity: {0}";
out.canvas_widthLabel = "Width: {0}";
// File manager
@ -324,7 +338,7 @@ define(function () {
out.login_notRegistered = 'Not registered?';
out.register_importRecent = "Import pad history (Recommended)";
out.register_acceptTerms = "I accept <a href='/terms.html'>the terms of service</a>";
out.register_acceptTerms = "I accept <a href='/terms.html' tabindex='-1'>the terms of service</a>";
out.register_passwordsDontMatch = "Passwords do not match!";
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.";
@ -556,6 +570,7 @@ define(function () {
out.tips.title = "You can set the title of your pad by clicking the top center.";
out.tips.store = "Every time you visit a pad, if you're logged in it will be saved to your CryptDrive.";
out.tips.marker = "You can highlight text in a pad using the \"marker\" item in the styles dropdown menu.";
out.tips.driveUpload = "Registered users can upload encrypted files by dragging and dropping them into their CryptDrive.";
out.feedback_about = "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions";
out.feedback_privacy = "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.";

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.8.0",
"version": "1.9.0",
"dependencies": {
"chainpad-server": "^1.0.1",
"express": "~4.10.1",

@ -17,6 +17,15 @@ var Store = require("./storage/file");
var DEFAULT_LIMIT = 50 * 1024 * 1024;
var SESSION_EXPIRATION_TIME = 60 * 1000;
var SUPPRESS_RPC_ERRORS = false;
var WARN = function (e, output) {
if (!SUPPRESS_RPC_ERRORS && e && output) {
console.error(new Date().toISOString() + ' [' + e + ']', output);
console.error(new Error(e).stack);
console.error();
}
};
var isValidId = function (chan) {
return chan && chan.length && /^[a-fA-F0-9]/.test(chan) ||
@ -237,11 +246,10 @@ var loadUserPins = function (Env, publicKey, cb) {
}
break;
default:
console.error('invalid message read from store');
WARN('invalid message read from store', msg);
}
} catch (e) {
console.log('invalid message read from store');
console.error(e);
WARN('invalid message read from store', e);
}
}, function () {
// no more messages
@ -328,8 +336,13 @@ var getMultipleFileSize = function (Env, channels, cb) {
channels.forEach(function (channel) {
getFileSize(Env, channel, function (e, size) {
if (e) {
console.error(e);
counts[channel] = -1;
// most likely error here is that a file no longer exists
// but a user still has it in their drive, and wants to know
// its size. We should find a way to inform them of this in
// the future. For now we can just tell them it has no size.
//WARN('getFileSize', e);
counts[channel] = 0;
return done();
}
counts[channel] = size;
@ -504,7 +517,7 @@ var pinChannel = function (Env, publicKey, channels, cb) {
getFreeSpace(Env, publicKey, function (e, free) {
if (e) {
console.error(e);
WARN('getFreeSpace', e);
return void cb(e);
}
if (pinSize > free) { return void cb('E_OVER_LIMIT'); }
@ -573,7 +586,7 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
getFreeSpace(Env, publicKey, function (e, free) {
if (e) {
console.error(e);
WARN('getFreeSpace', e);
return void cb(e);
}
@ -646,6 +659,9 @@ var makeFileStream = function (root, id, cb) {
stream.on('open', function () {
cb(void 0, stream);
});
stream.on('error', function (e) {
WARN('stream error', e);
});
} catch (err) {
cb('BAD_STREAM');
}
@ -737,14 +753,12 @@ var upload_complete = function (Env, publicKey, cb) {
safeMkdir(Path.join(paths.blob, prefix), function (e) {
if (e) {
console.error('[safeMkdir]');
console.error(e);
console.log();
WARN('safeMkdir', e);
return void cb('RENAME_ERR');
}
isFile(newPath, function (e, yes) {
if (e) {
console.error(e);
WARN('isFile', e);
return void cb(e);
}
if (yes) {
@ -770,7 +784,7 @@ var upload_complete = function (Env, publicKey, cb) {
// lol wut handle ur errors
Fs.rename(oldPath, newPath, function (e) {
if (e) {
console.error(e);
WARN('rename', e);
if (retries--) {
return setTimeout(function () {
@ -803,7 +817,7 @@ var upload_status = function (Env, publicKey, filesize, cb) {
if (filesize >= free) { return cb('NOT_ENOUGH_SPACE'); }
isFile(filePath, function (e, yes) {
if (e) {
console.error("uploadError: [%s]", e);
WARN('upload', e);
return cb('UNNOWN_ERROR');
}
cb(e, yes);
@ -834,11 +848,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
// load pin-store...
console.log('loading rpc module...');
var warn = function (e, output) {
if (e && !config.suppressRPCErrors) {
console.error(new Date().toISOString() + ' [' + e + ']', output);
}
};
if (config.suppressRPCErrors) { SUPPRESS_RPC_ERRORS = true; }
var keyOrDefaultString = function (key, def) {
return typeof(config[key]) === 'string'? config[key]: def;
@ -937,41 +947,41 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
case 'COOKIE': return void Respond(void 0);
case 'RESET':
return resetUserPins(Env, safeKey, msg[1], function (e, hash) {
//warn(e, hash);
//WARN(e, hash);
return void Respond(e, hash);
});
case 'PIN':
return pinChannel(Env, safeKey, msg[1], function (e, hash) {
warn(e, hash);
WARN(e, hash);
Respond(e, hash);
});
case 'UNPIN':
return unpinChannel(Env, safeKey, msg[1], function (e, hash) {
warn(e, hash);
WARN(e, hash);
Respond(e, hash);
});
case 'GET_HASH':
return void getHash(Env, safeKey, function (e, hash) {
warn(e, hash);
WARN(e, hash);
Respond(e, hash);
});
case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit
return getTotalSize(Env, safeKey, function (e, size) {
if (e) {
warn(e, safeKey);
WARN(e, safeKey);
return void Respond(e);
}
Respond(e, size);
});
case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[1], function (e, size) {
warn(e, msg[1]);
WARN(e, msg[1]);
Respond(e, size);
});
case 'UPDATE_LIMITS':
return void updateLimits(config, safeKey, function (e, limit) {
if (e) {
warn(e, limit);
WARN(e, limit);
return void Respond(e);
}
Respond(void 0, limit);
@ -979,7 +989,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
case 'GET_LIMIT':
return void getLimit(Env, safeKey, function (e, limit) {
if (e) {
warn(e, limit);
WARN(e, limit);
return void Respond(e);
}
Respond(void 0, limit);
@ -987,7 +997,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
case 'GET_MULTIPLE_FILE_SIZE':
return void getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) {
warn(e, dict);
WARN(e, dict);
return void Respond(e);
}
Respond(void 0, dict);
@ -997,7 +1007,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
case 'UPLOAD':
if (!privileged) { return deny(); }
return void upload(Env, safeKey, msg[1], function (e, len) {
warn(e, len);
WARN(e, len);
Respond(e, len);
});
case 'UPLOAD_STATUS':
@ -1015,13 +1025,13 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
case 'UPLOAD_COMPLETE':
if (!privileged) { return deny(); }
return void upload_complete(Env, safeKey, function (e, hash) {
warn(e, hash);
WARN(e, hash);
Respond(e, hash);
});
case 'UPLOAD_CANCEL':
if (!privileged) { return deny(); }
return void upload_cancel(Env, safeKey, function (e) {
warn(e);
WARN(e);
Respond(e);
});
default:
@ -1054,7 +1064,9 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
var updateLimitDaily = function () {
updateLimits(config, undefined, function (e) {
if (e) { console.error('Error updating the storage limits', e); }
if (e) {
WARN('limitUpdate', e);
}
});
};
updateLimitDaily();

@ -34,6 +34,7 @@ var setHeaders = (function () {
const headers = clone(config.httpHeaders);
if (config.contentSecurity) {
headers['Content-Security-Policy'] = clone(config.contentSecurity);
if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' }
if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) {
// backward compat for those who do not merge the new version of the config
// when updating. This prevents endless spinner if someone clicks donate.
@ -88,7 +89,9 @@ var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'cont
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
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')), {
maxAge: DEV_MODE? "0d": "365d"
}));
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
@ -138,7 +141,13 @@ app.get('/api/config', function(req, res){
var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app);
httpServer.listen(config.httpPort,config.httpAddress,function(){
console.log('[%s] listening on port %s', new Date().toISOString(), config.httpPort);
var host = config.httpAddress;
var hostName = !host.indexOf(':') ? '[' + host + ']' : host;
var port = config.httpPort;
var ps = port === 80? '': ':' + port;
console.log('\n[%s] server available http://%s%s', new Date().toISOString(), hostName, ps);
});
var wsConfig = { server: httpServer };

@ -53,6 +53,9 @@ body {
font-family: Calibri, Ubuntu, sans-serif;
word-wrap: break-word;
}
#previewContainer media-tag * {
max-width: 100%;
}
#preview {
max-width: 40vw;
margin: auto;

@ -56,6 +56,9 @@ body {
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
media-tag * {
max-width:100%;
}
}
#preview {

@ -9,6 +9,7 @@ define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/diffMarked.js',
'/bower_components/tweetnacl/nacl-fast.min.js', // needed for media-tag
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
Cryptget, DiffMd) {
var Messages = Cryptpad.Messages;

@ -20,5 +20,25 @@ define([], function () {
};
}
var failStore = function () {
console.error(new Error('wut'));
require(['jquery'], function ($) {
$.ajax({
type: 'HEAD',
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
};
try {
var test_key = 'localStorage_test';
var testval = Math.random().toString();
localStorage.setItem(test_key, testval);
if (localStorage.getItem(test_key) !== testval) {
failStore();
}
} catch (e) { console.error(e); failStore(); }
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
});

@ -0,0 +1,294 @@
define([
'jquery',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto) {
var Nacl = window.nacl;
var module = {};
module.create = function (common, config) {
var File = {};
var Messages = common.Messages;
var queue = File.queue = {
queue: [],
inProgress: false
};
var uid = function () {
return 'file-' + String(Math.random()).substring(2);
};
var $table = File.$table = $('<table>', { id: 'uploadStatus' });
var $thead = $('<tr>').appendTo($table);
$('<td>').text(Messages.upload_name).appendTo($thead);
$('<td>').text(Messages.upload_size).appendTo($thead);
$('<td>').text(Messages.upload_progress).appendTo($thead);
$('<td>').text(Messages.cancel).appendTo($thead);
var createTableContainer = function ($body) {
File.$container = $('<div>', { id: 'uploadStatusContainer' }).append($table).appendTo($body);
return File.$container;
};
var getData = function (file, href) {
var data = {};
data.name = file.metadata.name;
data.url = href;
if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true;
}
return data;
};
var upload = function (file) {
var blob = file.blob;
var metadata = file.metadata;
var id = file.id;
if (queue.inProgress) { return; }
queue.inProgress = true;
var $row = $table.find('tr[id="'+id+'"]');
$row.find('.upCancel').html('-');
var $pv = $row.find('.progressValue');
var $pb = $row.find('.progressContainer');
var $link = $row.find('.upLink');
var updateProgress = function (progressValue) {
$pv.text(Math.round(progressValue*100)/100 + '%');
$pb.css({
width: (progressValue/100)*188+'px'
});
};
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
console.log(box);
cb(e, msg);
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = common.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
$link.attr('href', href)
.click(function (e) {
e.preventDefault();
window.open($link.attr('href'), '_blank');
});
// TODO add button to table which copies link to clipboard?
//APP.toolbar.addElement(['fileshare'], {});
var title = metadata.name;
common.renamePad(title || "", href, function (err) {
if (err) { return void console.error(err); } // TODO
console.log(title);
common.log(Messages._getKey('upload_success', [title]));
common.prepareFeedback('upload')();
if (config.onUploaded) {
var data = getData(file, href);
config.onUploaded(file.dropEvent, data);
}
queue.inProgress = false;
queue.next();
});
//Title.updateTitle(title || "", href);
//APP.toolbar.title.show();
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
queue.inProgress = false;
queue.next();
if (e === 'TOO_LARGE') {
// TODO update table to say too big?
return void common.alert(Messages.upload_tooLarge);
}
if (e === 'NOT_ENOUGH_SPACE') {
// TODO update table to say not enough space?
return void common.alert(Messages.upload_notEnoughSpace);
}
console.error(e);
return void common.alert(Messages.upload_serverError);
}
if (pending) {
// TODO keep this message in case of pending files in another window?
return void common.confirm(Messages.upload_uploadPending, function (yes) {
if (!yes) { return; }
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
var prettySize = function (bytes) {
var kB = common.bytesToKilobytes(bytes);
if (kB < 1024) { return kB + Messages.KB; }
var mB = common.bytesToMegabytes(bytes);
return mB + Messages.MB;
};
queue.next = function () {
if (queue.queue.length === 0) {
queue.to = window.setTimeout(function () {
if (config.keepTable) { return; }
File.$container.fadeOut();
}, 3000);
return;
}
if (queue.inProgress) { return; }
File.$container.show();
var file = queue.queue.shift();
upload(file);
};
queue.push = function (obj) {
var id = uid();
obj.id = id;
queue.queue.push(obj);
$table.show();
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
var $progressBar = $('<div>', {'class':'progressContainer'});
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
var $tr = $('<tr>', {id: id}).appendTo($table);
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
$cancel.remove();
$tr.find('.upCancel').text('-');
$tr.find('.progressValue').text(Messages.upload_cancelled);
});
var $link = $('<a>', {
'class': 'upLink',
'rel': 'noopener noreferrer'
}).text(obj.metadata.name);
$('<td>').append($link).appendTo($tr);
$('<td>').text(prettySize(estimate)).appendTo($tr);
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
queue.next();
};
var handleFile = File.handleFile = function (file, e) {
var reader = new FileReader();
reader.onloadend = function () {
queue.push({
blob: this.result,
metadata: {
name: file.name,
type: file.type,
},
dropEvent: e
});
};
reader.readAsArrayBuffer(file);
};
var onFileDrop = File.onFileDrop = function (file, e) {
Array.prototype.slice.call(file).forEach(function (d) {
handleFile(d, e);
});
};
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
var counter = 0;
if (!$hoverArea) { $hoverArea = $area; }
$hoverArea
.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$hoverArea.addClass('hovering');
})
.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
$hoverArea.removeClass('hovering');
}
});
$area
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function (e) {
e.stopPropagation();
var dropped = e.originalEvent.dataTransfer.files;
counter = 0;
$hoverArea.removeClass('hovering');
onFileDrop(dropped, e);
});
};
var createUploader = function ($area, $hover, $body) {
if (!config.noHandlers) {
createAreaHandlers($area, null);
}
createTableContainer($body);
};
createUploader(config.dropArea, config.hoverArea, config.body);
return File;
};
return module;
});

@ -37,6 +37,17 @@ define([
var parsed = config.href ? common.parsePadUrl(config.href) : {};
var secret = common.getSecrets(parsed.type, parsed.hash);
History.readOnly = 1;
if (!secret.keys) {
secret.keys = secret.key;
History.readOnly = 2;
}
else if (!secret.keys.validateKey) {
secret.keys.validateKey = true;
History.readOnly = 0;
}
var crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () {
@ -185,6 +196,7 @@ define([
'class':'revertHistory buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (!History.readOnly) { $rev.hide(); }
onUpdate = function () {
$cur.attr('max', states.length);

@ -14,6 +14,7 @@ define(function () {
var getHeadingText = cfg.getHeadingText || function () { return; };
var updateLocalTitle = function (newTitle) {
exp.title = newTitle;
onLocal();
if (typeof cfg.updateLocalTitle === "function") {
cfg.updateLocalTitle(newTitle);
} else {
@ -43,11 +44,12 @@ define(function () {
onLocal();
};
exp.updateTitle = function (newTitle) {
// update title: href is optional; if not specified, we use window.location.href
exp.updateTitle = function (newTitle, href) {
if (newTitle === exp.title) { return; }
// Change the title now, and set it back to the old value if there is an error
var oldTitle = exp.title;
Cryptpad.renamePad(newTitle, function (err, data) {
Cryptpad.renamePad(newTitle, href, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
console.error(err);

@ -68,7 +68,11 @@ define([], function () {
Util.replaceHash = function (hash) {
if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
return void window.history.replaceState({}, window.document.title, hash);
void window.history.replaceState({}, window.document.title, hash);
if (typeof(window.onhashchange) === 'function') {
window.onhashchange();
}
return;
}
window.location.hash = hash;
};

@ -11,11 +11,13 @@ define([
'/common/common-title.js',
'/common/common-metadata.js',
'/common/common-codemirror.js',
'/common/common-file.js',
'/common/clipboard.js',
'/common/pinpad.js',
'/customize/application_config.js'
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) {
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata,
CodeMirror, Files, Clipboard, Pinpad, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@ -114,6 +116,9 @@ define([
// CodeMirror
common.createCodemirror = CodeMirror.create;
// Files
common.createFileManager = function (config) { return Files.create(common, config); };
// History
common.getHistory = function (config) { return History.create(common, config); };
@ -126,6 +131,11 @@ define([
return store.getProxy().proxy;
}
};
common.getFO = function () {
if (store && store.getProxy()) {
return store.getProxy().fo;
}
};
var getNetwork = common.getNetwork = function () {
if (store) {
if (store.getProxy() && store.getProxy().info) {
@ -144,7 +154,6 @@ define([
}
var href = '/common/feedback.html?' + action + '=' + (+new Date());
console.log('[feedback] %s', href);
$.ajax({
type: "HEAD",
url: href,
@ -300,7 +309,7 @@ define([
cb(parsed);
}
if (!pad.title) {
pad.title = common.getDefaultname(parsed);
pad.title = common.getDefaultName(parsed);
}
return parsed.hashData;
};
@ -520,8 +529,8 @@ define([
cb ("store.forgetPad is not a function");
};
common.setPadTitle = function (name, cb) {
var href = window.location.href;
common.setPadTitle = function (name, padHref, cb) {
var href = padHref || window.location.href;
var parsed = parsePadUrl(href);
if (!parsed.hash) { return; }
href = getRelativeHref(href);
@ -581,25 +590,26 @@ define([
return pad;
});
if (updateWeaker.length > 0) {
updateWeaker.forEach(function (obj) {
// If we have a stronger url, and if all the occurences of the weaker were
// in the trash, add remove them from the trash and add the stronger in root
getStore().restoreHref(obj.n);
});
}
if (!contains && href) {
var data = makePad(href, name);
getStore().pushData(data, function (e, id) {
if (e) {
if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true);
return;
}
else { throw new Error("Cannot push this pad to CryptDrive", e); }
return void cb(e);
}
getStore().addPad(id, common.initialPath);
cb(err, recent);
});
}
if (updateWeaker.length > 0) {
updateWeaker.forEach(function (obj) {
// If we have a stronger url, and if all the occurences of the weaker were
// in the trash, add remove them from the trash and add the stronger in root
getStore().restoreHref(obj.n);
});
return;
}
cb(err, recent);
});
@ -621,24 +631,41 @@ define([
/*
* Buttons
*/
common.renamePad = function (title, callback) {
common.renamePad = function (title, href, callback) {
if (title === null) { return; }
if (title.trim() === "") {
var parsed = parsePadUrl(window.location.href);
var parsed = parsePadUrl(href || window.location.href);
title = getDefaultName(parsed);
}
common.setPadTitle(title, function (err) {
common.setPadTitle(title, href, function (err) {
if (err) {
console.log("unable to set pad title");
console.log(err);
console.error(err);
return;
}
callback(null, title);
});
};
common.getUserFilesList = function () {
var store = common.getStore();
var proxy = store.getProxy();
var fo = proxy.fo;
var hashes = [];
var list = fo.getFiles().filter(function (id) {
var href = fo.getFileData(id).href;
var parsed = parsePadUrl(href);
if ((parsed.type === 'file' || parsed.type === 'media')
&& hashes.indexOf(parsed.hash) === -1) {
hashes.push(parsed.hash);
return true;
}
});
return list;
};
var getUserChannelList = common.getUserChannelList = function () {
var store = common.getStore();
var proxy = store.getProxy();
@ -879,6 +906,21 @@ define([
common.getPinnedUsage(todo);
};
var getAppSuffix = function () {
var parts = window.location.pathname.split('/')
.filter(function (x) { return x; });
if (!parts[0]) { return ''; }
return '_' + parts[0].toUpperCase();
};
var prepareFeedback = common.prepareFeedback = function (key) {
if (typeof(key) !== 'string') { return $.noop; }
return function () {
feedback(key.toUpperCase() + getAppSuffix());
};
};
common.createButton = function (type, rightside, data, callback) {
var button;
var size = "17px";
@ -887,6 +929,8 @@ define([
button = $('<button>', {
title: Messages.exportButtonTitle,
}).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'}));
button.click(prepareFeedback(type));
if (callback) {
button.click(callback);
}
@ -896,18 +940,40 @@ define([
title: Messages.importButtonTitle,
}).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'}));
if (callback) {
button.click(UI.importContent('text/plain', function (content, file) {
button
.click(prepareFeedback(type))
.click(UI.importContent('text/plain', function (content, file) {
callback(content, file);
}));
}
break;
case 'upload':
button = $('<button>', {
'class': 'btn btn-primary new',
title: Messages.uploadButtonTitle,
}).append($('<span>', {'class':'fa fa-upload'})).append(' '+Messages.uploadButton);
if (!data.FM) { return; }
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;'
}).on('change', function (e) {
var file = e.target.files[0];
var ev = {
target: data.target
};
data.FM.handleFile(file, ev);
if (callback) { callback(); }
});
button.click(function () { $input.click(); });
break;
case 'template':
if (!AppConfig.enableTemplates) { return; }
button = $('<button>', {
title: Messages.saveTemplateButton,
}).append($('<span>', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'}));
if (data.rt && data.Crypt) {
button.click(function () {
button
.click(function () {
var title = data.getTitle() || document.title;
var todo = function (val) {
if (typeof(val) !== "string") { return; }
@ -965,7 +1031,9 @@ define([
}
});
if (callback) {
button.click(function() {
button
.click(prepareFeedback(type))
.click(function() {
var href = window.location.href;
var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
common.confirm(msg, function (yes) {
@ -1021,7 +1089,9 @@ define([
style: 'font:'+size+' FontAwesome'
});
if (data.histConfig) {
button.click(function () {
button
.click(prepareFeedback(type))
.click(function () {
common.getHistory(data.histConfig);
});
}
@ -1030,7 +1100,8 @@ define([
button = $('<button>', {
'class': "fa fa-question",
style: 'font:'+size+' FontAwesome'
});
})
.click(prepareFeedback(type));
}
if (rightside) {
button.addClass('rightside-button');
@ -1378,8 +1449,8 @@ define([
initialized = true;
updateLocalVersion();
f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
}
};
@ -1422,6 +1493,7 @@ define([
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
document.location.reload();
return;
}

@ -1,8 +1,11 @@
define([
'jquery',
'/bower_components/marked/marked.min.js',
'/bower_components/diff-dom/diffDOM.js'
],function ($, Marked) {
'/common/cryptpad-common.js',
'/common/media-tag.js',
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
],function ($, Marked, Cryptpad, MediaTag) {
var DiffMd = {};
var DiffDOM = window.diffDOM;
@ -33,6 +36,20 @@ define([
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var mt = '<media-tag src="/blob/' + hexFileName.slice(0,2) + '/' + hexFileName + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
return mt;
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};
var forbiddenTags = [
'SCRIPT',
@ -43,6 +60,10 @@ define([
'AUDIO',
];
var unsafeTag = function (info) {
if (info.node && $(info.node).parents('media-tag').length) {
// Do not remove elements inside a media-tag
return true;
}
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
if (/^on/.test(info.diff.name)) {
console.log("Rejecting forbidden element attribute with name", info.diff.name);
@ -61,6 +82,7 @@ define([
}
};
var slice = function (coll) {
return Array.prototype.slice.call(coll);
};
@ -85,7 +107,7 @@ define([
var DD = new DiffDOM({
preDiffApply: function (info) {
if (unsafeTag(info)) { return true; }
}
},
});
var makeDiff = function (A, B, id) {
@ -119,9 +141,18 @@ define([
throw new Error(patch);
} else {
DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))');
$mts.each(function (i, el) {
MediaTag(el);
});
}
};
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
});
return DiffMd;
});

File diff suppressed because one or more lines are too long

@ -105,7 +105,7 @@ define([
var oldFo = FO.init(parsed.drive, {
Cryptpad: Cryptpad
});
var todo = function () {
var onMigrated = function () {
oldFo.fixFiles();
var newData = Cryptpad.getStore().getProxy();
var newFo = newData.fo;
@ -151,8 +151,10 @@ define([
proxy.FS_hashes = [];
}
proxy.FS_hashes.push(localStorage.FS_hash);
if (typeof(cb) === "function") { cb(); }
};
oldFo.migrate(todo);
oldFo.migrate(onMigrated);
return;
}
if (typeof(cb) === "function") { cb(); }
};

@ -63,6 +63,7 @@ types of messages:
// RPC responses are arrays. this message isn't meant for us.
return;
}
if (/FULL_HISTORY/.test(parsed[0])) { return; }
var response = parsed.slice(2);
@ -98,7 +99,7 @@ types of messages:
delete ctx.pending[txid];
return;
}
console.error("received message for txid[%s] with no callback", txid);
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
};
var create = function (network, edPrivateKey, edPublicKey, cb) {

@ -96,6 +96,10 @@ define([
} else {
styleToolbar($container);
}
$container.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
return $toolbar;
};

@ -520,7 +520,7 @@ define([
// ADD
var add = exp.add = function (id, path) {
if (!Cryptpad.isLoggedIn()) { return; }
if (!Cryptpad.isLoggedIn() && !config.testMode) { return; }
var data = files[FILES_DATA][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
@ -559,7 +559,7 @@ define([
exp.forget = function (href) {
var id = getIdFromHref(href);
if (!id) { return; }
if (!Cryptpad.isLoggedIn()) {
if (!Cryptpad.isLoggedIn() && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
@ -588,7 +588,7 @@ define([
};
var checkDeletedFiles = function () {
// Nothing in OLD_FILES_DATA for workgroups
if (workgroup || !Cryptpad.isLoggedIn()) { return; }
if (workgroup || (!Cryptpad.isLoggedIn() && !config.testMode)) { return; }
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
var fData = files[FILES_DATA];
@ -617,7 +617,7 @@ define([
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
if (!Cryptpad.isLoggedIn()) {
if (!Cryptpad.isLoggedIn() && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = find(path);
if (!el) { return; }
@ -967,7 +967,7 @@ define([
toClean.push(id);
continue;
}
if (Cryptpad.isLoggedIn() && rootFiles.indexOf(id) === -1) {
if ((Cryptpad.isLoggedIn() || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Cryptpad.createChannelId();
root[newName] = id;

@ -132,7 +132,7 @@ span.fa-folder-open {
width: calc(100% - 30px);
}
#tree li > span.element-row {
width: calc(100% + 5px);
min-width: calc(100% + 5px);
display: inline-block;
cursor: pointer;
margin-left: -5px;
@ -223,6 +223,11 @@ span.fa-folder-open {
display: none;
}
/* CONTENT */
#rightCol {
display: flex;
flex-flow: column;
flex: 1;
}
#content {
box-sizing: border-box;
background: #fff;
@ -340,9 +345,9 @@ span.fa-folder-open {
width: 100%;
height: 48px;
margin: 8px 0;
display: inline-flex;
justify-content: center;
display: inline-block;
overflow: hidden;
word-wrap: break-word;
}
#content div.grid li.element {
position: relative;
@ -450,9 +455,6 @@ span.fa-folder-open {
#driveToolbar {
background: #ddd;
color: #555;
height: 30px;
display: flex;
flex-flow: row;
border-top: 1px solid #ccc;
border-bottom: ;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
@ -516,21 +518,25 @@ span.fa-folder-open {
width: 250px;
margin: 0;
padding: 0;
display: inline-block;
}
#driveToolbar .rightside {
margin: 0;
padding: 0;
flex: 1;
display: inline-block;
float: right;
}
#driveToolbar .path {
display: inline-block;
height: 100%;
width: 100%;
height: 30px;
line-height: 30px;
cursor: default;
width: auto;
overflow: hidden;
white-space: nowrap;
direction: rtl;
max-width: 100%;
text-align: left;
}
#driveToolbar .path .element {
padding: 5px;

@ -171,7 +171,7 @@ span {
width: ~"calc(100% - 30px)";
}
& > span.element-row {
width: ~"calc(100% + 5px)";
min-width: ~"calc(100% + 5px)";
display: inline-block;
cursor: pointer;
margin-left: -5px;
@ -272,7 +272,11 @@ span {
}
/* CONTENT */
#rightCol {
display: flex;
flex-flow: column;
flex: 1;
}
#content {
box-sizing: border-box;
background: @content-bg;
@ -398,11 +402,12 @@ span {
width: 100%;
height: 48px;
margin: 8px 0;
display: inline-flex;
display: inline-block;
//align-items: center;
justify-content: center;
//justify-content: center;
overflow: hidden;
//text-overflow: ellipsis;
word-wrap: break-word;
}
&.element {
position: relative;
@ -519,9 +524,9 @@ span {
#driveToolbar {
background: @toolbar-bg;
color: @toolbar-fg;
height: 30px;
display: flex;
flex-flow: row;
//height: 30px;
//display: flex;
//flex-flow: row;
border-top: 1px solid @toolbar-border-col;
border-bottom: ;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
@ -586,21 +591,25 @@ span {
width: 250px;
margin: 0;
padding: 0;
display: inline-block;
}
.rightside {
margin: 0;
padding: 0;
flex: 1;
display: inline-block;
float: right;
}
.path {
display: inline-block;
height: 100%;
width: 100%;
height: 30px;
line-height: 30px;
cursor: default;
width: auto;
overflow: hidden;
white-space: nowrap;
direction: rtl;
max-width: 100%;
text-align: left;
.element {
padding: 5px;
border: 1px solid @toolbar-bg;

@ -10,13 +10,14 @@
</head>
<body>
<div id="toolbar" class="toolbar-container"></div>
<div id="driveToolbar"></div>
<div class="app-container" tabindex="0">
<div id="tree">
</div>
<div id="content" tabindex="2">
<div id="rightCol">
<div id="driveToolbar"></div>
<div id="content" tabindex="2"></div>
</div>
<div id="treeContextMenu" class="contextMenu dropdown clearfix">
<div id="treeContextMenu" class="contextMenu dropdown clearfix unselectable">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
@ -26,7 +27,7 @@
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
</ul>
</div>
<div id="contentContextMenu" class="contextMenu dropdown clearfix">
<div id="contentContextMenu" class="contextMenu dropdown clearfix unselectable">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" data-icon="fa-folder" class="newfolder editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
<li><a tabindex="-1" data-icon="fa-file-word-o" class="newdoc own editable dropdown-item" data-type="pad" data-localization="button_newpad">New pad</a></li>
@ -36,7 +37,7 @@
<li><a tabindex="-1" data-icon="fa-paint-brush" class="newdoc own editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
</ul>
</div>
<div id="defaultContextMenu" class="contextMenu dropdown clearfix">
<div id="defaultContextMenu" class="contextMenu dropdown clearfix unselectable">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
@ -44,12 +45,12 @@
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
</ul>
</div>
<div id="trashTreeContextMenu" class="contextMenu dropdown clearfix">
<div id="trashTreeContextMenu" class="contextMenu dropdown clearfix unselectable">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" data-icon="fa-trash-o" class="empty editable dropdown-item" data-localization="fc_empty">Empty the trash</a></li>
</ul>
</div>
<div id="trashContextMenu" class="contextMenu dropdown clearfix">
<div id="trashContextMenu" class="contextMenu dropdown clearfix unselectable">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" data-icon="fa-eraser" class="remove editable dropdown-item" data-localization="fc_remove">Delete permanently</a></li>
<li><a tabindex="-1" data-icon="fa-repeat" class="restore editable dropdown-item" data-localization="fc_restore">Restore</a></li>

@ -205,6 +205,17 @@ define([
var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu");
var $trashContextMenu = $iframe.find("#trashContextMenu");
$tree.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
$driveToolbar.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
// TOOLBAR
/* add a "change username" button */
@ -317,6 +328,7 @@ define([
width: '0px',
height: '0px'
});
module.hideMenu(e);
if (sel.move) { return; }
sel.move = function (ev) {
var rectMove = ev.currentTarget.getBoundingClientRect(),
@ -638,12 +650,7 @@ define([
};
var updatePathSize = function () {
var $context = $iframe.find('#contextButtonsContainer');
var l = 50;
if ($context.length) {
l += $context.width() || 0;
}
$driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - '+l+'px)');
$driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - 50px)');
};
var getSelectedPaths = function ($element) {
@ -954,23 +961,7 @@ define([
if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) {
return;
}
// "force" is currently unused but may be configurable by user
if (newPath[0] !== TRASH || force) {
andThen();
return;
}
var msg = Messages._getKey('fm_removeSeveralDialog', [paths.length]);
if (paths.length === 1) {
var path = paths[0].slice();
var el = filesOp.find(path);
var name = filesOp.isFile(el) ? getElementName(path) : path.pop();
msg = Messages._getKey('fm_removeDialog', [name]);
}
Cryptpad.confirm(msg, function (res) {
$(ifrw).focus();
if (!res) { return; }
andThen();
});
};
// Drag & drop:
// The data transferred is a stringified JSON containing the path of the dragged element
@ -1012,13 +1003,30 @@ define([
ev.dataTransfer.setData("text", stringify(data));
};
var onFileDrop = APP.onFileDrop = function (file, e) {
APP.FM.onFileDrop(file, e);
};
var findDropPath = function (target) {
var $target = $(target);
var $el = findDataHolder($target);
var newPath = $el.data('path');
if ((!newPath || filesOp.isFile(filesOp.find(newPath)))
&& $target.parents('#content')) {
newPath = currentPath;
}
return newPath;
};
var onDrop = function (ev) {
ev.preventDefault();
$iframe.find('.droppable').removeClass('droppable');
var data = ev.dataTransfer.getData("text");
// Don't the the normal drop handler for file upload
var fileDrop = ev.dataTransfer.files;
if (fileDrop.length) { return void onFileDrop(fileDrop, ev); }
var oldPaths = JSON.parse(data).path;
if (!oldPaths) { return; }
// Dropped elements can be moved from the same file manager or imported from another one.
// A moved element should be removed from its previous location
var movedPaths = [];
@ -1032,8 +1040,7 @@ define([
}
});
var $el = findDataHolder($(ev.target));
var newPath = $el.data('path');
var newPath = findDropPath(ev.target);
if (!newPath) { return; }
if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh);
@ -1069,6 +1076,8 @@ define([
e.preventDefault();
});
$element.on('drop', function (e) {
e.preventDefault();
e.stopPropagation();
onDrop(e.originalEvent);
});
$element.on('dragenter', function (e) {
@ -1087,6 +1096,7 @@ define([
}
});
};
addDragAndDropHandlers($content, null, true, true);
// In list mode, display metadata from the filesData object
// _WORKGROUP_ : Do not display title, atime and ctime columns since we don't have files data
@ -1247,7 +1257,7 @@ define([
var createTitle = function (path, noStyle) {
if (!path || path.length === 0) { return; }
var isTrash = filesOp.isPathIn(path, [TRASH]);
var $title = $('<span>', {'class': 'path unselectable'});
var $title = $driveToolbar.find('.path');
if (APP.mobile()) {
return $title;
}
@ -1427,6 +1437,16 @@ define([
return $block;
};
var createUploadButton = function () {
var inTrash = filesOp.isPathIn(currentPath, [TRASH]);
if (inTrash) { return; }
var data = {
FM: APP.FM,
target: $content[0]
};
return Cryptpad.createButton('upload', false, data);
};
var hideNewButton = function () {
$iframe.find('.dropdown-bar-content').hide();
};
@ -1599,11 +1619,9 @@ define([
var createToolbar = function () {
var $toolbar = $driveToolbar;
$toolbar.html('');
var $leftside = $('<div>', {'class': 'leftside'}).appendTo($toolbar);
if (!APP.mobile()) {
$leftside.width($tree.width());
}
$('<div>', {'class': 'leftside'}).appendTo($toolbar);
$('<div>', {'class': 'rightside'}).appendTo($toolbar);
$('<div>', {'class': 'path unselectable'}).appendTo($toolbar);
return $toolbar;
};
@ -1765,6 +1783,7 @@ define([
module.hideMenu();
if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; }
// Only Trash and Root are available in not-owned files manager
if (displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError);
@ -1798,7 +1817,6 @@ define([
if (!isSearch) { delete APP.Search.oldLocation; }
module.resetTree();
if (displayedCategories.indexOf(SEARCH) !== -1 && $tree.find('#searchInput').length) {
// in history mode we want to focus the version number input
if (!history.isHistoryMode && !APP.mobile()) {
@ -1828,7 +1846,7 @@ define([
}
var $list = $('<ul>').appendTo($dirContent);
createTitle(path).appendTo($toolbar.find('.rightside'));
createTitle(path).appendTo($toolbar.find('.path'));
updatePathSize();
if (APP.mobile()) {
@ -1863,6 +1881,7 @@ define([
// NewButton can be undefined if we're in read only mode
$toolbar.find('.leftside').append(createNewButton(isInRoot));
$toolbar.find('.leftside').append(createUploadButton());
var $folderHeader = getFolderListHeader();
@ -2375,8 +2394,7 @@ define([
var name = paths[0].path[paths[0].path.length - 1];
if ($(this).hasClass("remove")) {
if (paths.length === 1) {
if (path.length === 4) { name = path[1]; }
Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) {
Cryptpad.confirm(Messages.fm_removePermanentlyDialog, function(res) {
if (!res) { return; }
filesOp.delete([path], refresh);
});
@ -2392,7 +2410,14 @@ define([
}
else if ($(this).hasClass("restore")) {
if (paths.length !== 1) { return; }
if (path.length === 4) { name = path[1]; }
if (path.length === 4) {
var el = filesOp.find(path);
if (filesOp.isFile(el)) {
name = filesOp.getTitle(el);
} else {
name = path[1];
}
}
Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) {
if (!res) { return; }
filesOp.restore(path, refresh);
@ -2413,7 +2438,7 @@ define([
e.preventDefault();
});
$appContainer.on('mouseup', function (e) {
if (sel.down) { return; }
//if (sel.down) { return; }
if (e.which !== 1) { return ; }
module.hideMenu(e);
//removeSelected(e);
@ -2511,22 +2536,10 @@ define([
}
});
$iframe.find('#tree').mousedown(function () {
if (APP.mobile()) { return; }
if (APP.resizeTree) { return; }
APP.resizeTree = window.setInterval(function () {
$driveToolbar.find('.leftside').width($tree.width());
updatePathSize();
}, 100);
});
$appContainer.mouseup(function () {
window.clearInterval(APP.resizeTree);
APP.resizeTree = undefined;
});
history.onEnterHistory = function (obj) {
var files = obj.drive;
filesOp = FO.init(files, config);
appStatus.isReady = true;
refresh();
};
history.onLeaveHistory = function () {
@ -2550,15 +2563,38 @@ define([
filesOp.pushData(data, function (e, id) {
if (e) { return void console.error("Error while creating the default pad:", e); } // TODO LIMIT?
filesOp.add(id);
});
if (typeof(cb) === "function") { cb(); }
});
});
delete sessionStorage.createReadme;
return;
}
if (typeof(cb) === "function") { cb(); }
};
var fmConfig = {
noHandlers: true,
onUploaded: function (ev, data) {
try {
// Get the folder path
var newPath = findDropPath(ev.target);
if (!newPath) { return void refresh(); }
var href = data.url;
// Get the current file location in ROOT
var id = filesOp.getIdFromHref(href);
var paths = filesOp.findFile(id);
if (paths.length !== 1) { return; }
// Try to move and refresh
moveElements([paths[0]], newPath, true);
refresh();
} catch (e) {
console.error(e);
refresh();
}
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
createReadme(proxy, function () {
refresh();
APP.userList.onChange();
@ -2657,7 +2693,7 @@ define([
var userList = APP.userList = info.userList;
var config = {
displayed: ['useradmin', 'spinner', 'lag', 'state', 'limit'],
displayed: ['useradmin', 'spinner', 'lag', 'state', 'limit', 'newpad'],
userList: {
list: userList,
userNetfluxId: info.myID
@ -2689,7 +2725,7 @@ define([
if (err) { return void logError(err); }
$leftside.html('');
$leftside.append($limitContainer);
});
}, true);
/* add a history button */
var histConfig = {
@ -2704,7 +2740,7 @@ define([
history.onEnterHistory(obj);
},
$toolbar: APP.$bar,
href: window.location.origin + window.location.pathname + APP.hash
href: window.location.origin + window.location.pathname + '#' + APP.hash
};
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$rightside.append($hist);

@ -6,6 +6,19 @@ body {
#toolbar {
display: flex;
}
body {
display: flex;
flex-flow: column;
}
#app {
flex: 1;
background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
}
.cryptpad-toolbar {
padding: 0px;
display: inline-block;
@ -25,6 +38,10 @@ body {
position: absolute;
z-index: -1;
}
media-tag img {
max-width: 100%;
max-height: calc(100vh - 64px);
}
#upload-form,
#download-form {
padding: 0px;
@ -48,6 +65,19 @@ body {
height: 50vh;
box-sizing: border-box;
}
#download-form label {
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
word-wrap: break-word;
}
#download-form label span {
width: 50vh;
max-width: 80vw;
text-align: center;
line-height: 1.5em;
}
.hovering {
background-color: rgba(255, 0, 115, 0.5) !important;
}
@ -58,20 +88,14 @@ body {
display: none;
}
.inputfile + label {
border: 2px solid black;
background-color: rgba(50, 50, 50, 0.1);
display: block;
}
.inputfile:focus + label,
.inputfile + label:hover {
background-color: rgba(50, 50, 50, 0.3);
}
#progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
transition: width 500ms;
transition: width 200ms;
width: 0%;
max-width: 100%;
max-height: 100%;
@ -79,42 +103,8 @@ body {
z-index: 10000;
display: block;
}
#status {
display: none;
width: 80vw;
margin-top: 50px;
margin-left: 10vw;
border: 1px solid black;
border-collapse: collapse;
}
#status tr:nth-child(1) {
background-color: #ccc;
border: 1px solid #999;
}
#status tr:nth-child(1) td {
text-align: center;
}
#status td {
border-left: 1px solid #BBB;
border-right: 1px solid #BBB;
padding: 0 10px;
}
#status .upProgress {
width: 200px;
position: relative;
text-align: center;
}
#status .progressContainer {
position: absolute;
width: 0px;
left: 5px;
top: 1px;
bottom: 1px;
background-color: rgba(0, 0, 255, 0.3);
}
#status .upCancel {
text-align: center;
}
#status .fa.cancel {
color: #ff0073;
body #uploadStatusContainer {
background-color: rgba(255, 255, 255, 0.9);
color: black;
opacity: 0.9;
}

@ -12,6 +12,19 @@ html, body {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
body {
display: flex;
flex-flow: column;
}
#app {
flex: 1;
background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
}
.cryptpad-toolbar {
padding: 0px;
display: inline-block;
@ -32,6 +45,13 @@ html, body {
z-index: -1;
}
media-tag {
img {
max-width: 100%;
max-height: ~"calc(100vh - 64px)";
}
}
#upload-form, #download-form {
padding: 0px;
margin: 0px;
@ -54,6 +74,21 @@ html, body {
box-sizing: border-box;
}
}
#download-form {
label {
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
word-wrap: break-word;
span {
width: 50vh;
max-width: 80vw;
text-align: center;
line-height: 1.5em;
}
}
}
.hovering {
background-color: rgba(255, 0, 115, 0.5) !important;
}
@ -65,14 +100,14 @@ html, body {
display: none;
}
.inputfile + label {
border: 2px solid black;
background-color: rgba(50, 50, 50, .10);
//border: 2px solid black;
//background-color: rgba(50, 50, 50, .10);
display: block;
}
.inputfile:focus + label,
.inputfile + label:hover {
background-color: rgba(50, 50, 50, 0.30);
//background-color: rgba(50, 50, 50, 0.30);
}
#progress {
@ -82,7 +117,7 @@ html, body {
height: 100%;
transition: width 500ms;
transition: width 200ms;
width: 0%;
max-width: 100%;
max-height: 100%;
@ -91,38 +126,8 @@ html, body {
display: block;
}
#status {
display: none;
width: 80vw;
margin-top: 50px;
margin-left: 10vw;
border: 1px solid black;
border-collapse: collapse;
tr:nth-child(1) {
background-color: #ccc;
border: 1px solid #999;
td { text-align: center; }
}
td {
border-left: 1px solid #BBB;
border-right: 1px solid #BBB;
padding: 0 10px;
}
.upProgress {
width: 200px;
position: relative;
text-align: center;
body #uploadStatusContainer {
background-color: rgba(255, 255, 255, 0.9);
color: black;
opacity: 0.9;
}
.progressContainer {
position: absolute;
width: 0px;
left: 5px;
top: 1px; bottom: 1px;
background-color: rgba(0,0,255,0.3);
}
.upCancel { text-align: center; }
.fa.cancel {
color: rgb(255, 0, 115);
}
}

@ -10,27 +10,23 @@
</head>
<body>
<div id="toolbar" class="toolbar-container"></div>
<div id="app">
<div id="upload-form" style="display: none;">
<input type="file" name="file" id="file" class="inputfile" />
<label for="file" class="block unselectable" data-localization-title="upload_choose"
<label for="file" class="btn btn-primary block unselectable" data-localization-title="upload_choose"
data-localization="upload_choose"></label>
</div>
<div id="download-form" style="display: none;">
<input type="button" name="dl" id="dl" class="inputfile" />
<label for="dl" class="block unselectable" data-localization-title="download_button"
data-localization="download_button"></label>
<label for="dl" class="btn btn-success block unselectable" data-localization-title="download_button"><span data-localization="download_button"></span></label>
<span class="block" id="progress"></span>
</div>
<table id="status" style="display: none;">
<tr>
<td data-localization="upload_name">File name</td>
<td data-localization="upload_size">Size</td>
<td data-localization="upload_progress">Progress</td>
<td data-localization="cancel">Cancel</td>
</tr>
</table>
<div id="download-view" style="display: none;">
<media-tag id="encryptedFile"></media-tag>
</div>
<div id="feedback" class="block hidden">
</div>
</div>
</body>
</html>

@ -7,209 +7,38 @@ define([
'/common/visible.js',
'/common/notify.js',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
var Messages = Cryptpad.Messages;
var saveAs = window.saveAs;
var Nacl = window.nacl;
var APP = {};
var APP = window.APP = {};
$(function () {
var andThen = function () {
var ifrw = $('#pad-iframe')[0].contentWindow;
var $iframe = $('#pad-iframe').contents();
var $appContainer = $iframe.find('#app');
var $form = $iframe.find('#upload-form');
var $dlform = $iframe.find('#download-form');
var $dlview = $iframe.find('#download-view');
var $label = $form.find('label');
var $table = $iframe.find('#status');
var $dllabel = $dlform.find('label span');
var $progress = $iframe.find('#progress');
var $body = $iframe.find('body');
$iframe.find('body').on('dragover', function (e) { e.preventDefault(); });
$iframe.find('body').on('drop', function (e) { e.preventDefault(); });
$body.on('dragover', function (e) { e.preventDefault(); });
$body.on('drop', function (e) { e.preventDefault(); });
Cryptpad.addLoadingScreen();
var Title;
var myFile;
var myDataType;
var queue = {
queue: [],
inProgress: false
};
var uid = function () {
return 'file-' + String(Math.random()).substring(2);
};
var upload = function (blob, metadata, id) {
console.log(metadata);
if (queue.inProgress) { return; }
queue.inProgress = true;
var $row = $table.find('tr[id="'+id+'"]');
$row.find('.upCancel').html('-');
var $pv = $row.find('.progressValue');
var $pb = $row.find('.progressContainer');
var $link = $row.find('.upLink');
var updateProgress = function (progressValue) {
$pv.text(Math.round(progressValue*100)/100 + '%');
$pb.css({
width: (progressValue/100)*188+'px'
});
};
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
Cryptpad.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
console.log(box);
cb(e, msg);
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
Cryptpad.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = Cryptpad.getFileHashFromKeys(id, b64Key);
$link.attr('href', '/file/#' + hash)
.click(function (e) {
e.preventDefault();
window.open($link.attr('href'), '_blank');
});
// TODO add button to table which copies link to clipboard?
//APP.toolbar.addElement(['fileshare'], {});
var title = document.title = metadata.name;
myFile = blob;
myDataType = metadata.type;
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
Title.updateTitle(title || defaultName);
APP.toolbar.title.show();
console.log(title);
Cryptpad.log(Messages._getKey('upload_success', [title]));
queue.inProgress = false;
queue.next();
});
};
Cryptpad.uploadStatus(estimate, function (e, pending) {
if (e) {
queue.inProgress = false;
queue.next();
if (e === 'TOO_LARGE') {
// TODO update table to say too big?
return void Cryptpad.alert(Messages.upload_tooLarge);
}
if (e === 'NOT_ENOUGH_SPACE') {
// TODO update table to say not enough space?
return void Cryptpad.alert(Messages.upload_notEnoughSpace);
}
console.error(e);
return void Cryptpad.alert(Messages.upload_serverError);
}
if (pending) {
// TODO keep this message in case of pending files in another window?
return void Cryptpad.confirm(Messages.upload_uploadPending, function (yes) {
if (!yes) { return; }
Cryptpad.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
var prettySize = function (bytes) {
var kB = Cryptpad.bytesToKilobytes(bytes);
if (kB < 1024) { return kB + Messages.KB; }
var mB = Cryptpad.bytesToMegabytes(bytes);
return mB + Messages.MB;
};
queue.next = function () {
if (queue.queue.length === 0) { return; }
if (queue.inProgress) { return; }
var file = queue.queue.shift();
upload(file.blob, file.metadata, file.id);
};
queue.push = function (obj) {
var id = uid();
obj.id = id;
queue.queue.push(obj);
$table.show();
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
var $progressBar = $('<div>', {'class':'progressContainer'});
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
var $tr = $('<tr>', {id: id}).appendTo($table);
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
$cancel.remove();
$tr.find('.upCancel').text('-');
$tr.find('.progressValue').text(Messages.upload_cancelled);
});
var $link = $('<a>', {
'class': 'upLink',
}).text(obj.metadata.name);
$('<td>').append($link).appendTo($tr);
$('<td>').text(prettySize(estimate)).appendTo($tr);
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
queue.next();
};
var uploadMode = false;
var andThen = function () {
var $bar = $iframe.find('.toolbar-container');
var secret;
@ -229,13 +58,6 @@ define([
return data ? data.title : undefined;
};
var exportFile = function () {
var filename = Cryptpad.fixFileName(document.title);
if (!(typeof(filename) === 'string' && filename)) { return; }
var blob = new Blob([myFile], {type: myDataType});
saveAs(blob, filename);
};
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade'];
@ -257,60 +79,112 @@ define([
if (uploadMode) { toolbar.title.hide(); }
var $rightside = toolbar.$rightside;
var $export = Cryptpad.createButton('export', true, {}, exportFile);
$rightside.append($export);
Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle);
if (!uploadMode) {
$dlform.show();
var src = Cryptpad.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { return void console.error(e); }
var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle);
var displayFile = function (ev) {
var $mt = $dlview.find('media-tag');
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Cryptpad.base64ToHex(secret.channel);
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
console.log(decrypted);
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
if (!$dlButton.length) {
$appContainer.css('background', 'white');
}
$dlButton.addClass('btn btn-success');
toolbar.$rightside.append(Cryptpad.createButton('export', true, {}, function () {
saveAs(decrypted.blob, decrypted.metadata.name);
}))
.append(Cryptpad.createButton('forget', true, {}, function () {
// not sure what to do here
}));
// make pdfs big
$iframe.find('media-tag iframe').css({
'height': 'calc(100vh - 64px)',
width: 'calc(100vw - 15px)',
});
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
Cryptpad.alert(error.message);
})
.on('decryptionProgress', function (e) {
var progress = e.originalEvent;
var p = progress.percent +'%';
$progress.width(p);
console.log(progress.percent);
});
require(['/common/media-tag.js'], function (MediaTag) {
/**
* Allowed mime types that have to be set for a rendering after a decryption.
*
* @type {Array}
*/
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'audio/mp3',
'audio/ogg',
'audio/wav',
'audio/webm',
'video/mp4',
'video/ogg',
'video/webm',
'application/pdf',
'application/dash+xml',
'download'
];
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
MediaTag($mt[0]);
});
};
var todoBigFile = function (sizeMb) {
$dlform.show();
Cryptpad.removeLoadingScreen();
$dllabel.append($('<br>'));
$dllabel.append(metadata.name);
$dllabel.append($('<br>'));
$dllabel.append(Messages._getKey('formattedMB', [sizeMb]));
var decrypting = false;
$dlform.find('#dl, #progress').click(function () {
var onClick = function (ev) {
if (decrypting) { return; }
if (myFile) { return void exportFile(); }
decrypting = true;
return Cryptpad.fetch(src, function (e, u8) {
if (e) {
decrypting = false;
return void Cryptpad.alert(e);
}
// now decrypt the u8
if (!u8 || !u8.length) {
return void Cryptpad.errorLoadingScreen(e);
}
FileCrypto.decrypt(u8, key, function (e, data) {
if (e) {
decrypting = false;
return console.error(e);
}
console.log(data);
var title = document.title = data.metadata.name;
myFile = data.content;
myDataType = data.metadata.type;
Title.updateTitle(title || Title.defaultTitle);
exportFile();
decrypting = false;
}, function (progress) {
var p = progress * 100 +'%';
$progress.width(p);
console.error(progress);
});
});
displayFile(ev);
};
if (sizeMb < 5) { return void onClick(); }
$dlform.find('#dl, #progress').click(onClick);
};
Cryptpad.getFileSize(window.location.href, function (e, data) {
if (e) { return void Cryptpad.errorLoadingScreen(e); }
var size = Cryptpad.bytesToMegabytes(data);
return void todoBigFile(size);
});
});
return;
@ -329,57 +203,18 @@ define([
display: 'block',
});
var handleFile = function (file) {
console.log(file);
var reader = new FileReader();
reader.onloadend = function () {
queue.push({
blob: this.result,
metadata: {
name: file.name,
type: file.type,
}
});
};
reader.readAsArrayBuffer(file);
var fmConfig = {
dropArea: $form,
hoverArea: $label,
body: $body,
keepTable: true // Don't fadeOut the tbale with the uploaded files
};
var FM = Cryptpad.createFileManager(fmConfig);
$form.find("#file").on('change', function (e) {
var file = e.target.files[0];
handleFile(file);
});
var counter = 0;
$label
.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$label.addClass('hovering');
})
.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
$label.removeClass('hovering');
}
});
$form
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function (e) {
e.stopPropagation();
var dropped = e.originalEvent.dataTransfer.files;
counter = 0;
$label.removeClass('hovering');
Array.prototype.slice.call(dropped).forEach(function (d) {
handleFile(d);
});
FM.handleFile(file);
});
// we're in upload mode

@ -64,7 +64,12 @@ define([
$('button.login').click();
});
var hashing = false;
$('button.login').click(function () {
if (hashing) { return void console.log("hashing is already in progress"); }
hashing = true;
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
window.setTimeout(function () {
Cryptpad.addLoadingScreen(Messages.login_hashing);
@ -89,6 +94,7 @@ define([
Cryptpad.feedback('LOGIN', true);
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
Cryptpad.login(result.userHash, result.userName, function () {
hashing = false;
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
@ -107,17 +113,23 @@ define([
switch (err) {
case 'NO_SUCH_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_noSuchUser);
Cryptpad.alert(Messages.login_noSuchUser, function () {
hashing = false;
});
});
break;
case 'INVAL_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalUser);
Cryptpad.alert(Messages.login_invalUser, function () {
hashing = false;
});
});
break;
case 'INVAL_PASS':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalPass);
Cryptpad.alert(Messages.login_invalPass, function () {
hashing = false;
});
});
break;
default: // UNHANDLED ERROR

@ -8,17 +8,26 @@
<style>
html, body {
margin: 0px;
height: 100%;
}
.cryptpad-toolbar {
margin-bottom: 1px;
padding: 0px;
display: inline-block;
}
media-tag * {
max-width: 100%;
margin: auto;
display: block;
}
media-tag *:not(button) {
height: 100%;
}
media-tag video {
min-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>

@ -6,8 +6,8 @@ define([
'/common/cryptpad-common.js',
//'/common/visible.js',
//'/common/notify.js',
'pdfjs-dist/build/pdf',
'pdfjs-dist/build/pdf.worker',
//'pdfjs-dist/build/pdf',
//'pdfjs-dist/build/pdf.worker',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {

@ -487,8 +487,14 @@ define([
var updateIcon = function () {
$collapse.removeClass('fa-caret-down').removeClass('fa-caret-up');
var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible');
if (isCollapsed) { $collapse.addClass('fa-caret-down'); }
else { $collapse.addClass('fa-caret-up'); }
if (isCollapsed) {
if (!initializing) { Cryptpad.feedback('HIDETOOLBAR_PAD'); }
$collapse.addClass('fa-caret-down');
}
else {
if (!initializing) { Cryptpad.feedback('SHOWTOOLBAR_PAD'); }
$collapse.addClass('fa-caret-up');
}
};
updateIcon();
$collapse.click(function () {
@ -666,6 +672,19 @@ define([
onLocal();
return test;
};
$bar.find('.cke_button').click(function () {
var e = this;
var classString = e.getAttribute('class');
var classes = classString.split(' ').filter(function (c) {
return /cke_button__/.test(c);
});
var id = classes[0];
if (typeof(id) === 'string') {
Cryptpad.feedback(id.toUpperCase());
}
});
});
};

@ -29,6 +29,7 @@
<div class="upper">
<button id="publish" data-localization-title="poll_publish_button" data-localization="poll_publish_button" style="display: none;">publish poll</button>
<button id="admin" data-localization-title="poll_admin_button" data-localization="poll_admin_button" style="display: none;">admin</button>
<button id="help" data-localization-title="poll_show_help_button" data-localization="poll_show_help_button">help</button>
</div>
<div class="realtime">

@ -46,7 +46,8 @@ define([
editable: {
row: [],
col: []
}
},
locked: false
};
var sortColumns = function (order, firstcol) {
@ -97,7 +98,7 @@ define([
// Enable the checkboxes for the user's column (committed or not)
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
$('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled');
$('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
if (isOwnColumnCommitted()) { return; }
@ -108,12 +109,14 @@ define([
var unlockElements = function () {
APP.editable.row.forEach(function (id) {
$('input[type="text"][disabled="disabled"][data-rt-id="' + id + '"]').removeAttr('disabled');
var $input = $('input[type="text"][disabled="disabled"][data-rt-id="' + id + '"]').removeAttr('disabled');
$input.parent().parent().addClass('editing');
$('span.edit[data-rt-id="' + id + '"]').css('visibility', 'hidden');
});
APP.editable.col.forEach(function (id) {
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
var $input = $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
$input.parent().addClass('editing');
$('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled');
$('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
});
};
@ -274,10 +277,17 @@ define([
Render.setValue(object, id, input.value);
change(null, null, null, 50);
break;
case 'checkbox':
debug("checkbox[tr-id='%s'] %s", id, input.checked);
case 'number':
debug("checkbox[tr-id='%s'] %s", id, input.value);
if (APP.editable.col.indexOf(x) >= 0 || x === APP.userid) {
Render.setValue(object, id, input.checked);
var value = parseInt(input.value);
if (isNaN(value)) {
console.error("Got NaN?!");
break;
}
Render.setValue(object, id, value);
change();
} else {
debug('checkbox locked');
@ -290,12 +300,14 @@ define([
};
var hideInputs = function (target, isKeyup) {
if (APP.locked) { return; }
if (!isKeyup && $(target).is('[type="text"]')) {
return;
}
$('.lock[data-rt-id!="' + APP.userid + '"]').addClass('fa-lock').removeClass('fa-unlock').attr('title', Messages.poll_locked);
var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td');
$cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true);
$cells.removeClass('editing');
$('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible');
APP.editable.col = [APP.userid];
APP.editable.row = [];
@ -349,9 +361,13 @@ define([
};
var handleClick = function (e, isKeyup) {
if (APP.locked) { return; }
e.stopPropagation();
if (!APP.ready) { return; }
if (!isKeyup && e.which !== 1) { return; } // only allow left clicks
var target = e && e.target;
if (!target) { return void debug("NO TARGET"); }
@ -369,10 +385,19 @@ define([
hideInputs(target, isKeyup);
break;
}
if ($(target).is('input[type="number"]')) { console.error("number input focused?"); break; }
handleInput(target);
break;
case 'LABEL':
var input = $('input[type="number"][id=' + $(target).attr('for') + ']');
var value = parseInt(input.val());
input.val((value + 1) % 4);
handleInput(input[0]);
break;
case 'SPAN':
//case 'LABEL':
if (shouldLock) {
break;
}
@ -421,6 +446,15 @@ define([
});
};
var showHelp = function(help) {
if (typeof help === 'undefined') { help = !$('#howItWorks').is(':visible'); }
var msg = (help ? Messages.poll_hide_help_button : Messages.poll_show_help_button);
$('#howItWorks').toggle(help);
$('#help').text(msg).attr('title', msg);
};
var Title;
var UserList;
@ -457,7 +491,7 @@ var ready = function (info, userid, readOnly) {
APP.$createRow = $('#create-option').click(function () {
Render.createRow(proxy, function (empty, id) {
change(null, null, null, null, function() {
$('.edit[data-rt-id="' + id + '"]').click();
handleSpan($('.edit[data-rt-id="' + id + '"]')[0]);
});
});
});
@ -465,7 +499,7 @@ var ready = function (info, userid, readOnly) {
APP.$createCol = $('#create-user').click(function () {
Render.createColumn(proxy, function (empty, id) {
change(null, null, null, null, function() {
$('.lock[data-rt-id="' + id + '"]').click();
handleSpan($('.lock[data-rt-id="' + id + '"]')[0]);
});
});
});
@ -486,12 +520,16 @@ var ready = function (info, userid, readOnly) {
publish(true);
});
// #publish button is removed in readonly
APP.$admin = $('#admin')
.click(function () {
publish(false);
});
APP.$help = $('#help')
.click(function () {
showHelp();
});
// Title
if (APP.proxy.info.defaultTitle) {
Title.updateDefaultTitle(APP.proxy.info.defaultTitle);
@ -527,7 +565,10 @@ var ready = function (info, userid, readOnly) {
.click(handleClick)
.on('keyup', function (e) { handleClick(e, true); });
$(window).click(hideInputs);
$(window).click(function(e) {
if (e.which !== 1) { return; }
hideInputs();
});
proxy
.on('change', ['info'], function (o, n, p) {
@ -570,14 +611,33 @@ var ready = function (info, userid, readOnly) {
UserList.getLastName(APP.toolbar.$userNameButton, isNew);
};
var setEditable = function (editable) {
APP.locked = !editable;
if (editable === false) {
// disable all the things
$('.realtime input, .realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked);
$('span.edit, span.remove').hide();
$('span.lock').addClass('fa-lock').removeClass('fa-unlock')
.attr('title', Messages.poll_locked)
.css({'cursor': 'default'});
} else {
// enable
$('span.edit, span.remove').show();
$('span.lock').css({'cursor': ''});
$('.realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked);
unlockElements();
}
};
var disconnect = function () {
//setEditable(false); // TODO
setEditable(false);
APP.toolbar.failed();
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
};
var reconnect = function (info) {
//setEditable(true); // TODO
setEditable(true);
APP.toolbar.reconnecting(info.myId);
Cryptpad.findOKButton().click();
};
@ -632,7 +692,7 @@ var create = function (info) {
/* add a forget button */
var forgetCb = function (err) {
if (err) { return; }
disconnect();
setEditable(false);
};
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
@ -698,12 +758,13 @@ var create = function (info) {
Cryptpad.setAttribute(HIDE_INTRODUCTION_TEXT, "1", function (e) {
if (e) { console.error(e); }
});
} else if (value === "1") {
$('#howItWorks').hide();
showHelp(true);
} else {
showHelp(false);
}
});
//Cryptpad.onLogout(function () { setEditable(false); }); TODO
Cryptpad.onLogout(function () { setEditable(false); });
});
Cryptpad.onError(function (info) {
if (info) {

@ -170,6 +170,10 @@ div.realtime table {
border-collapse: collapse;
width: calc(100% - 1px);
}
form.realtime table .editing,
div.realtime table .editing {
background-color: #88b8cc;
}
form.realtime table tr td:first-child,
div.realtime table tr td:first-child {
position: absolute;
@ -225,40 +229,63 @@ div.realtime table tr td.checkbox-cell div.checkbox-contain label {
height: 100%;
width: 100%;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable),
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable),
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) {
display: none;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover {
font-weight: bold;
background-color: #FA5858;
color: #000;
display: block;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover:after {
height: 100%;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.yes,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.yes {
background-color: #46E981;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.uncommitted,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.uncommitted {
background: #ddd;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.mine,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.mine {
display: none;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover {
background-color: #FA5858;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover:after {
content: "✖";
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover {
background-color: #46E981;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after {
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover:after {
content: "✔";
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted {
background: #ddd;
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover {
background-color: #ff5;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine {
display: none;
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover:after {
content: "~";
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover {
background-color: #ccc;
}
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover:after,
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover:after {
content: "?";
}
form.realtime table input[type="text"],
div.realtime table input[type="text"] {
@ -298,8 +325,8 @@ div.realtime table thead td input[type="text"][disabled] {
color: #000;
border: 1px solid transparent;
}
form.realtime table tbody .text-cell,
div.realtime table tbody .text-cell {
form.realtime table tbody td:not(.editing) .text-cell,
div.realtime table tbody td:not(.editing) .text-cell {
background: #aaa;
}
form.realtime table tbody .text-cell input[type="text"],

@ -4,10 +4,13 @@
@poll-th-bg: #aaa;
@poll-th-user-bg: #999;
@poll-td-bg: #aaa;
@poll-editing: #88b8cc;
@poll-placeholder: #666;
@poll-border-color: #555;
@poll-cover-color: #000;
@poll-fg: #000;
@poll-option-yellow: #ff5;
@poll-option-gray: #ccc;
html, body {
width: 100%;
@ -195,6 +198,9 @@ form.realtime, div.realtime {
table {
border-collapse: collapse;
width: ~"calc(100% - 1px)";
.editing {
background-color: @poll-editing;
}
tr {
td:first-child {
position:absolute;
@ -247,7 +253,7 @@ form.realtime, div.realtime {
}
input {
&[type="checkbox"] {
&[type="number"] {
&:not(.editable) {
display: none;
@ -255,26 +261,21 @@ form.realtime, div.realtime {
display: block;
font-weight: bold;
background-color: @cp-red;
color: @poll-cover-color;
&:after {
height: 100%;
}
&:after { content: "✖"; }
display: block;
&.yes {
background-color: @cp-green;
&:after { content: "✔"; }
}
&.uncommitted {
background: #ddd;
}
&.mine {
display: none;
}
@ -282,6 +283,31 @@ form.realtime, div.realtime {
}
}
}
input[type="number"][value="0"] {
~ .cover {
background-color: @cp-red;
&:after { content: "✖"; }
}
}
input[type="number"][value="1"] {
~ .cover {
background-color: @cp-green;
&:after { content: "✔"; }
}
}
input[type="number"][value="2"] {
~ .cover {
background-color: @poll-option-yellow;
&:after { content: "~"; }
}
}
input[type="number"][value="3"] {
~ .cover {
background-color: @poll-option-gray;
&:after { content: "?"; }
}
}
}
}
}
@ -327,8 +353,12 @@ form.realtime, div.realtime {
}
tbody {
td:not(.editing) {
.text-cell {
background: @poll-td-bg;
}
}
.text-cell {
//border-radius: 20px 0 0 20px;
input[type="text"] {
width: ~"calc(100% - 50px)";

@ -70,7 +70,12 @@ var Renderer = function (Cryptpad) {
};
var getCellValue = Render.getCellValue = function (obj, cellId) {
return Cryptpad.find(obj, ['table', 'cells'].concat([cellId]));
var value = Cryptpad.find(obj, ['table', 'cells'].concat([cellId]));
if (typeof value === 'boolean') {
return (value === true ? 1 : 0);
} else {
return value;
}
};
var setRowValue = Render.setRowValue = function (obj, rowId, value) {
@ -234,16 +239,20 @@ var Renderer = function (Cryptpad) {
disabled: 'disabled'
}].concat(cols.map(function (col) {
var id = [col, rows[i-1]].join('_');
var val = cells[id] || false;
var val = cells[id];
var result = {
'data-rt-id': id,
type: 'checkbox',
type: 'number',
autocomplete: 'nope',
value: '3',
};
if (readOnly) {
result.disabled = "disabled";
}
if (val) { result.checked = true; }
if (typeof val !== 'undefined') {
if (typeof val === 'boolean') { val = (val ? '1' : '0'); }
result.value = val;
}
return result;
}));
});
@ -297,9 +306,6 @@ var Renderer = function (Cryptpad) {
attrs.id = cell['data-rt-id'];
var labelClass = 'cover';
if (cell.checked) {
labelClass += ' yes';
}
// TODO implement Yes/No/Maybe/Undecided
return ['TD', {class:"checkbox-cell"}, [
@ -326,7 +332,7 @@ var Renderer = function (Cryptpad) {
]];
}
if (cell && cell.type === 'checkbox') {
if (cell && cell.type === 'number') {
return makeCheckbox(cell);
}
return ['TD', cell, []];
@ -399,7 +405,7 @@ var Renderer = function (Cryptpad) {
preDiffApply: function (info) {
if (!diffIsInput(info)) { return; }
switch (getInputType(info)) {
case 'checkbox':
case 'number':
//console.log('checkbox');
//console.log("[preDiffApply]", info);
break;

@ -63,6 +63,7 @@ define([
var $register = $('button#register');
var registering = false;
var logMeIn = function (result) {
if (Test.testing) {
Test.passed();
@ -79,6 +80,7 @@ define([
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
Cryptpad.login(result.userHash, result.userName, function () {
registering = false;
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
@ -95,6 +97,11 @@ define([
};
$register.click(function () {
if (registering) {
console.log("registration is already in progress");
return;
}
var uname = $uname.val();
var passwd = $passwd.val();
var confirmPassword = $confirm.val();
@ -115,6 +122,7 @@ define([
function (yes) {
if (!yes) { return; }
registering = true;
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
window.setTimeout(function () {
Cryptpad.addLoadingScreen(Messages.login_hashing);
@ -127,20 +135,27 @@ define([
switch (err) {
case 'NO_SUCH_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_noSuchUser);
Cryptpad.alert(Messages.login_noSuchUser, function () {
registering = false;
});
});
break;
case 'INVAL_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalUser);
Cryptpad.alert(Messages.login_invalUser, function () {
registering = false;
});
});
break;
case 'INVAL_PASS':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalPass);
Cryptpad.alert(Messages.login_invalPass, function () {
registering = false;
});
});
break;
case 'ALREADY_REGISTERED':
// logMeIn should reset registering = false
Cryptpad.removeLoadingScreen(function () {
Cryptpad.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
@ -155,6 +170,7 @@ define([
});
break;
default: // UNHANDLED ERROR
registering = false;
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
}
return;

@ -108,7 +108,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -40,10 +40,10 @@
<div id="bar"></div>
<!-- <textarea></textarea>-->
<div id="cme_toolbox" class="toolbar-container"></div>
<textarea id="editor1" name="editor1"></textarea>
<span class="cp slide">
<div id="editorContainer">
<textarea id="editor1" name="editor1"></textarea>
<div class="cp slide" tabindex="2">
<div id="modal">
<div id="button_exit" class="button"><span class="fa fa-times"></span></div>
<div id="button_left" class="button"><span class="fa fa-chevron-left"></span></div>
@ -51,7 +51,8 @@
<div id="content"></div>
</div>
<div id="print"></div>
</span>
</div>
</div>
<div id="nope"></div>
<div id="colorPicker_check"></div>
</body>

@ -9,6 +9,7 @@ define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/slide/slide.js',
'/bower_components/tweetnacl/nacl-fast.min.js', // needed for media-tag
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
var Messages = Cryptpad.Messages;
@ -47,6 +48,7 @@ define([
};
var andThen = function (CMeditor) {
var $iframe = $('#pad-iframe').contents();
var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad);
editor = CodeMirror.editor;
@ -66,7 +68,7 @@ define([
var setTabTitle = function (title) {
var slideNumber = '';
if (Slide.index && Slide.content.length) {
if (Slide.shown) { //Slide.index && Slide.content.length) {
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
}
document.title = title + slideNumber;
@ -194,6 +196,66 @@ define([
}
};
var createFileDialog = function () {
var $body = $iframe.find('body');
var $block = $body.find('#fileDialog');
if (!$block.length) {
$block = $('<div>', {id: "fileDialog"}).appendTo($body);
}
$block.html('');
$('<span>', {
'class': 'close fa fa-times',
'title': Messages.filePicker_close
}).click(function () {
$block.hide();
}).appendTo($block);
var $description = $('<p>').text(Messages.filePicker_description);
$block.append($description);
var $filter = $('<p>').appendTo($block);
var $container = $('<span>', {'class': 'fileContainer'}).appendTo($block);
var updateContainer = function () {
$container.html('');
var filter = $filter.find('.filter').val().trim();
var list = Cryptpad.getUserFilesList();
var fo = Cryptpad.getFO();
list.forEach(function (id) {
var data = fo.getFileData(id);
var name = fo.getTitle(id);
if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) {
return;
}
var $span = $('<span>', {'class': 'element'}).appendTo($container);
var $inner = $('<span>').text(name);
$span.append($inner).click(function () {
var cleanName = name.replace(/[\[\]]/g, '');
var text = '!['+cleanName+']('+data.href+')';
editor.replaceSelection(text);
$block.hide();
console.log(data.href);
});
});
};
var to;
$('<input>', {
type: 'text',
'class': 'filter',
'placeholder': Messages.filePicker_filter
}).appendTo($filter).on('keypress', function () {
if (to) { window.clearTimeout(to); }
to = window.setTimeout(updateContainer, 300);
});
$filter.append(' '+Messages.or+' ');
var data = {FM: APP.FM};
$filter.append(Cryptpad.createButton('upload', false, data, function () {
$block.hide();
}));
updateContainer();
$body.keydown(function (e) {
if (e.which === 27) { $block.hide(); }
});
$block.show();
};
var createPrintDialog = function () {
var slideOptionsTmp = {
title: false,
@ -355,6 +417,33 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
$('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
$('body').append(createFileDialog());
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
$previewButton.removeClass('fa-question').addClass('fa-eye');
$previewButton.attr('title', Messages.previewButtonTitle);
$previewButton.click(function () {
var $c = $iframe.find('#editorContainer');
if ($c.hasClass('preview')) {
Cryptpad.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
return void $c.removeClass('preview');
}
Cryptpad.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
$c.addClass('preview');
Slide.updateFontSize();
});
$rightside.append($previewButton);
var $printButton = $('<button>', {
title: Messages.printButtonTitle,
'class': 'rightside-button fa fa-print',
@ -483,21 +572,27 @@ define([
// Update the user list (metadata) from the hyperjson
Metadata.update(userDoc);
editor.setValue(newDoc || initialState);
if (Cryptpad.initialName && Title.isDefaultTitle()) {
Title.updateTitle(Cryptpad.initialName);
onLocal();
}
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data === true && APP.$previewButton) {
APP.$previewButton.click();
}
});
Slide.onChange(function (o, n, l) {
var slideNumber = '';
if (n !== null) {
document.title = Title.title + ' (' + (++n) + '/' + l + ')';
return;
if (Slide.shown) { //Slide.index && Slide.content.length) {
slideNumber = ' (' + (++n) + '/' + l + ')';
}
}
console.log("Exiting presentation mode");
document.title = Title.title;
document.title = Title.title + slideNumber;
});
Cryptpad.removeLoadingScreen();
@ -505,9 +600,25 @@ define([
initializing = false;
onLocal(); // push local state to avoid parse errors later.
Slide.update(editor.getValue());
if (readOnly) { return; }
UserList.getLastName(toolbar.$userNameButton, isNew);
var fmConfig = {
dropArea: $iframe.find('.CodeMirror'),
body: $iframe.find('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
var cleanName = data.name.replace(/[\[\]]/g, '');
var text = '!['+cleanName+']('+data.url+')';
/*if (data.mediatag) {
text = '!'+text;
}*/
editor.replaceSelection(text);
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
};
config.onRemote = function () {

@ -71,6 +71,44 @@ body .CodeMirror-focused .cm-matchhighlight {
visibility: visible;
}
}
#cme_toolbox {
z-index: 10000;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#editorContainer .CodeMirror {
resize: none;
width: 100vw;
}
#editorContainer.preview .CodeMirror {
width: 50vw;
}
.preview .cp {
flex: 1;
overflow: hidden;
}
.preview .cp div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
}
.preview .cp div#modal:not(.shown) #content .slide-container {
width: 100%;
}
.preview .cp div#modal:not(.shown) #content .slide-frame {
width: 50vw;
height: 28.125vw;
max-height: 100vh;
max-width: 177.78vh;
}
.cp {
/* Slide position (print mode) */
/* Slide position (present mode) */
@ -94,6 +132,7 @@ body .CodeMirror-focused .cm-matchhighlight {
page-break-after: always;
position: relative;
box-sizing: border-box;
overflow: hidden;
align-items: center;
justify-content: center;
}
@ -114,6 +153,8 @@ body .CodeMirror-focused .cm-matchhighlight {
}
.cp div.modal,
.cp div#modal {
background-color: black;
color: white;
/* Navigation buttons */
box-sizing: border-box;
z-index: 9001;
@ -160,9 +201,7 @@ body .CodeMirror-focused .cm-matchhighlight {
position: fixed;
top: 0px;
left: 0px;
z-index: 100;
background-color: black;
color: white;
z-index: 100000;
height: 100vh;
width: 100%;
}
@ -174,12 +213,9 @@ body .CodeMirror-focused .cm-matchhighlight {
overflow: visible;
white-space: nowrap;
}
.cp div.modal #content.transition,
.cp div#modal #content.transition {
transition: margin-left 1s;
}
.cp div.modal #content .slide-frame,
.cp div#modal #content .slide-frame {
overflow: hidden;
display: inline-block;
box-sizing: border-box;
border: 1px solid;
@ -207,6 +243,10 @@ body .CodeMirror-focused .cm-matchhighlight {
text-align: center;
vertical-align: top;
}
.cp div.modal #content.transition .slide-container,
.cp div#modal #content.transition .slide-container {
transition: margin-left 1s;
}
.cp div.modal .center,
.cp div#modal .center {
position: relative;
@ -300,6 +340,8 @@ body .CodeMirror-focused .cm-matchhighlight {
width: 90%;
margin: auto;
padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
}
.cp div#modal #content .slide-frame ul,
.cp #print .slide-frame ul,
@ -345,3 +387,42 @@ body .CodeMirror-focused .cm-matchhighlight {
font-size: 10%;
line-height: 110%;
}
#fileDialog {
position: absolute;
background-color: rgba(200, 200, 200, 0.8);
top: 15vh;
bottom: 15vh;
left: 10vw;
right: 10vw;
border: 1px solid black;
z-index: 10;
overflow: auto;
display: none;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
text-align: center;
}
#fileDialog .close {
position: absolute;
top: 0;
right: 0;
padding: 5px;
cursor: pointer;
}
#fileDialog .element {
cursor: pointer;
display: inline-flex;
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin: 5px;
overflow: hidden;
word-wrap: break-word;
background-color: white;
padding: 5px;
align-items: center;
}
#fileDialog .element span {
width: 100px;
text-align: center;
}

@ -41,12 +41,20 @@ define([
var updateFontSize = Slide.updateFontSize = function () {
// 20vh
// 20 * 16 / 9vw
if ($(window).width() > 16/9*$(window).height()) {
$content.css('font-size', '20vh');
var wbase = 20;
var vh = 20;
var $elem = $(window);
if (!Slide.shown) {
wbase = 10;
vh *= $content.height()/$(window).height();
$elem = $content;
}
if ($elem.width() > 16/9*$elem.height()) {
$content.css('font-size', vh+'vh');
// $print.css('font-size', '20vh');
return;
}
$content.css('font-size', (20*9/16)+'vw');
$content.css('font-size', (wbase*9/16)+'vw');
// $print.css('font-size', (20*9/16)+'vw');
};
@ -86,7 +94,8 @@ define([
}
//$content.find('.' + slideClass).hide();
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
$content.css('margin-left', -(i*100)+'vw');
//$content.css('margin-left', -(i*100)+'vw');
$content.find('.slide-container').first().css('margin-left', -(i*100)+'%');
updateFontSize();
change(Slide.lastIndex, Slide.index);
};
@ -121,6 +130,7 @@ define([
$pad.addClass('fullscreen');
$('#iframe-container').addClass('fullscreen');
$('.top-bar').hide();
updateFontSize();
return;
}
window.location.hash = window.location.hash.replace(/\/present$/, '/');
@ -131,10 +141,12 @@ define([
$('#iframe-container').removeClass('fullscreen');
$('.top-bar').show();
$modal.removeClass('shown');
updateFontSize();
};
Slide.update = function (content, init) {
if (!Slide.shown && !init) { return; }
Slide.update = function (content) {
updateFontSize();
//if (!init) { return; }
if (!content) { content = ''; }
var old = Slide.content;
Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
@ -198,13 +210,16 @@ define([
$modal.trigger(ev);
});
$modal.find('#button_right').click(function () {
console.log('right');
var ev = $.Event("keyup");
ev.which = 39;
$modal.trigger(ev);
});
$pad.contents().find('.CodeMirror').keyup(function (e) { e.stopPropagation(); });
$(ifrw).on('keyup', function (e) {
if (!Slide.shown) { return; }
//if (!Slide.shown) { return; }
if (e.ctrlKey) { return; }
switch(e.which) {
case 33: // pageup
case 38: // up

@ -77,6 +77,49 @@ body {
}
#cme_toolbox {
z-index: 10000;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
.CodeMirror {
resize: none;
width: 100vw;
}
&.preview {
.CodeMirror {
//resize: horizontal;
width: 50vw;
}
}
}
.preview .cp {
flex: 1;
overflow: hidden;
div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
#content {
.slide-container {
width: 100%;
}
.slide-frame {
width: 50vw;
height: 28.125vw; // height:width ratio = 9/16 = .5625
max-height: 100vh;
max-width: 177.78vh; // 16/9 = 1.778
}
}
}
}
.cp {
/* Slide position (print mode) */
@ -95,6 +138,7 @@ body {
page-break-after: always;
position: relative;
box-sizing: border-box;
overflow: hidden;
li {
min-width: @ratio*50vw;
}
@ -123,6 +167,8 @@ body {
/* Slide position (present mode) */
div.modal, div#modal {
display: none;
background-color: black;
color: white;
/* Navigation buttons */
.button {
@ -154,22 +200,18 @@ div.modal, div#modal {
position: fixed;
top: 0px;
left: 0px;
z-index: 100;
background-color: black;
color: white;
z-index: 100000;
height: 100vh;
width: 100%;
}
#content {
&.transition {
transition: margin-left 1s;
}
font-size: 20vh;
position: relative;
height: 100%;
overflow: visible;
white-space: nowrap;
.slide-frame {
overflow: hidden;
display: inline-block;
box-sizing: border-box;
border: 1px solid;
@ -198,6 +240,11 @@ div.modal, div#modal {
text-align: center;
vertical-align: top;
}
&.transition {
.slide-container {
transition: margin-left 1s;
}
}
}
box-sizing: border-box;
@ -266,6 +313,8 @@ div#modal #content, #print {
width: 90%;
margin: auto;
padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
}
ul, ol {
@ -308,3 +357,42 @@ div#modal #content, #print {
}
}
}
#fileDialog {
position: absolute;
background-color: rgba(200, 200, 200, 0.8);
top: 15vh; bottom: 15vh;
left: 10vw; right: 10vw;
border: 1px solid black;
z-index: 10;
overflow: auto;
display: none;
font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
font-size: 16px;
text-align: center;
.close {
position: absolute;
top: 0;
right: 0;
padding: 5px;
cursor: pointer;
}
.element {
cursor: pointer;
display: inline-flex;
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin: 5px;
overflow: hidden;
word-wrap: break-word;
background-color: white;
padding: 5px;
align-items: center;
span {
width: 100px;
text-align: center;
}
}
}

@ -108,7 +108,7 @@
</div>
</div>
</div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
<div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer>
</body>

@ -25,7 +25,7 @@
<button id="toggleDraw" data-localization="canvas_disable"></button>
<button id="delete" style="display: none;" data-localization="canvas_delete"></button>
<input id="width" data-localization-title="canvas_width" type="range" value="5" min="1" max="100"></input><label for="width">5</label>
<input id="opacity" data-localization-title="canvas_opacity" type="range" value="1" min="0" max="1" step="0.1"></input><label for="opacity">1</label>
<input id="opacity" data-localization-title="canvas_opacity" type="range" value="1" min="0.1" max="1" step="0.1"></input><label for="opacity">1</label>
<span class="selected"></span>
</div>
<div id="colors">&nbsp;</div>

@ -97,7 +97,7 @@ window.canvas = canvas;
var updateBrushWidth = function () {
var val = $width.val();
canvas.freeDrawingBrush.width = Number(val);
$widthLabel.text(val);
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
createCursor();
};
updateBrushWidth();
@ -108,7 +108,7 @@ window.canvas = canvas;
var val = $opacity.val();
brush.opacity = Number(val);
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
$opacityLabel.text(val);
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
createCursor();
};
updateBrushOpacity();
@ -338,12 +338,12 @@ window.canvas = canvas;
});
$rightside.append($forget);
makeColorButton($rightside);
var editHash;
if (!readOnly) {
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
makeColorButton($rightside);
}
if (!readOnly) { Cryptpad.replaceHash(editHash); }
};

Loading…
Cancel
Save