Merge branch 'staging' into newCk

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

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

@ -33,9 +33,9 @@ module.exports = {
* it is recommended that you configure these fields to match the * it is recommended that you configure these fields to match the
* domain which will serve your CryptPad instance. * 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 /* this allows connections over secure or insecure websockets
if you are deploying to production, you'll probably want to remove if you are deploying to production, you'll probably want to remove

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

@ -10,7 +10,19 @@ CKEDITOR.editorConfig = function( config ) {
// document itself and causes problems when it's sent across the wire and reflected back // document itself and causes problems when it's sent across the wire and reflected back
config.removePlugins= 'resize'; config.removePlugins= 'resize';
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify'; 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.font_defaultLabel = 'Arial';
config.fontSize_defaultLabel = '16'; config.fontSize_defaultLabel = '16';

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

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

@ -1164,6 +1164,57 @@ html.cp,
.limit-container .upgrade { .limit-container .upgrade {
margin-left: 10px; 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 { #cors-store {
display: none; display: none;
} }

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

@ -39,5 +39,5 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div> <div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer> </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 // hack for our cross-origin iframe
#cors-store { #cors-store {
display: none; display: none;

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

@ -235,7 +235,7 @@ define(function () {
out.login_invalPass = "Contraseña requirida"; out.login_invalPass = "Contraseña requirida";
out.login_unhandledError = "Un error inesperado se produjo :("; out.login_unhandledError = "Un error inesperado se produjo :(";
out.register_importRecent = "Importar historial (recomendado)"; 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_passwordsDontMatch = "Las contraseñas no corresponden";
out.register_mustAcceptTerms = "Tienes que acceptar los términos de servicio"; 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."; 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.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.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.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_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."; 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_locked = "Cerrado";
out.poll_unlocked = "Abierto"; out.poll_unlocked = "Abierto";
out.poll_show_help_button = "Mostrar ayuda";
out.poll_hide_help_button = "Esconder ayuda";
// 1.8.0 - Idopogo // 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."; 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.login_notRegistered = "¿No estás registrado?";
out.upload_mustLogin = "Tienes que estar conectado para subir archivos"; 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; return out;
}); });

@ -108,6 +108,8 @@ define(function () {
out.newButton = 'Nouveau'; out.newButton = 'Nouveau';
out.newButtonTitle = 'Créer un nouveau pad'; 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.saveTemplateButton = "Sauver en tant que modèle";
out.saveTemplatePrompt = "Choisir un titre pour ce modèle"; out.saveTemplatePrompt = "Choisir un titre pour ce modèle";
@ -131,6 +133,12 @@ define(function () {
out.printCSS = "Personnaliser l'apparence (CSS):"; out.printCSS = "Personnaliser l'apparence (CSS):";
out.printTransition = "Activer les animations de transition"; 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.slideOptionsTitle = "Personnaliser la présentation";
out.slideOptionsButton = "Enregistrer (Entrée)"; out.slideOptionsButton = "Enregistrer (Entrée)";
@ -211,6 +219,9 @@ define(function () {
out.poll_locked = "Verrouillé"; out.poll_locked = "Verrouillé";
out.poll_unlocked = "Déverrouillé"; out.poll_unlocked = "Déverrouillé";
out.poll_show_help_button = "Afficher l'aide";
out.poll_hide_help_button = "Cacher l'aide";
// Canvas // Canvas
out.canvas_clear = "Nettoyer"; out.canvas_clear = "Nettoyer";
out.canvas_delete = "Supprimer la sélection"; out.canvas_delete = "Supprimer la sélection";
@ -218,6 +229,8 @@ define(function () {
out.canvas_enable = "Activer le dessin"; out.canvas_enable = "Activer le dessin";
out.canvas_width = "Épaisseur"; out.canvas_width = "Épaisseur";
out.canvas_opacity = "Opacité"; out.canvas_opacity = "Opacité";
out.canvas_opacityLabel = "opacité: {0}";
out.canvas_widthLabel = "taille: {0}";
// File manager // File manager
@ -322,7 +335,7 @@ define(function () {
out.login_notRegistered = 'Pas encore inscrit ?'; out.login_notRegistered = 'Pas encore inscrit ?';
out.register_importRecent = "Importer l'historique (Recommendé)"; 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_passwordsDontMatch = "Les mots de passe doivent être identiques!";
out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation."; 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."; 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.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.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.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_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."; 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.newButton = 'New';
out.newButtonTitle = 'Create a new pad'; 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.saveTemplateButton = "Save as template";
out.saveTemplatePrompt = "Choose a title for the template"; out.saveTemplatePrompt = "Choose a title for the template";
@ -133,6 +135,12 @@ define(function () {
out.printCSS = "Custom style rules (CSS):"; out.printCSS = "Custom style rules (CSS):";
out.printTransition = "Enable transition animations"; 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.slideOptionsTitle = "Customize your slides";
out.slideOptionsButton = "Save (enter)"; out.slideOptionsButton = "Save (enter)";
@ -213,6 +221,9 @@ define(function () {
out.poll_locked = "Locked"; out.poll_locked = "Locked";
out.poll_unlocked = "Unlocked"; out.poll_unlocked = "Unlocked";
out.poll_show_help_button = "Show help";
out.poll_hide_help_button = "Hide help";
// Canvas // Canvas
out.canvas_clear = "Clear"; out.canvas_clear = "Clear";
out.canvas_delete = "Delete selection"; out.canvas_delete = "Delete selection";
@ -220,6 +231,9 @@ define(function () {
out.canvas_enable = "Enable draw"; out.canvas_enable = "Enable draw";
out.canvas_width = "Width"; out.canvas_width = "Width";
out.canvas_opacity = "Opacity"; out.canvas_opacity = "Opacity";
out.canvas_opacityLabel = "opacity: {0}";
out.canvas_widthLabel = "Width: {0}";
// File manager // File manager
@ -324,7 +338,7 @@ define(function () {
out.login_notRegistered = 'Not registered?'; out.login_notRegistered = 'Not registered?';
out.register_importRecent = "Import pad history (Recommended)"; 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_passwordsDontMatch = "Passwords do not match!";
out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustAcceptTerms = "You must accept the terms of service.";
out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm.";
@ -556,6 +570,7 @@ define(function () {
out.tips.title = "You can set the title of your pad by clicking the top center."; 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.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.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_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."; 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", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.8.0", "version": "1.9.0",
"dependencies": { "dependencies": {
"chainpad-server": "^1.0.1", "chainpad-server": "^1.0.1",
"express": "~4.10.1", "express": "~4.10.1",

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

@ -34,6 +34,7 @@ var setHeaders = (function () {
const headers = clone(config.httpHeaders); const headers = clone(config.httpHeaders);
if (config.contentSecurity) { if (config.contentSecurity) {
headers['Content-Security-Policy'] = clone(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) { if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) {
// backward compat for those who do not merge the new version of the config // backward compat for those who do not merge the new version of the config
// when updating. This prevents endless spinner if someone clicks donate. // 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$'); var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')))); app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
maxAge: DEV_MODE? "0d": "365d"
}));
app.use("/customize", Express.static(__dirname + '/customize')); app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist')); 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); var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app);
httpServer.listen(config.httpPort,config.httpAddress,function(){ 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 }; var wsConfig = { server: httpServer };

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

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

@ -9,6 +9,7 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/cryptget.js', '/common/cryptget.js',
'/common/diffMarked.js', '/common/diffMarked.js',
'/bower_components/tweetnacl/nacl-fast.min.js', // needed for media-tag
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, ], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
Cryptget, DiffMd) { Cryptget, DiffMd) {
var Messages = Cryptpad.Messages; 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')]); 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 parsed = config.href ? common.parsePadUrl(config.href) : {};
var secret = common.getSecrets(parsed.type, parsed.hash); 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 crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () { var to = window.setTimeout(function () {
@ -185,6 +196,7 @@ define([
'class':'revertHistory buttonSuccess', 'class':'revertHistory buttonSuccess',
title: Messages.history_restoreTitle title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav); }).text(Messages.history_restore).appendTo($nav);
if (!History.readOnly) { $rev.hide(); }
onUpdate = function () { onUpdate = function () {
$cur.attr('max', states.length); $cur.attr('max', states.length);

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

@ -68,7 +68,11 @@ define([], function () {
Util.replaceHash = function (hash) { Util.replaceHash = function (hash) {
if (window.history && window.history.replaceState) { if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; } 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; window.location.hash = hash;
}; };

@ -11,11 +11,13 @@ define([
'/common/common-title.js', '/common/common-title.js',
'/common/common-metadata.js', '/common/common-metadata.js',
'/common/common-codemirror.js', '/common/common-codemirror.js',
'/common/common-file.js',
'/common/clipboard.js', '/common/clipboard.js',
'/common/pinpad.js', '/common/pinpad.js',
'/customize/application_config.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 /* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata any particular pad type. This includes functions for committing metadata
@ -114,6 +116,9 @@ define([
// CodeMirror // CodeMirror
common.createCodemirror = CodeMirror.create; common.createCodemirror = CodeMirror.create;
// Files
common.createFileManager = function (config) { return Files.create(common, config); };
// History // History
common.getHistory = function (config) { return History.create(common, config); }; common.getHistory = function (config) { return History.create(common, config); };
@ -126,6 +131,11 @@ define([
return store.getProxy().proxy; return store.getProxy().proxy;
} }
}; };
common.getFO = function () {
if (store && store.getProxy()) {
return store.getProxy().fo;
}
};
var getNetwork = common.getNetwork = function () { var getNetwork = common.getNetwork = function () {
if (store) { if (store) {
if (store.getProxy() && store.getProxy().info) { if (store.getProxy() && store.getProxy().info) {
@ -144,7 +154,6 @@ define([
} }
var href = '/common/feedback.html?' + action + '=' + (+new Date()); var href = '/common/feedback.html?' + action + '=' + (+new Date());
console.log('[feedback] %s', href);
$.ajax({ $.ajax({
type: "HEAD", type: "HEAD",
url: href, url: href,
@ -300,7 +309,7 @@ define([
cb(parsed); cb(parsed);
} }
if (!pad.title) { if (!pad.title) {
pad.title = common.getDefaultname(parsed); pad.title = common.getDefaultName(parsed);
} }
return parsed.hashData; return parsed.hashData;
}; };
@ -520,8 +529,8 @@ define([
cb ("store.forgetPad is not a function"); cb ("store.forgetPad is not a function");
}; };
common.setPadTitle = function (name, cb) { common.setPadTitle = function (name, padHref, cb) {
var href = window.location.href; var href = padHref || window.location.href;
var parsed = parsePadUrl(href); var parsed = parsePadUrl(href);
if (!parsed.hash) { return; } if (!parsed.hash) { return; }
href = getRelativeHref(href); href = getRelativeHref(href);
@ -581,25 +590,26 @@ define([
return pad; 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) { if (!contains && href) {
var data = makePad(href, name); var data = makePad(href, name);
getStore().pushData(data, function (e, id) { getStore().pushData(data, function (e, id) {
if (e) { if (e) {
if (e === 'E_OVER_LIMIT') { if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true); 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); getStore().addPad(id, common.initialPath);
cb(err, recent);
}); });
} return;
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);
});
} }
cb(err, recent); cb(err, recent);
}); });
@ -621,24 +631,41 @@ define([
/* /*
* Buttons * Buttons
*/ */
common.renamePad = function (title, callback) { common.renamePad = function (title, href, callback) {
if (title === null) { return; } if (title === null) { return; }
if (title.trim() === "") { if (title.trim() === "") {
var parsed = parsePadUrl(window.location.href); var parsed = parsePadUrl(href || window.location.href);
title = getDefaultName(parsed); title = getDefaultName(parsed);
} }
common.setPadTitle(title, function (err) { common.setPadTitle(title, href, function (err) {
if (err) { if (err) {
console.log("unable to set pad title"); console.log("unable to set pad title");
console.log(err); console.error(err);
return; return;
} }
callback(null, title); 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 getUserChannelList = common.getUserChannelList = function () {
var store = common.getStore(); var store = common.getStore();
var proxy = store.getProxy(); var proxy = store.getProxy();
@ -879,6 +906,21 @@ define([
common.getPinnedUsage(todo); 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) { common.createButton = function (type, rightside, data, callback) {
var button; var button;
var size = "17px"; var size = "17px";
@ -887,6 +929,8 @@ define([
button = $('<button>', { button = $('<button>', {
title: Messages.exportButtonTitle, title: Messages.exportButtonTitle,
}).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'})); }).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'}));
button.click(prepareFeedback(type));
if (callback) { if (callback) {
button.click(callback); button.click(callback);
} }
@ -896,18 +940,40 @@ define([
title: Messages.importButtonTitle, title: Messages.importButtonTitle,
}).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'})); }).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'}));
if (callback) { 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); callback(content, file);
})); }));
} }
break; 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': case 'template':
if (!AppConfig.enableTemplates) { return; } if (!AppConfig.enableTemplates) { return; }
button = $('<button>', { button = $('<button>', {
title: Messages.saveTemplateButton, title: Messages.saveTemplateButton,
}).append($('<span>', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'})); }).append($('<span>', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'}));
if (data.rt && data.Crypt) { if (data.rt && data.Crypt) {
button.click(function () { button
.click(function () {
var title = data.getTitle() || document.title; var title = data.getTitle() || document.title;
var todo = function (val) { var todo = function (val) {
if (typeof(val) !== "string") { return; } if (typeof(val) !== "string") { return; }
@ -965,7 +1031,9 @@ define([
} }
}); });
if (callback) { if (callback) {
button.click(function() { button
.click(prepareFeedback(type))
.click(function() {
var href = window.location.href; var href = window.location.href;
var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog; var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
common.confirm(msg, function (yes) { common.confirm(msg, function (yes) {
@ -1021,7 +1089,9 @@ define([
style: 'font:'+size+' FontAwesome' style: 'font:'+size+' FontAwesome'
}); });
if (data.histConfig) { if (data.histConfig) {
button.click(function () { button
.click(prepareFeedback(type))
.click(function () {
common.getHistory(data.histConfig); common.getHistory(data.histConfig);
}); });
} }
@ -1030,7 +1100,8 @@ define([
button = $('<button>', { button = $('<button>', {
'class': "fa fa-question", 'class': "fa fa-question",
style: 'font:'+size+' FontAwesome' style: 'font:'+size+' FontAwesome'
}); })
.click(prepareFeedback(type));
} }
if (rightside) { if (rightside) {
button.addClass('rightside-button'); button.addClass('rightside-button');
@ -1378,8 +1449,8 @@ define([
initialized = true; initialized = true;
updateLocalVersion(); updateLocalVersion();
f(void 0, env); f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
} }
}; };
@ -1422,6 +1493,7 @@ define([
|| parsedOld.channel !== parsedNew.channel || parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode || parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) { || parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
document.location.reload(); document.location.reload();
return; return;
} }

@ -1,8 +1,11 @@
define([ define([
'jquery', 'jquery',
'/bower_components/marked/marked.min.js', '/bower_components/marked/marked.min.js',
'/bower_components/diff-dom/diffDOM.js' '/common/cryptpad-common.js',
],function ($, Marked) { '/common/media-tag.js',
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
],function ($, Marked, Cryptpad, MediaTag) {
var DiffMd = {}; var DiffMd = {};
var DiffDOM = window.diffDOM; var DiffDOM = window.diffDOM;
@ -33,6 +36,20 @@ define([
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n'; 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 = [ var forbiddenTags = [
'SCRIPT', 'SCRIPT',
@ -43,6 +60,10 @@ define([
'AUDIO', 'AUDIO',
]; ];
var unsafeTag = function (info) { 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 (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
if (/^on/.test(info.diff.name)) { if (/^on/.test(info.diff.name)) {
console.log("Rejecting forbidden element attribute with name", info.diff.name); console.log("Rejecting forbidden element attribute with name", info.diff.name);
@ -61,6 +82,7 @@ define([
} }
}; };
var slice = function (coll) { var slice = function (coll) {
return Array.prototype.slice.call(coll); return Array.prototype.slice.call(coll);
}; };
@ -85,7 +107,7 @@ define([
var DD = new DiffDOM({ var DD = new DiffDOM({
preDiffApply: function (info) { preDiffApply: function (info) {
if (unsafeTag(info)) { return true; } if (unsafeTag(info)) { return true; }
} },
}); });
var makeDiff = function (A, B, id) { var makeDiff = function (A, B, id) {
@ -119,9 +141,18 @@ define([
throw new Error(patch); throw new Error(patch);
} else { } else {
DD.apply($content[0], patch); 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; return DiffMd;
}); });

File diff suppressed because one or more lines are too long

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

@ -63,6 +63,7 @@ types of messages:
// RPC responses are arrays. this message isn't meant for us. // RPC responses are arrays. this message isn't meant for us.
return; return;
} }
if (/FULL_HISTORY/.test(parsed[0])) { return; }
var response = parsed.slice(2); var response = parsed.slice(2);
@ -98,7 +99,7 @@ types of messages:
delete ctx.pending[txid]; delete ctx.pending[txid];
return; 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) { var create = function (network, edPrivateKey, edPublicKey, cb) {

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

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

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

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

@ -10,13 +10,14 @@
</head> </head>
<body> <body>
<div id="toolbar" class="toolbar-container"></div> <div id="toolbar" class="toolbar-container"></div>
<div id="driveToolbar"></div>
<div class="app-container" tabindex="0"> <div class="app-container" tabindex="0">
<div id="tree"> <div id="tree">
</div> </div>
<div id="content" tabindex="2"> <div id="rightCol">
<div id="driveToolbar"></div>
<div id="content" tabindex="2"></div>
</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;"> <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-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> <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> <li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
</ul> </ul>
</div> </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;"> <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-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> <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> <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> </ul>
</div> </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;"> <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-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> <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> <li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
</ul> </ul>
</div> </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;"> <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> <li><a tabindex="-1" data-icon="fa-trash-o" class="empty editable dropdown-item" data-localization="fc_empty">Empty the trash</a></li>
</ul> </ul>
</div> </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;"> <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-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> <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 $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu");
var $trashContextMenu = $iframe.find("#trashContextMenu"); 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 // TOOLBAR
/* add a "change username" button */ /* add a "change username" button */
@ -317,6 +328,7 @@ define([
width: '0px', width: '0px',
height: '0px' height: '0px'
}); });
module.hideMenu(e);
if (sel.move) { return; } if (sel.move) { return; }
sel.move = function (ev) { sel.move = function (ev) {
var rectMove = ev.currentTarget.getBoundingClientRect(), var rectMove = ev.currentTarget.getBoundingClientRect(),
@ -638,12 +650,7 @@ define([
}; };
var updatePathSize = function () { var updatePathSize = function () {
var $context = $iframe.find('#contextButtonsContainer'); $driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - 50px)');
var l = 50;
if ($context.length) {
l += $context.width() || 0;
}
$driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - '+l+'px)');
}; };
var getSelectedPaths = function ($element) { var getSelectedPaths = function ($element) {
@ -954,23 +961,7 @@ define([
if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) { if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) {
return; return;
} }
// "force" is currently unused but may be configurable by user andThen();
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: // Drag & drop:
// The data transferred is a stringified JSON containing the path of the dragged element // 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)); 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) { var onDrop = function (ev) {
ev.preventDefault(); ev.preventDefault();
$iframe.find('.droppable').removeClass('droppable'); $iframe.find('.droppable').removeClass('droppable');
var data = ev.dataTransfer.getData("text"); 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; var oldPaths = JSON.parse(data).path;
if (!oldPaths) { return; } if (!oldPaths) { return; }
// Dropped elements can be moved from the same file manager or imported from another one. // 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 // A moved element should be removed from its previous location
var movedPaths = []; var movedPaths = [];
@ -1032,8 +1040,7 @@ define([
} }
}); });
var $el = findDataHolder($(ev.target)); var newPath = findDropPath(ev.target);
var newPath = $el.data('path');
if (!newPath) { return; } if (!newPath) { return; }
if (movedPaths && movedPaths.length) { if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh); moveElements(movedPaths, newPath, null, refresh);
@ -1069,6 +1076,8 @@ define([
e.preventDefault(); e.preventDefault();
}); });
$element.on('drop', function (e) { $element.on('drop', function (e) {
e.preventDefault();
e.stopPropagation();
onDrop(e.originalEvent); onDrop(e.originalEvent);
}); });
$element.on('dragenter', function (e) { $element.on('dragenter', function (e) {
@ -1087,6 +1096,7 @@ define([
} }
}); });
}; };
addDragAndDropHandlers($content, null, true, true);
// In list mode, display metadata from the filesData object // 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 // _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) { var createTitle = function (path, noStyle) {
if (!path || path.length === 0) { return; } if (!path || path.length === 0) { return; }
var isTrash = filesOp.isPathIn(path, [TRASH]); var isTrash = filesOp.isPathIn(path, [TRASH]);
var $title = $('<span>', {'class': 'path unselectable'}); var $title = $driveToolbar.find('.path');
if (APP.mobile()) { if (APP.mobile()) {
return $title; return $title;
} }
@ -1427,6 +1437,16 @@ define([
return $block; 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 () { var hideNewButton = function () {
$iframe.find('.dropdown-bar-content').hide(); $iframe.find('.dropdown-bar-content').hide();
}; };
@ -1599,11 +1619,9 @@ define([
var createToolbar = function () { var createToolbar = function () {
var $toolbar = $driveToolbar; var $toolbar = $driveToolbar;
$toolbar.html(''); $toolbar.html('');
var $leftside = $('<div>', {'class': 'leftside'}).appendTo($toolbar); $('<div>', {'class': 'leftside'}).appendTo($toolbar);
if (!APP.mobile()) {
$leftside.width($tree.width());
}
$('<div>', {'class': 'rightside'}).appendTo($toolbar); $('<div>', {'class': 'rightside'}).appendTo($toolbar);
$('<div>', {'class': 'path unselectable'}).appendTo($toolbar);
return $toolbar; return $toolbar;
}; };
@ -1765,6 +1783,7 @@ define([
module.hideMenu(); module.hideMenu();
if (!APP.editable) { debug("Read-only mode"); } if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; } if (!appStatus.isReady && !force) { return; }
// Only Trash and Root are available in not-owned files manager // Only Trash and Root are available in not-owned files manager
if (displayedCategories.indexOf(path[0]) === -1) { if (displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError); log(Messages.categoryError);
@ -1798,7 +1817,6 @@ define([
if (!isSearch) { delete APP.Search.oldLocation; } if (!isSearch) { delete APP.Search.oldLocation; }
module.resetTree(); module.resetTree();
if (displayedCategories.indexOf(SEARCH) !== -1 && $tree.find('#searchInput').length) { if (displayedCategories.indexOf(SEARCH) !== -1 && $tree.find('#searchInput').length) {
// in history mode we want to focus the version number input // in history mode we want to focus the version number input
if (!history.isHistoryMode && !APP.mobile()) { if (!history.isHistoryMode && !APP.mobile()) {
@ -1828,7 +1846,7 @@ define([
} }
var $list = $('<ul>').appendTo($dirContent); var $list = $('<ul>').appendTo($dirContent);
createTitle(path).appendTo($toolbar.find('.rightside')); createTitle(path).appendTo($toolbar.find('.path'));
updatePathSize(); updatePathSize();
if (APP.mobile()) { if (APP.mobile()) {
@ -1863,6 +1881,7 @@ define([
// NewButton can be undefined if we're in read only mode // NewButton can be undefined if we're in read only mode
$toolbar.find('.leftside').append(createNewButton(isInRoot)); $toolbar.find('.leftside').append(createNewButton(isInRoot));
$toolbar.find('.leftside').append(createUploadButton());
var $folderHeader = getFolderListHeader(); var $folderHeader = getFolderListHeader();
@ -2375,8 +2394,7 @@ define([
var name = paths[0].path[paths[0].path.length - 1]; var name = paths[0].path[paths[0].path.length - 1];
if ($(this).hasClass("remove")) { if ($(this).hasClass("remove")) {
if (paths.length === 1) { if (paths.length === 1) {
if (path.length === 4) { name = path[1]; } Cryptpad.confirm(Messages.fm_removePermanentlyDialog, function(res) {
Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) {
if (!res) { return; } if (!res) { return; }
filesOp.delete([path], refresh); filesOp.delete([path], refresh);
}); });
@ -2392,7 +2410,14 @@ define([
} }
else if ($(this).hasClass("restore")) { else if ($(this).hasClass("restore")) {
if (paths.length !== 1) { return; } 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) { Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) {
if (!res) { return; } if (!res) { return; }
filesOp.restore(path, refresh); filesOp.restore(path, refresh);
@ -2413,7 +2438,7 @@ define([
e.preventDefault(); e.preventDefault();
}); });
$appContainer.on('mouseup', function (e) { $appContainer.on('mouseup', function (e) {
if (sel.down) { return; } //if (sel.down) { return; }
if (e.which !== 1) { return ; } if (e.which !== 1) { return ; }
module.hideMenu(e); module.hideMenu(e);
//removeSelected(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) { history.onEnterHistory = function (obj) {
var files = obj.drive; var files = obj.drive;
filesOp = FO.init(files, config); filesOp = FO.init(files, config);
appStatus.isReady = true;
refresh(); refresh();
}; };
history.onLeaveHistory = function () { history.onLeaveHistory = function () {
@ -2550,8 +2563,8 @@ define([
filesOp.pushData(data, function (e, id) { filesOp.pushData(data, function (e, id) {
if (e) { return void console.error("Error while creating the default pad:", e); } // TODO LIMIT? if (e) { return void console.error("Error while creating the default pad:", e); } // TODO LIMIT?
filesOp.add(id); filesOp.add(id);
if (typeof(cb) === "function") { cb(); }
}); });
if (typeof(cb) === "function") { cb(); }
}); });
delete sessionStorage.createReadme; delete sessionStorage.createReadme;
return; return;
@ -2559,6 +2572,29 @@ define([
if (typeof(cb) === "function") { cb(); } 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 () { createReadme(proxy, function () {
refresh(); refresh();
APP.userList.onChange(); APP.userList.onChange();
@ -2657,7 +2693,7 @@ define([
var userList = APP.userList = info.userList; var userList = APP.userList = info.userList;
var config = { var config = {
displayed: ['useradmin', 'spinner', 'lag', 'state', 'limit'], displayed: ['useradmin', 'spinner', 'lag', 'state', 'limit', 'newpad'],
userList: { userList: {
list: userList, list: userList,
userNetfluxId: info.myID userNetfluxId: info.myID
@ -2689,7 +2725,7 @@ define([
if (err) { return void logError(err); } if (err) { return void logError(err); }
$leftside.html(''); $leftside.html('');
$leftside.append($limitContainer); $leftside.append($limitContainer);
}); }, true);
/* add a history button */ /* add a history button */
var histConfig = { var histConfig = {
@ -2704,7 +2740,7 @@ define([
history.onEnterHistory(obj); history.onEnterHistory(obj);
}, },
$toolbar: APP.$bar, $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}); var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$rightside.append($hist); $rightside.append($hist);

@ -6,6 +6,19 @@ body {
#toolbar { #toolbar {
display: flex; 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 { .cryptpad-toolbar {
padding: 0px; padding: 0px;
display: inline-block; display: inline-block;
@ -25,6 +38,10 @@ body {
position: absolute; position: absolute;
z-index: -1; z-index: -1;
} }
media-tag img {
max-width: 100%;
max-height: calc(100vh - 64px);
}
#upload-form, #upload-form,
#download-form { #download-form {
padding: 0px; padding: 0px;
@ -48,6 +65,19 @@ body {
height: 50vh; height: 50vh;
box-sizing: border-box; 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 { .hovering {
background-color: rgba(255, 0, 115, 0.5) !important; background-color: rgba(255, 0, 115, 0.5) !important;
} }
@ -58,20 +88,14 @@ body {
display: none; display: none;
} }
.inputfile + label { .inputfile + label {
border: 2px solid black;
background-color: rgba(50, 50, 50, 0.1);
display: block; display: block;
} }
.inputfile:focus + label,
.inputfile + label:hover {
background-color: rgba(50, 50, 50, 0.3);
}
#progress { #progress {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
height: 100%; height: 100%;
transition: width 500ms; transition: width 200ms;
width: 0%; width: 0%;
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
@ -79,42 +103,8 @@ body {
z-index: 10000; z-index: 10000;
display: block; display: block;
} }
#status { body #uploadStatusContainer {
display: none; background-color: rgba(255, 255, 255, 0.9);
width: 80vw; color: black;
margin-top: 50px; opacity: 0.9;
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;
} }

@ -12,6 +12,19 @@ html, body {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar 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 { .cryptpad-toolbar {
padding: 0px; padding: 0px;
display: inline-block; display: inline-block;
@ -32,6 +45,13 @@ html, body {
z-index: -1; z-index: -1;
} }
media-tag {
img {
max-width: 100%;
max-height: ~"calc(100vh - 64px)";
}
}
#upload-form, #download-form { #upload-form, #download-form {
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
@ -54,6 +74,21 @@ html, body {
box-sizing: border-box; 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 { .hovering {
background-color: rgba(255, 0, 115, 0.5) !important; background-color: rgba(255, 0, 115, 0.5) !important;
} }
@ -65,14 +100,14 @@ html, body {
display: none; display: none;
} }
.inputfile + label { .inputfile + label {
border: 2px solid black; //border: 2px solid black;
background-color: rgba(50, 50, 50, .10); //background-color: rgba(50, 50, 50, .10);
display: block; display: block;
} }
.inputfile:focus + label, .inputfile:focus + label,
.inputfile + label:hover { .inputfile + label:hover {
background-color: rgba(50, 50, 50, 0.30); //background-color: rgba(50, 50, 50, 0.30);
} }
#progress { #progress {
@ -82,7 +117,7 @@ html, body {
height: 100%; height: 100%;
transition: width 500ms; transition: width 200ms;
width: 0%; width: 0%;
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
@ -91,38 +126,8 @@ html, body {
display: block; display: block;
} }
#status { body #uploadStatusContainer {
display: none; background-color: rgba(255, 255, 255, 0.9);
width: 80vw; color: black;
margin-top: 50px; opacity: 0.9;
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;
}
.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,26 +10,22 @@
</head> </head>
<body> <body>
<div id="toolbar" class="toolbar-container"></div> <div id="toolbar" class="toolbar-container"></div>
<div id="upload-form" style="display: none;"> <div id="app">
<input type="file" name="file" id="file" class="inputfile" /> <div id="upload-form" style="display: none;">
<label for="file" class="block unselectable" data-localization-title="upload_choose" <input type="file" name="file" id="file" class="inputfile" />
data-localization="upload_choose"></label> <label for="file" class="btn btn-primary block unselectable" data-localization-title="upload_choose"
</div> data-localization="upload_choose"></label>
<div id="download-form" style="display: none;"> </div>
<input type="button" name="dl" id="dl" class="inputfile" /> <div id="download-form" style="display: none;">
<label for="dl" class="block unselectable" data-localization-title="download_button" <input type="button" name="dl" id="dl" class="inputfile" />
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> <span class="block" id="progress"></span>
</div> </div>
<table id="status" style="display: none;"> <div id="download-view" style="display: none;">
<tr> <media-tag id="encryptedFile"></media-tag>
<td data-localization="upload_name">File name</td> </div>
<td data-localization="upload_size">Size</td> <div id="feedback" class="block hidden">
<td data-localization="upload_progress">Progress</td> </div>
<td data-localization="cancel">Cancel</td>
</tr>
</table>
<div id="feedback" class="block hidden">
</div> </div>
</body> </body>
</html> </html>

@ -7,209 +7,38 @@ define([
'/common/visible.js', '/common/visible.js',
'/common/notify.js', '/common/notify.js',
'/file/file-crypto.js', '/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) { ], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
var saveAs = window.saveAs; var saveAs = window.saveAs;
var Nacl = window.nacl; var Nacl = window.nacl;
var APP = {}; var APP = window.APP = {};
$(function () { $(function () {
var ifrw = $('#pad-iframe')[0].contentWindow; var andThen = function () {
var $iframe = $('#pad-iframe').contents(); var ifrw = $('#pad-iframe')[0].contentWindow;
var $form = $iframe.find('#upload-form'); var $iframe = $('#pad-iframe').contents();
var $dlform = $iframe.find('#download-form'); var $appContainer = $iframe.find('#app');
var $label = $form.find('label'); var $form = $iframe.find('#upload-form');
var $table = $iframe.find('#status'); var $dlform = $iframe.find('#download-form');
var $progress = $iframe.find('#progress'); var $dlview = $iframe.find('#download-view');
var $label = $form.find('label');
$iframe.find('body').on('dragover', function (e) { e.preventDefault(); }); var $dllabel = $dlform.find('label span');
$iframe.find('body').on('drop', function (e) { e.preventDefault(); }); var $progress = $iframe.find('#progress');
var $body = $iframe.find('body');
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>', { $body.on('dragover', function (e) { e.preventDefault(); });
'class': 'upLink', $body.on('drop', function (e) { e.preventDefault(); });
}).text(obj.metadata.name);
$('<td>').append($link).appendTo($tr); Cryptpad.addLoadingScreen();
$('<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 Title;
};
var uploadMode = false; var uploadMode = false;
var andThen = function () {
var $bar = $iframe.find('.toolbar-container'); var $bar = $iframe.find('.toolbar-container');
var secret; var secret;
@ -229,13 +58,6 @@ define([
return data ? data.title : undefined; 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); Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade']; var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade'];
@ -257,60 +79,112 @@ define([
if (uploadMode) { toolbar.title.hide(); } 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); Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle);
if (!uploadMode) { if (!uploadMode) {
$dlform.show();
var src = Cryptpad.getBlobPathFromHex(hexFileName); var src = Cryptpad.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr; var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey); var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
var title = document.title = metadata.name; var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle); Title.updateTitle(title || Title.defaultTitle);
Cryptpad.removeLoadingScreen(); var displayFile = function (ev) {
var decrypting = false; var $mt = $dlview.find('media-tag');
$dlform.find('#dl, #progress').click(function () { var cryptKey = secret.keys && secret.keys.fileKeyStr;
if (decrypting) { return; } var hexFileName = Cryptpad.base64ToHex(secret.channel);
if (myFile) { return void exportFile(); } $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
decrypting = true; $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
return Cryptpad.fetch(src, function (e, u8) { $(window.document).on('decryption', function (e) {
if (e) { var decrypted = e.originalEvent;
decrypting = false; if (decrypted.callback) { decrypted.callback(); }
return void Cryptpad.alert(e);
} console.log(decrypted);
$dlview.show();
// now decrypt the u8 $dlform.hide();
if (!u8 || !u8.length) { var $dlButton = $dlview.find('media-tag button');
return void Cryptpad.errorLoadingScreen(e); if (ev) { $dlButton.click(); }
if (!$dlButton.length) {
$appContainer.css('background', 'white');
} }
$dlButton.addClass('btn btn-success');
FileCrypto.decrypt(u8, key, function (e, data) {
if (e) { toolbar.$rightside.append(Cryptpad.createButton('export', true, {}, function () {
decrypting = false; saveAs(decrypted.blob, decrypted.metadata.name);
return console.error(e); }))
} .append(Cryptpad.createButton('forget', true, {}, function () {
console.log(data); // not sure what to do here
var title = document.title = data.metadata.name; }));
myFile = data.content;
myDataType = data.metadata.type; // make pdfs big
Title.updateTitle(title || Title.defaultTitle); $iframe.find('media-tag iframe').css({
exportFile(); 'height': 'calc(100vh - 64px)',
decrypting = false; width: 'calc(100vw - 15px)',
}, function (progress) {
var p = progress * 100 +'%';
$progress.width(p);
console.error(progress);
}); });
})
.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;
var onClick = function (ev) {
if (decrypting) { return; }
decrypting = true;
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; return;
@ -329,57 +203,18 @@ define([
display: 'block', display: 'block',
}); });
var handleFile = function (file) { var fmConfig = {
console.log(file); dropArea: $form,
var reader = new FileReader(); hoverArea: $label,
reader.onloadend = function () { body: $body,
queue.push({ keepTable: true // Don't fadeOut the tbale with the uploaded files
blob: this.result,
metadata: {
name: file.name,
type: file.type,
}
});
};
reader.readAsArrayBuffer(file);
}; };
var FM = Cryptpad.createFileManager(fmConfig);
$form.find("#file").on('change', function (e) { $form.find("#file").on('change', function (e) {
var file = e.target.files[0]; var file = e.target.files[0];
handleFile(file); FM.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);
});
}); });
// we're in upload mode // we're in upload mode

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

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

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

@ -487,8 +487,14 @@ define([
var updateIcon = function () { var updateIcon = function () {
$collapse.removeClass('fa-caret-down').removeClass('fa-caret-up'); $collapse.removeClass('fa-caret-down').removeClass('fa-caret-up');
var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible'); var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible');
if (isCollapsed) { $collapse.addClass('fa-caret-down'); } if (isCollapsed) {
else { $collapse.addClass('fa-caret-up'); } if (!initializing) { Cryptpad.feedback('HIDETOOLBAR_PAD'); }
$collapse.addClass('fa-caret-down');
}
else {
if (!initializing) { Cryptpad.feedback('SHOWTOOLBAR_PAD'); }
$collapse.addClass('fa-caret-up');
}
}; };
updateIcon(); updateIcon();
$collapse.click(function () { $collapse.click(function () {
@ -666,6 +672,19 @@ define([
onLocal(); onLocal();
return test; 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"> <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="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="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>
<div class="realtime"> <div class="realtime">

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

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

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

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

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

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

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

@ -9,6 +9,7 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/cryptget.js', '/common/cryptget.js',
'/slide/slide.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) { ], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
@ -47,6 +48,7 @@ define([
}; };
var andThen = function (CMeditor) { var andThen = function (CMeditor) {
var $iframe = $('#pad-iframe').contents();
var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad); var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad);
editor = CodeMirror.editor; editor = CodeMirror.editor;
@ -66,7 +68,7 @@ define([
var setTabTitle = function (title) { var setTabTitle = function (title) {
var slideNumber = ''; var slideNumber = '';
if (Slide.index && Slide.content.length) { if (Slide.shown) { //Slide.index && Slide.content.length) {
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')'; slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
} }
document.title = title + slideNumber; 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 createPrintDialog = function () {
var slideOptionsTmp = { var slideOptionsTmp = {
title: false, title: false,
@ -355,6 +417,33 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $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>', { var $printButton = $('<button>', {
title: Messages.printButtonTitle, title: Messages.printButtonTitle,
'class': 'rightside-button fa fa-print', 'class': 'rightside-button fa fa-print',
@ -483,21 +572,27 @@ define([
// Update the user list (metadata) from the hyperjson // Update the user list (metadata) from the hyperjson
Metadata.update(userDoc); Metadata.update(userDoc);
editor.setValue(newDoc || initialState); editor.setValue(newDoc || initialState);
if (Cryptpad.initialName && Title.isDefaultTitle()) { if (Cryptpad.initialName && Title.isDefaultTitle()) {
Title.updateTitle(Cryptpad.initialName); 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) { Slide.onChange(function (o, n, l) {
var slideNumber = '';
if (n !== null) { if (n !== null) {
document.title = Title.title + ' (' + (++n) + '/' + l + ')'; if (Slide.shown) { //Slide.index && Slide.content.length) {
return; slideNumber = ' (' + (++n) + '/' + l + ')';
}
} }
console.log("Exiting presentation mode"); document.title = Title.title + slideNumber;
document.title = Title.title;
}); });
Cryptpad.removeLoadingScreen(); Cryptpad.removeLoadingScreen();
@ -505,9 +600,25 @@ define([
initializing = false; initializing = false;
onLocal(); // push local state to avoid parse errors later. onLocal(); // push local state to avoid parse errors later.
Slide.update(editor.getValue());
if (readOnly) { return; } if (readOnly) { return; }
UserList.getLastName(toolbar.$userNameButton, isNew); 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 () { config.onRemote = function () {

@ -71,6 +71,44 @@ body .CodeMirror-focused .cm-matchhighlight {
visibility: visible; 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 { .cp {
/* Slide position (print mode) */ /* Slide position (print mode) */
/* Slide position (present mode) */ /* Slide position (present mode) */
@ -94,6 +132,7 @@ body .CodeMirror-focused .cm-matchhighlight {
page-break-after: always; page-break-after: always;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
@ -114,6 +153,8 @@ body .CodeMirror-focused .cm-matchhighlight {
} }
.cp div.modal, .cp div.modal,
.cp div#modal { .cp div#modal {
background-color: black;
color: white;
/* Navigation buttons */ /* Navigation buttons */
box-sizing: border-box; box-sizing: border-box;
z-index: 9001; z-index: 9001;
@ -160,9 +201,7 @@ body .CodeMirror-focused .cm-matchhighlight {
position: fixed; position: fixed;
top: 0px; top: 0px;
left: 0px; left: 0px;
z-index: 100; z-index: 100000;
background-color: black;
color: white;
height: 100vh; height: 100vh;
width: 100%; width: 100%;
} }
@ -174,12 +213,9 @@ body .CodeMirror-focused .cm-matchhighlight {
overflow: visible; overflow: visible;
white-space: nowrap; 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,
.cp div#modal #content .slide-frame { .cp div#modal #content .slide-frame {
overflow: hidden;
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid; border: 1px solid;
@ -207,6 +243,10 @@ body .CodeMirror-focused .cm-matchhighlight {
text-align: center; text-align: center;
vertical-align: top; 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,
.cp div#modal .center { .cp div#modal .center {
position: relative; position: relative;
@ -300,6 +340,8 @@ body .CodeMirror-focused .cm-matchhighlight {
width: 90%; width: 90%;
margin: auto; margin: auto;
padding-left: .25vw; padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
} }
.cp div#modal #content .slide-frame ul, .cp div#modal #content .slide-frame ul,
.cp #print .slide-frame ul, .cp #print .slide-frame ul,
@ -345,3 +387,42 @@ body .CodeMirror-focused .cm-matchhighlight {
font-size: 10%; font-size: 10%;
line-height: 110%; 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;
}

@ -38,15 +38,23 @@ define([
} }
}; };
var updateFontSize = Slide.updateFontSize = function() { var updateFontSize = Slide.updateFontSize = function () {
// 20vh // 20vh
// 20 * 16 / 9vw // 20 * 16 / 9vw
if ($(window).width() > 16/9*$(window).height()) { var wbase = 20;
$content.css('font-size', '20vh'); 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'); // $print.css('font-size', '20vh');
return; 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'); // $print.css('font-size', (20*9/16)+'vw');
}; };
@ -86,7 +94,8 @@ define([
} }
//$content.find('.' + slideClass).hide(); //$content.find('.' + slideClass).hide();
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show(); //$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(); updateFontSize();
change(Slide.lastIndex, Slide.index); change(Slide.lastIndex, Slide.index);
}; };
@ -121,6 +130,7 @@ define([
$pad.addClass('fullscreen'); $pad.addClass('fullscreen');
$('#iframe-container').addClass('fullscreen'); $('#iframe-container').addClass('fullscreen');
$('.top-bar').hide(); $('.top-bar').hide();
updateFontSize();
return; return;
} }
window.location.hash = window.location.hash.replace(/\/present$/, '/'); window.location.hash = window.location.hash.replace(/\/present$/, '/');
@ -131,10 +141,12 @@ define([
$('#iframe-container').removeClass('fullscreen'); $('#iframe-container').removeClass('fullscreen');
$('.top-bar').show(); $('.top-bar').show();
$modal.removeClass('shown'); $modal.removeClass('shown');
updateFontSize();
}; };
Slide.update = function (content, init) { Slide.update = function (content) {
if (!Slide.shown && !init) { return; } updateFontSize();
//if (!init) { return; }
if (!content) { content = ''; } if (!content) { content = ''; }
var old = Slide.content; var old = Slide.content;
Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n'); Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
@ -198,13 +210,16 @@ define([
$modal.trigger(ev); $modal.trigger(ev);
}); });
$modal.find('#button_right').click(function () { $modal.find('#button_right').click(function () {
console.log('right');
var ev = $.Event("keyup"); var ev = $.Event("keyup");
ev.which = 39; ev.which = 39;
$modal.trigger(ev); $modal.trigger(ev);
}); });
$pad.contents().find('.CodeMirror').keyup(function (e) { e.stopPropagation(); });
$(ifrw).on('keyup', function (e) { $(ifrw).on('keyup', function (e) {
if (!Slide.shown) { return; } //if (!Slide.shown) { return; }
if (e.ctrlKey) { return; }
switch(e.which) { switch(e.which) {
case 33: // pageup case 33: // pageup
case 38: // up 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 { .cp {
/* Slide position (print mode) */ /* Slide position (print mode) */
@ -95,6 +138,7 @@ body {
page-break-after: always; page-break-after: always;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
li { li {
min-width: @ratio*50vw; min-width: @ratio*50vw;
} }
@ -123,6 +167,8 @@ body {
/* Slide position (present mode) */ /* Slide position (present mode) */
div.modal, div#modal { div.modal, div#modal {
display: none; display: none;
background-color: black;
color: white;
/* Navigation buttons */ /* Navigation buttons */
.button { .button {
@ -154,22 +200,18 @@ div.modal, div#modal {
position: fixed; position: fixed;
top: 0px; top: 0px;
left: 0px; left: 0px;
z-index: 100; z-index: 100000;
background-color: black;
color: white;
height: 100vh; height: 100vh;
width: 100%; width: 100%;
} }
#content { #content {
&.transition {
transition: margin-left 1s;
}
font-size: 20vh; font-size: 20vh;
position: relative; position: relative;
height: 100%; height: 100%;
overflow: visible; overflow: visible;
white-space: nowrap; white-space: nowrap;
.slide-frame { .slide-frame {
overflow: hidden;
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid; border: 1px solid;
@ -198,6 +240,11 @@ div.modal, div#modal {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
} }
&.transition {
.slide-container {
transition: margin-left 1s;
}
}
} }
box-sizing: border-box; box-sizing: border-box;
@ -266,6 +313,8 @@ div#modal #content, #print {
width: 90%; width: 90%;
margin: auto; margin: auto;
padding-left: .25vw; padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
} }
ul, ol { 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>
</div> </div>
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div> <div class="version-footer">CryptPad v1.9.0 (Jackelope)</div>
</footer> </footer>
</body> </body>

@ -25,7 +25,7 @@
<button id="toggleDraw" data-localization="canvas_disable"></button> <button id="toggleDraw" data-localization="canvas_disable"></button>
<button id="delete" style="display: none;" data-localization="canvas_delete"></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="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> <span class="selected"></span>
</div> </div>
<div id="colors">&nbsp;</div> <div id="colors">&nbsp;</div>

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

Loading…
Cancel
Save