diff --git a/bower.json b/bower.json index b9c7a8247..587c9e2e8 100644 --- a/bower.json +++ b/bower.json @@ -37,7 +37,6 @@ "diff-dom": "^2.1.1", "alertifyjs": "^1.0.11", "scrypt-async": "^1.2.0", - "bootstrap": "#v4.0.0-alpha.6", - "pdfjs-dist": "^1.8.398" + "bootstrap": "#v4.0.0-alpha.6" } } diff --git a/config.example.js b/config.example.js index c7a831b4c..d2ea25a3e 100644 --- a/config.example.js +++ b/config.example.js @@ -33,9 +33,9 @@ module.exports = { * it is recommended that you configure these fields to match the * domain which will serve your CryptPad instance. */ - "child-src 'self' *", + "child-src 'self' blob: *", - "media-src *", + "media-src * blob:", /* this allows connections over secure or insecure websockets if you are deploying to production, you'll probably want to remove diff --git a/customize.dist/about.html b/customize.dist/about.html index dadd21e3b..613cac012 100644 --- a/customize.dist/about.html +++ b/customize.dist/about.html @@ -117,7 +117,7 @@ - + diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index e5efb642f..45d5bbd38 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -10,7 +10,19 @@ CKEDITOR.editorConfig = function( config ) { // document itself and causes problems when it's sent across the wire and reflected back config.removePlugins= 'resize'; config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify'; - config.toolbarGroups= [{"name":"clipboard","groups":["clipboard","undo"]},{"name":"editing","groups":["find","selection"]},{"name":"links"},{"name":"insert"},{"name":"forms"},{"name":"tools"},{"name":"document","groups":["mode","document","doctools"]},{"name":"others"},{"name":"basicstyles","groups":["basicstyles","cleanup"]},{"name":"paragraph","groups":["list","indent","blocks","align","bidi"]},{"name":"styles"},{"name":"colors"}]; + config.toolbarGroups= [ + // {"name":"clipboard","groups":["clipboard","undo"]}, + //{"name":"editing","groups":["find","selection"]}, + {"name":"links"}, + {"name":"insert"}, + {"name":"forms"}, + {"name":"tools"}, + {"name":"document","groups":["mode","document","doctools"]}, + {"name":"others"}, + {"name":"basicstyles","groups":["basicstyles","cleanup"]}, + {"name":"paragraph","groups":["list","indent","blocks","align","bidi"]}, + {"name":"styles"}, + {"name":"colors"}]; config.font_defaultLabel = 'Arial'; config.fontSize_defaultLabel = '16'; diff --git a/customize.dist/contact.html b/customize.dist/contact.html index 768cf2f7c..1bcc53dfc 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -114,7 +114,7 @@ - + diff --git a/customize.dist/index.html b/customize.dist/index.html index db9cef793..287592c42 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -236,7 +236,7 @@ - + diff --git a/customize.dist/main.css b/customize.dist/main.css index 49f06eedc..9e459880a 100644 --- a/customize.dist/main.css +++ b/customize.dist/main.css @@ -1164,6 +1164,57 @@ html.cp, .limit-container .upgrade { margin-left: 10px; } +/* Upload status table */ +#uploadStatusContainer { + position: absolute; + left: 10vw; + right: 10vw; + bottom: 100px; + background-color: rgba(0, 0, 0, 0.5); + color: white; + opacity: 0.7; + font-family: Ubuntu,Georgia,Cambria,serif; + box-sizing: border-box; + z-index: 10; + display: none; +} +#uploadStatusContainer #uploadStatus { + width: 80vw; + border: 1px solid black; + border-collapse: collapse; +} +#uploadStatusContainer #uploadStatus tr:nth-child(1) { + background-color: #888; + border: 1px solid #999; +} +#uploadStatusContainer #uploadStatus tr:nth-child(1) td { + text-align: center; +} +#uploadStatusContainer #uploadStatus td { + border-left: 1px solid #BBB; + border-right: 1px solid #BBB; + padding: 0 10px; +} +#uploadStatusContainer #uploadStatus .upProgress { + width: 200px; + position: relative; + text-align: center; + box-sizing: border-box; +} +#uploadStatusContainer #uploadStatus .progressContainer { + position: absolute; + width: 0px; + left: 5px; + top: 1px; + bottom: 1px; + background-color: rgba(0, 0, 255, 0.3); +} +#uploadStatusContainer #uploadStatus .upCancel { + text-align: center; +} +#uploadStatusContainer #uploadStatus .fa.cancel { + color: #ff0073; +} #cors-store { display: none; } diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 8218dbad2..3d4e8f99f 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -135,7 +135,7 @@ - + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index a43182135..cdc0dc3a0 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -39,5 +39,5 @@ - + diff --git a/customize.dist/src/less/cryptpad.less b/customize.dist/src/less/cryptpad.less index 49608df00..e0c071d41 100644 --- a/customize.dist/src/less/cryptpad.less +++ b/customize.dist/src/less/cryptpad.less @@ -600,6 +600,52 @@ noscript { } } +/* Upload status table */ +#uploadStatusContainer { + position: absolute; + left: 10vw; right: 10vw; + bottom: 100px; + background-color: rgba(0,0,0,0.5); + color: white; + opacity: 0.7; + font-family: Ubuntu,Georgia,Cambria,serif; + box-sizing: border-box; + z-index:10; + display: none; + #uploadStatus { + width: 80vw; + border: 1px solid black; + border-collapse: collapse; + tr:nth-child(1) { + background-color: #888; + border: 1px solid #999; + td { text-align: center; } + } + td { + border-left: 1px solid #BBB; + border-right: 1px solid #BBB; + padding: 0 10px; + } + .upProgress { + width: 200px; + position: relative; + text-align: center; + box-sizing: border-box; + } + .progressContainer { + position: absolute; + width: 0px; + left: 5px; + top: 1px; bottom: 1px; + background-color: rgba(0,0,255,0.3); + } + .upCancel { text-align: center; } + .fa.cancel { + color: rgb(255, 0, 115); + } + } +} + // hack for our cross-origin iframe #cors-store { display: none; diff --git a/customize.dist/terms.html b/customize.dist/terms.html index bda3dcbb9..62dc97f0e 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -118,7 +118,7 @@ - + diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 57eb41f9e..8104557c0 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -235,7 +235,7 @@ define(function () { out.login_invalPass = "Contraseña requirida"; out.login_unhandledError = "Un error inesperado se produjo :("; out.register_importRecent = "Importar historial (recomendado)"; - out.register_acceptTerms = "Accepto los términos de servicio"; + out.register_acceptTerms = "Accepto los términos de servicio"; out.register_passwordsDontMatch = "Las contraseñas no corresponden"; out.register_mustAcceptTerms = "Tienes que acceptar los términos de servicio"; out.register_mustRememberPass = "No podemos reiniciar tu contraseña si la olvidas. ¡Es muy importante que la recuerdes! Marca la casilla para confirmarlo."; @@ -351,6 +351,7 @@ define(function () { out.tips.title = "Puedes cambiar el título de tus pads en la parte superior de la pantalla."; out.tips.store = "Cada vez que visitas un pad con una sesión iniciada se guardará a tu CryptDrive."; out.tips.marker = "Puedes resaltar texto en un pad utilizando el \"marcador\" en el menú de estílo."; + out.tips.driveUpload = "Usuarios registrados pueden subir archivos cifrados arrastrandolos hacia CryptDrive."; out.feedback_about = "Si estas leyendo esto, quizas estés curioso de saber porqué CryptPad solicita esta página cuando haces algunas acciones"; out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos esta página para conocer las funcionalidades que importan a nuestros usuarios, pidiendolo con un parametro que nos dice que accion fue realizada."; @@ -452,6 +453,9 @@ define(function () { out.poll_locked = "Cerrado"; out.poll_unlocked = "Abierto"; + out.poll_show_help_button = "Mostrar ayuda"; + out.poll_hide_help_button = "Esconder ayuda"; + // 1.8.0 - Idopogo out.common_connectionLost = "Connexión perdida
El documento está ahora en modo solo lectura hasta que la conexión vuelva."; @@ -467,5 +471,19 @@ define(function () { out.login_notRegistered = "¿No estás registrado?"; out.upload_mustLogin = "Tienes que estar conectado para subir archivos"; + out.uploadButton = "Subir"; + out.uploadButtonTitle = "Subir un archivo a la carpeta"; + out.filePickerButton = "Incrustar un archivo"; + out.filePicker_close = "Cerrar"; + out.filePicker_description = "Elige un archivo de tu CryptDrive para incrustarlo o sube uno nuevo"; + out.filePicker_filter = "Filtrar por nombre"; + out.or = "o"; + out.languageButton = "Lenguaje"; + out.languageButtonTitle = "Elige el lenguaje para resaltado de sintaxis"; + out.themeButton = "Tema"; + out.themeButtonTitle = "Selecciona el tema de color para los editores de código y presentación"; + out.canvas_opacityLabel = "Opacidad: {0}"; + out.canvas_widthLabel = "Talla: {0}"; + return out; }); diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 6a3285e91..af7ebba91 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -108,6 +108,8 @@ define(function () { out.newButton = 'Nouveau'; out.newButtonTitle = 'Créer un nouveau pad'; + out.uploadButton = 'Upload'; + out.uploadButtonTitle = 'Uploader un nouveau fichier dans le dossier actuel'; out.saveTemplateButton = "Sauver en tant que modèle"; out.saveTemplatePrompt = "Choisir un titre pour ce modèle"; @@ -131,6 +133,12 @@ define(function () { out.printCSS = "Personnaliser l'apparence (CSS):"; out.printTransition = "Activer les animations de transition"; + out.filePickerButton = "Intégrer un fichier"; + out.filePicker_close = "Fermer"; + out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou uploadez-en un nouveau"; + out.filePicker_filter = "Filtrez les fichiers par leur nom"; + out.or = 'ou'; + out.slideOptionsTitle = "Personnaliser la présentation"; out.slideOptionsButton = "Enregistrer (Entrée)"; @@ -211,6 +219,9 @@ define(function () { out.poll_locked = "Verrouillé"; out.poll_unlocked = "Déverrouillé"; + out.poll_show_help_button = "Afficher l'aide"; + out.poll_hide_help_button = "Cacher l'aide"; + // Canvas out.canvas_clear = "Nettoyer"; out.canvas_delete = "Supprimer la sélection"; @@ -218,6 +229,8 @@ define(function () { out.canvas_enable = "Activer le dessin"; out.canvas_width = "Épaisseur"; out.canvas_opacity = "Opacité"; + out.canvas_opacityLabel = "opacité: {0}"; + out.canvas_widthLabel = "taille: {0}"; // File manager @@ -322,7 +335,7 @@ define(function () { out.login_notRegistered = 'Pas encore inscrit ?'; out.register_importRecent = "Importer l'historique (Recommendé)"; - out.register_acceptTerms = "J'accepte les conditions d'utilisation"; + out.register_acceptTerms = "J'accepte les conditions d'utilisation"; out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!"; out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation."; out.register_mustRememberPass = "Nous ne pouvons pas réinitialiser votre mot de passe si vous l'oubliez. C'est important que vous vous en souveniez! Veuillez cocher la case pour confirmer."; @@ -545,6 +558,7 @@ define(function () { out.tips.title = "Vous pouvez changer le titre de votre pad en cliquant au centre en haut de la page."; out.tips.store = "Dés que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés."; out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles."; + out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive."; out.feedback_about = "Si vous lisez ceci, vous vous demandez probablement pourquoi CryptPad envoie des requêtes vers des pages web quand vous realisez certaines actions."; out.feedback_privacy = "Nous prenons au sérieux le respect de votre vie privée, et en même temps nous souhaitons rendre CryptPad très simple à utiliser. Nous utilisons cette page pour comprendre quelles fonctionnalités dans l'interface comptent le plus pour les utilisateurs, en l'appelant avec un paramètre spécifiant quelle action a été réalisée."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 557fcce6c..4e8acb1b0 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -110,6 +110,8 @@ define(function () { out.newButton = 'New'; out.newButtonTitle = 'Create a new pad'; + out.uploadButton = 'Upload'; + out.uploadButtonTitle = 'Upload a new file to the current folder'; out.saveTemplateButton = "Save as template"; out.saveTemplatePrompt = "Choose a title for the template"; @@ -133,6 +135,12 @@ define(function () { out.printCSS = "Custom style rules (CSS):"; out.printTransition = "Enable transition animations"; + out.filePickerButton = "Embed a file"; + out.filePicker_close = "Close"; + out.filePicker_description = "Choose a file from your CryptDrive to embed it or upload a new one"; + out.filePicker_filter = "Filter files by name"; + out.or = 'or'; + out.slideOptionsTitle = "Customize your slides"; out.slideOptionsButton = "Save (enter)"; @@ -213,6 +221,9 @@ define(function () { out.poll_locked = "Locked"; out.poll_unlocked = "Unlocked"; + out.poll_show_help_button = "Show help"; + out.poll_hide_help_button = "Hide help"; + // Canvas out.canvas_clear = "Clear"; out.canvas_delete = "Delete selection"; @@ -220,6 +231,9 @@ define(function () { out.canvas_enable = "Enable draw"; out.canvas_width = "Width"; out.canvas_opacity = "Opacity"; + out.canvas_opacityLabel = "opacity: {0}"; + out.canvas_widthLabel = "Width: {0}"; + // File manager @@ -324,7 +338,7 @@ define(function () { out.login_notRegistered = 'Not registered?'; out.register_importRecent = "Import pad history (Recommended)"; - out.register_acceptTerms = "I accept the terms of service"; + out.register_acceptTerms = "I accept the terms of service"; out.register_passwordsDontMatch = "Passwords do not match!"; out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; @@ -556,6 +570,7 @@ define(function () { out.tips.title = "You can set the title of your pad by clicking the top center."; out.tips.store = "Every time you visit a pad, if you're logged in it will be saved to your CryptDrive."; out.tips.marker = "You can highlight text in a pad using the \"marker\" item in the styles dropdown menu."; + out.tips.driveUpload = "Registered users can upload encrypted files by dragging and dropping them into their CryptDrive."; out.feedback_about = "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions"; out.feedback_privacy = "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken."; diff --git a/package.json b/package.json index 9454404ac..864d27b52 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.8.0", + "version": "1.9.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/rpc.js b/rpc.js index cfb6d7772..25b1ae550 100644 --- a/rpc.js +++ b/rpc.js @@ -17,6 +17,15 @@ var Store = require("./storage/file"); var DEFAULT_LIMIT = 50 * 1024 * 1024; var SESSION_EXPIRATION_TIME = 60 * 1000; +var SUPPRESS_RPC_ERRORS = false; + +var WARN = function (e, output) { + if (!SUPPRESS_RPC_ERRORS && e && output) { + console.error(new Date().toISOString() + ' [' + e + ']', output); + console.error(new Error(e).stack); + console.error(); + } +}; var isValidId = function (chan) { return chan && chan.length && /^[a-fA-F0-9]/.test(chan) || @@ -237,11 +246,10 @@ var loadUserPins = function (Env, publicKey, cb) { } break; default: - console.error('invalid message read from store'); + WARN('invalid message read from store', msg); } } catch (e) { - console.log('invalid message read from store'); - console.error(e); + WARN('invalid message read from store', e); } }, function () { // no more messages @@ -328,8 +336,13 @@ var getMultipleFileSize = function (Env, channels, cb) { channels.forEach(function (channel) { getFileSize(Env, channel, function (e, size) { if (e) { - console.error(e); - counts[channel] = -1; + // most likely error here is that a file no longer exists + // but a user still has it in their drive, and wants to know + // its size. We should find a way to inform them of this in + // the future. For now we can just tell them it has no size. + + //WARN('getFileSize', e); + counts[channel] = 0; return done(); } counts[channel] = size; @@ -504,7 +517,7 @@ var pinChannel = function (Env, publicKey, channels, cb) { getFreeSpace(Env, publicKey, function (e, free) { if (e) { - console.error(e); + WARN('getFreeSpace', e); return void cb(e); } if (pinSize > free) { return void cb('E_OVER_LIMIT'); } @@ -573,7 +586,7 @@ var resetUserPins = function (Env, publicKey, channelList, cb) { getFreeSpace(Env, publicKey, function (e, free) { if (e) { - console.error(e); + WARN('getFreeSpace', e); return void cb(e); } @@ -646,6 +659,9 @@ var makeFileStream = function (root, id, cb) { stream.on('open', function () { cb(void 0, stream); }); + stream.on('error', function (e) { + WARN('stream error', e); + }); } catch (err) { cb('BAD_STREAM'); } @@ -737,14 +753,12 @@ var upload_complete = function (Env, publicKey, cb) { safeMkdir(Path.join(paths.blob, prefix), function (e) { if (e) { - console.error('[safeMkdir]'); - console.error(e); - console.log(); + WARN('safeMkdir', e); return void cb('RENAME_ERR'); } isFile(newPath, function (e, yes) { if (e) { - console.error(e); + WARN('isFile', e); return void cb(e); } if (yes) { @@ -770,7 +784,7 @@ var upload_complete = function (Env, publicKey, cb) { // lol wut handle ur errors Fs.rename(oldPath, newPath, function (e) { if (e) { - console.error(e); + WARN('rename', e); if (retries--) { return setTimeout(function () { @@ -803,7 +817,7 @@ var upload_status = function (Env, publicKey, filesize, cb) { if (filesize >= free) { return cb('NOT_ENOUGH_SPACE'); } isFile(filePath, function (e, yes) { if (e) { - console.error("uploadError: [%s]", e); + WARN('upload', e); return cb('UNNOWN_ERROR'); } cb(e, yes); @@ -834,11 +848,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) // load pin-store... console.log('loading rpc module...'); - var warn = function (e, output) { - if (e && !config.suppressRPCErrors) { - console.error(new Date().toISOString() + ' [' + e + ']', output); - } - }; + if (config.suppressRPCErrors) { SUPPRESS_RPC_ERRORS = true; } var keyOrDefaultString = function (key, def) { return typeof(config[key]) === 'string'? config[key]: def; @@ -937,41 +947,41 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'COOKIE': return void Respond(void 0); case 'RESET': return resetUserPins(Env, safeKey, msg[1], function (e, hash) { - //warn(e, hash); + //WARN(e, hash); return void Respond(e, hash); }); case 'PIN': return pinChannel(Env, safeKey, msg[1], function (e, hash) { - warn(e, hash); + WARN(e, hash); Respond(e, hash); }); case 'UNPIN': return unpinChannel(Env, safeKey, msg[1], function (e, hash) { - warn(e, hash); + WARN(e, hash); Respond(e, hash); }); case 'GET_HASH': return void getHash(Env, safeKey, function (e, hash) { - warn(e, hash); + WARN(e, hash); Respond(e, hash); }); case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit return getTotalSize(Env, safeKey, function (e, size) { if (e) { - warn(e, safeKey); + WARN(e, safeKey); return void Respond(e); } Respond(e, size); }); case 'GET_FILE_SIZE': return void getFileSize(Env, msg[1], function (e, size) { - warn(e, msg[1]); + WARN(e, msg[1]); Respond(e, size); }); case 'UPDATE_LIMITS': return void updateLimits(config, safeKey, function (e, limit) { if (e) { - warn(e, limit); + WARN(e, limit); return void Respond(e); } Respond(void 0, limit); @@ -979,7 +989,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'GET_LIMIT': return void getLimit(Env, safeKey, function (e, limit) { if (e) { - warn(e, limit); + WARN(e, limit); return void Respond(e); } Respond(void 0, limit); @@ -987,7 +997,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'GET_MULTIPLE_FILE_SIZE': return void getMultipleFileSize(Env, msg[1], function (e, dict) { if (e) { - warn(e, dict); + WARN(e, dict); return void Respond(e); } Respond(void 0, dict); @@ -997,7 +1007,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'UPLOAD': if (!privileged) { return deny(); } return void upload(Env, safeKey, msg[1], function (e, len) { - warn(e, len); + WARN(e, len); Respond(e, len); }); case 'UPLOAD_STATUS': @@ -1015,13 +1025,13 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'UPLOAD_COMPLETE': if (!privileged) { return deny(); } return void upload_complete(Env, safeKey, function (e, hash) { - warn(e, hash); + WARN(e, hash); Respond(e, hash); }); case 'UPLOAD_CANCEL': if (!privileged) { return deny(); } return void upload_cancel(Env, safeKey, function (e) { - warn(e); + WARN(e); Respond(e); }); default: @@ -1054,7 +1064,9 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) var updateLimitDaily = function () { updateLimits(config, undefined, function (e) { - if (e) { console.error('Error updating the storage limits', e); } + if (e) { + WARN('limitUpdate', e); + } }); }; updateLimitDaily(); diff --git a/server.js b/server.js index 289184d8a..aed35c2fc 100644 --- a/server.js +++ b/server.js @@ -34,6 +34,7 @@ var setHeaders = (function () { const headers = clone(config.httpHeaders); if (config.contentSecurity) { headers['Content-Security-Policy'] = clone(config.contentSecurity); + if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' } if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) { // backward compat for those who do not merge the new version of the config // when updating. This prevents endless spinner if someone clicks donate. @@ -88,7 +89,9 @@ var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'cont var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); -app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')))); +app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { + maxAge: DEV_MODE? "0d": "365d" +})); app.use("/customize", Express.static(__dirname + '/customize')); app.use("/customize", Express.static(__dirname + '/customize.dist')); @@ -138,7 +141,13 @@ app.get('/api/config', function(req, res){ var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app); httpServer.listen(config.httpPort,config.httpAddress,function(){ - console.log('[%s] listening on port %s', new Date().toISOString(), config.httpPort); + var host = config.httpAddress; + var hostName = !host.indexOf(':') ? '[' + host + ']' : host; + + var port = config.httpPort; + var ps = port === 80? '': ':' + port; + + console.log('\n[%s] server available http://%s%s', new Date().toISOString(), hostName, ps); }); var wsConfig = { server: httpServer }; diff --git a/www/code/code.css b/www/code/code.css index 8c6cb3240..320256ab5 100644 --- a/www/code/code.css +++ b/www/code/code.css @@ -53,6 +53,9 @@ body { font-family: Calibri, Ubuntu, sans-serif; word-wrap: break-word; } +#previewContainer media-tag * { + max-width: 100%; +} #preview { max-width: 40vw; margin: auto; diff --git a/www/code/code.less b/www/code/code.less index 5f1aa2365..47b430350 100644 --- a/www/code/code.less +++ b/www/code/code.less @@ -56,6 +56,9 @@ body { box-sizing: border-box; font-family: Calibri,Ubuntu,sans-serif; word-wrap: break-word; + media-tag * { + max-width:100%; + } } #preview { diff --git a/www/code/main.js b/www/code/main.js index 6a04c73ec..e9f42dae3 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -9,6 +9,7 @@ define([ '/common/cryptpad-common.js', '/common/cryptget.js', '/common/diffMarked.js', + '/bower_components/tweetnacl/nacl-fast.min.js', // needed for media-tag ], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, DiffMd) { var Messages = Cryptpad.Messages; diff --git a/www/common/boot2.js b/www/common/boot2.js index 76ce9bcf9..40f9639e8 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -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')]); }); diff --git a/www/common/common-file.js b/www/common/common-file.js new file mode 100644 index 000000000..ad56bae49 --- /dev/null +++ b/www/common/common-file.js @@ -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 = $('', { id: 'uploadStatus' }); + var $thead = $('').appendTo($table); + $('', {id: id}).appendTo($table); + + var $cancel = $('', {'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 = $('', { + 'class': 'upLink', + 'rel': 'noopener noreferrer' + }).text(obj.metadata.name); + + $('', {id: id}).appendTo($table); - - var $cancel = $('', {'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 andThen = function () { + var ifrw = $('#pad-iframe')[0].contentWindow; + var $iframe = $('#pad-iframe').contents(); + var $appContainer = $iframe.find('#app'); + var $form = $iframe.find('#upload-form'); + var $dlform = $iframe.find('#download-form'); + var $dlview = $iframe.find('#download-view'); + var $label = $form.find('label'); + var $dllabel = $dlform.find('label span'); + var $progress = $iframe.find('#progress'); + var $body = $iframe.find('body'); - var $link = $('', { - 'class': 'upLink', - }).text(obj.metadata.name); + $body.on('dragover', function (e) { e.preventDefault(); }); + $body.on('drop', function (e) { e.preventDefault(); }); - $('
').text(Messages.upload_name).appendTo($thead); + $('').text(Messages.upload_size).appendTo($thead); + $('').text(Messages.upload_progress).appendTo($thead); + $('').text(Messages.cancel).appendTo($thead); + + var createTableContainer = function ($body) { + File.$container = $('
', { 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 = $('
', {'class':'progressContainer'}); + var $progressValue = $('', {'class':'progressValue'}).text(Messages.upload_pending); + + var $tr = $('
').append($link).appendTo($tr); + $('').text(prettySize(estimate)).appendTo($tr); + $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); + $('', {'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; +}); diff --git a/www/common/common-history.js b/www/common/common-history.js index 48b210eb3..e75a192c1 100644 --- a/www/common/common-history.js +++ b/www/common/common-history.js @@ -37,6 +37,17 @@ define([ var parsed = config.href ? common.parsePadUrl(config.href) : {}; var secret = common.getSecrets(parsed.type, parsed.hash); + + History.readOnly = 1; + if (!secret.keys) { + secret.keys = secret.key; + History.readOnly = 2; + } + else if (!secret.keys.validateKey) { + secret.keys.validateKey = true; + History.readOnly = 0; + } + var crypto = Crypto.createEncryptor(secret.keys); var to = window.setTimeout(function () { @@ -185,6 +196,7 @@ define([ 'class':'revertHistory buttonSuccess', title: Messages.history_restoreTitle }).text(Messages.history_restore).appendTo($nav); + if (!History.readOnly) { $rev.hide(); } onUpdate = function () { $cur.attr('max', states.length); diff --git a/www/common/common-title.js b/www/common/common-title.js index 226dc1b34..3b71ec63e 100644 --- a/www/common/common-title.js +++ b/www/common/common-title.js @@ -14,6 +14,7 @@ define(function () { var getHeadingText = cfg.getHeadingText || function () { return; }; var updateLocalTitle = function (newTitle) { exp.title = newTitle; + onLocal(); if (typeof cfg.updateLocalTitle === "function") { cfg.updateLocalTitle(newTitle); } else { @@ -43,11 +44,12 @@ define(function () { onLocal(); }; - exp.updateTitle = function (newTitle) { + // update title: href is optional; if not specified, we use window.location.href + exp.updateTitle = function (newTitle, href) { if (newTitle === exp.title) { return; } // Change the title now, and set it back to the old value if there is an error var oldTitle = exp.title; - Cryptpad.renamePad(newTitle, function (err, data) { + Cryptpad.renamePad(newTitle, href, function (err, data) { if (err) { console.log("Couldn't set pad title"); console.error(err); diff --git a/www/common/common-util.js b/www/common/common-util.js index 0d0d4c776..0bc53f5dd 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -68,7 +68,11 @@ define([], function () { Util.replaceHash = function (hash) { if (window.history && window.history.replaceState) { if (!/^#/.test(hash)) { hash = '#' + hash; } - return void window.history.replaceState({}, window.document.title, hash); + void window.history.replaceState({}, window.document.title, hash); + if (typeof(window.onhashchange) === 'function') { + window.onhashchange(); + } + return; } window.location.hash = hash; }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index fcefbd0fe..a385dea91 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -11,11 +11,13 @@ define([ '/common/common-title.js', '/common/common-metadata.js', '/common/common-codemirror.js', + '/common/common-file.js', '/common/clipboard.js', '/common/pinpad.js', '/customize/application_config.js' -], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) { +], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, + CodeMirror, Files, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata @@ -114,6 +116,9 @@ define([ // CodeMirror common.createCodemirror = CodeMirror.create; + // Files + common.createFileManager = function (config) { return Files.create(common, config); }; + // History common.getHistory = function (config) { return History.create(common, config); }; @@ -126,6 +131,11 @@ define([ return store.getProxy().proxy; } }; + common.getFO = function () { + if (store && store.getProxy()) { + return store.getProxy().fo; + } + }; var getNetwork = common.getNetwork = function () { if (store) { if (store.getProxy() && store.getProxy().info) { @@ -144,7 +154,6 @@ define([ } var href = '/common/feedback.html?' + action + '=' + (+new Date()); - console.log('[feedback] %s', href); $.ajax({ type: "HEAD", url: href, @@ -300,7 +309,7 @@ define([ cb(parsed); } if (!pad.title) { - pad.title = common.getDefaultname(parsed); + pad.title = common.getDefaultName(parsed); } return parsed.hashData; }; @@ -520,8 +529,8 @@ define([ cb ("store.forgetPad is not a function"); }; - common.setPadTitle = function (name, cb) { - var href = window.location.href; + common.setPadTitle = function (name, padHref, cb) { + var href = padHref || window.location.href; var parsed = parsePadUrl(href); if (!parsed.hash) { return; } href = getRelativeHref(href); @@ -581,25 +590,26 @@ define([ return pad; }); + if (updateWeaker.length > 0) { + updateWeaker.forEach(function (obj) { + // If we have a stronger url, and if all the occurences of the weaker were + // in the trash, add remove them from the trash and add the stronger in root + getStore().restoreHref(obj.n); + }); + } if (!contains && href) { var data = makePad(href, name); getStore().pushData(data, function (e, id) { if (e) { if (e === 'E_OVER_LIMIT') { common.alert(Messages.pinLimitNotPinned, null, true); - return; } - else { throw new Error("Cannot push this pad to CryptDrive", e); } + return void cb(e); } getStore().addPad(id, common.initialPath); + cb(err, recent); }); - } - if (updateWeaker.length > 0) { - updateWeaker.forEach(function (obj) { - // If we have a stronger url, and if all the occurences of the weaker were - // in the trash, add remove them from the trash and add the stronger in root - getStore().restoreHref(obj.n); - }); + return; } cb(err, recent); }); @@ -621,24 +631,41 @@ define([ /* * Buttons */ - common.renamePad = function (title, callback) { + common.renamePad = function (title, href, callback) { if (title === null) { return; } if (title.trim() === "") { - var parsed = parsePadUrl(window.location.href); + var parsed = parsePadUrl(href || window.location.href); title = getDefaultName(parsed); } - common.setPadTitle(title, function (err) { + common.setPadTitle(title, href, function (err) { if (err) { console.log("unable to set pad title"); - console.log(err); + console.error(err); return; } callback(null, title); }); }; + common.getUserFilesList = function () { + var store = common.getStore(); + var proxy = store.getProxy(); + var fo = proxy.fo; + var hashes = []; + var list = fo.getFiles().filter(function (id) { + var href = fo.getFileData(id).href; + var parsed = parsePadUrl(href); + if ((parsed.type === 'file' || parsed.type === 'media') + && hashes.indexOf(parsed.hash) === -1) { + hashes.push(parsed.hash); + return true; + } + }); + return list; + }; + var getUserChannelList = common.getUserChannelList = function () { var store = common.getStore(); var proxy = store.getProxy(); @@ -879,6 +906,21 @@ define([ common.getPinnedUsage(todo); }; + var getAppSuffix = function () { + var parts = window.location.pathname.split('/') + .filter(function (x) { return x; }); + + if (!parts[0]) { return ''; } + return '_' + parts[0].toUpperCase(); + }; + + var prepareFeedback = common.prepareFeedback = function (key) { + if (typeof(key) !== 'string') { return $.noop; } + return function () { + feedback(key.toUpperCase() + getAppSuffix()); + }; + }; + common.createButton = function (type, rightside, data, callback) { var button; var size = "17px"; @@ -887,6 +929,8 @@ define([ button = $('
').append($link).appendTo($tr); - $('').text(prettySize(estimate)).appendTo($tr); - $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); - $('', {'class': 'upCancel'}).append($cancel).appendTo($tr); + Cryptpad.addLoadingScreen(); - queue.next(); - }; + var Title; - var uploadMode = false; + var uploadMode = false; - var andThen = function () { var $bar = $iframe.find('.toolbar-container'); var secret; @@ -229,13 +58,6 @@ define([ return data ? data.title : undefined; }; - var exportFile = function () { - var filename = Cryptpad.fixFileName(document.title); - if (!(typeof(filename) === 'string' && filename)) { return; } - var blob = new Blob([myFile], {type: myDataType}); - saveAs(blob, filename); - }; - Title = Cryptpad.createTitle({}, function(){}, Cryptpad); var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade']; @@ -257,60 +79,112 @@ define([ if (uploadMode) { toolbar.title.hide(); } - var $rightside = toolbar.$rightside; - - var $export = Cryptpad.createButton('export', true, {}, exportFile); - $rightside.append($export); - Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle); if (!uploadMode) { - $dlform.show(); var src = Cryptpad.getBlobPathFromHex(hexFileName); var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Nacl.util.decodeBase64(cryptKey); + FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { if (e) { return void console.error(e); } var title = document.title = metadata.name; Title.updateTitle(title || Title.defaultTitle); - Cryptpad.removeLoadingScreen(); - var decrypting = false; - $dlform.find('#dl, #progress').click(function () { - if (decrypting) { return; } - if (myFile) { return void exportFile(); } - decrypting = true; - - return Cryptpad.fetch(src, function (e, u8) { - if (e) { - decrypting = false; - return void Cryptpad.alert(e); - } - - // now decrypt the u8 - if (!u8 || !u8.length) { - return void Cryptpad.errorLoadingScreen(e); + var displayFile = function (ev) { + var $mt = $dlview.find('media-tag'); + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var hexFileName = Cryptpad.base64ToHex(secret.channel); + $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); + $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); + + $(window.document).on('decryption', function (e) { + var decrypted = e.originalEvent; + if (decrypted.callback) { decrypted.callback(); } + + console.log(decrypted); + $dlview.show(); + $dlform.hide(); + var $dlButton = $dlview.find('media-tag button'); + if (ev) { $dlButton.click(); } + if (!$dlButton.length) { + $appContainer.css('background', 'white'); } - - FileCrypto.decrypt(u8, key, function (e, data) { - if (e) { - decrypting = false; - return console.error(e); - } - console.log(data); - var title = document.title = data.metadata.name; - myFile = data.content; - myDataType = data.metadata.type; - Title.updateTitle(title || Title.defaultTitle); - exportFile(); - decrypting = false; - }, function (progress) { - var p = progress * 100 +'%'; - $progress.width(p); - console.error(progress); + $dlButton.addClass('btn btn-success'); + + toolbar.$rightside.append(Cryptpad.createButton('export', true, {}, function () { + saveAs(decrypted.blob, decrypted.metadata.name); + })) + .append(Cryptpad.createButton('forget', true, {}, function () { + // not sure what to do here + })); + + // make pdfs big + $iframe.find('media-tag iframe').css({ + 'height': 'calc(100vh - 64px)', + width: 'calc(100vw - 15px)', }); + }) + .on('decryptionError', function (e) { + var error = e.originalEvent; + Cryptpad.alert(error.message); + }) + .on('decryptionProgress', function (e) { + var progress = e.originalEvent; + var p = progress.percent +'%'; + $progress.width(p); + console.log(progress.percent); }); + + require(['/common/media-tag.js'], function (MediaTag) { + /** + * Allowed mime types that have to be set for a rendering after a decryption. + * + * @type {Array} + */ + var allowedMediaTypes = [ + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/gif', + 'audio/mp3', + 'audio/ogg', + 'audio/wav', + 'audio/webm', + 'video/mp4', + 'video/ogg', + 'video/webm', + 'application/pdf', + 'application/dash+xml', + 'download' + ]; + MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes); + + MediaTag($mt[0]); + }); + }; + + var todoBigFile = function (sizeMb) { + $dlform.show(); + Cryptpad.removeLoadingScreen(); + $dllabel.append($('
')); + $dllabel.append(metadata.name); + $dllabel.append($('
')); + $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; @@ -329,57 +203,18 @@ define([ display: 'block', }); - var handleFile = function (file) { - console.log(file); - var reader = new FileReader(); - reader.onloadend = function () { - queue.push({ - blob: this.result, - metadata: { - name: file.name, - type: file.type, - } - }); - }; - reader.readAsArrayBuffer(file); + var fmConfig = { + dropArea: $form, + hoverArea: $label, + body: $body, + keepTable: true // Don't fadeOut the tbale with the uploaded files }; + var FM = Cryptpad.createFileManager(fmConfig); + $form.find("#file").on('change', function (e) { var file = e.target.files[0]; - handleFile(file); - }); - - var counter = 0; - $label - .on('dragenter', function (e) { - e.preventDefault(); - e.stopPropagation(); - counter++; - $label.addClass('hovering'); - }) - .on('dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - counter--; - if (counter <= 0) { - $label.removeClass('hovering'); - } - }); - - $form - .on('drag dragstart dragend dragover drop dragenter dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - }) - .on('drop', function (e) { - e.stopPropagation(); - var dropped = e.originalEvent.dataTransfer.files; - counter = 0; - $label.removeClass('hovering'); - - Array.prototype.slice.call(dropped).forEach(function (d) { - handleFile(d); - }); + FM.handleFile(file); }); // we're in upload mode diff --git a/www/login/main.js b/www/login/main.js index caa77fe4b..ae860888d 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -64,7 +64,12 @@ define([ $('button.login').click(); }); + var hashing = false; $('button.login').click(function () { + if (hashing) { return void console.log("hashing is already in progress"); } + + hashing = true; + // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up window.setTimeout(function () { Cryptpad.addLoadingScreen(Messages.login_hashing); @@ -89,6 +94,7 @@ define([ Cryptpad.feedback('LOGIN', true); Cryptpad.whenRealtimeSyncs(result.realtime, function() { Cryptpad.login(result.userHash, result.userName, function () { + hashing = false; if (sessionStorage.redirectTo) { var h = sessionStorage.redirectTo; var parser = document.createElement('a'); @@ -107,17 +113,23 @@ define([ switch (err) { case 'NO_SUCH_USER': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_noSuchUser); + Cryptpad.alert(Messages.login_noSuchUser, function () { + hashing = false; + }); }); break; case 'INVAL_USER': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalUser); + Cryptpad.alert(Messages.login_invalUser, function () { + hashing = false; + }); }); break; case 'INVAL_PASS': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalPass); + Cryptpad.alert(Messages.login_invalPass, function () { + hashing = false; + }); }); break; default: // UNHANDLED ERROR diff --git a/www/media/inner.html b/www/media/inner.html index 46e5cea4a..7d04259bb 100644 --- a/www/media/inner.html +++ b/www/media/inner.html @@ -8,17 +8,26 @@ diff --git a/www/media/main.js b/www/media/main.js index bc861d699..c864ad7e4 100644 --- a/www/media/main.js +++ b/www/media/main.js @@ -6,8 +6,8 @@ define([ '/common/cryptpad-common.js', //'/common/visible.js', //'/common/notify.js', - 'pdfjs-dist/build/pdf', - 'pdfjs-dist/build/pdf.worker', + //'pdfjs-dist/build/pdf', + //'pdfjs-dist/build/pdf.worker', '/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/file-saver/FileSaver.min.js', ], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) { diff --git a/www/pad/main.js b/www/pad/main.js index 8be56b330..2a48f12a3 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -487,8 +487,14 @@ define([ var updateIcon = function () { $collapse.removeClass('fa-caret-down').removeClass('fa-caret-up'); var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible'); - if (isCollapsed) { $collapse.addClass('fa-caret-down'); } - else { $collapse.addClass('fa-caret-up'); } + if (isCollapsed) { + if (!initializing) { Cryptpad.feedback('HIDETOOLBAR_PAD'); } + $collapse.addClass('fa-caret-down'); + } + else { + if (!initializing) { Cryptpad.feedback('SHOWTOOLBAR_PAD'); } + $collapse.addClass('fa-caret-up'); + } }; updateIcon(); $collapse.click(function () { @@ -666,6 +672,19 @@ define([ onLocal(); return test; }; + + $bar.find('.cke_button').click(function () { + var e = this; + var classString = e.getAttribute('class'); + var classes = classString.split(' ').filter(function (c) { + return /cke_button__/.test(c); + }); + + var id = classes[0]; + if (typeof(id) === 'string') { + Cryptpad.feedback(id.toUpperCase()); + } + }); }); }; diff --git a/www/poll/index.html b/www/poll/index.html index 31226d989..091086f5c 100644 --- a/www/poll/index.html +++ b/www/poll/index.html @@ -29,6 +29,7 @@
+
diff --git a/www/poll/main.js b/www/poll/main.js index eb0edef26..adfe60ec3 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -46,7 +46,8 @@ define([ editable: { row: [], col: [] - } + }, + locked: false }; var sortColumns = function (order, firstcol) { @@ -97,7 +98,7 @@ define([ // Enable the checkboxes for the user's column (committed or not) $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); - $('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled'); + $('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled'); $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked); if (isOwnColumnCommitted()) { return; } @@ -108,12 +109,14 @@ define([ var unlockElements = function () { APP.editable.row.forEach(function (id) { - $('input[type="text"][disabled="disabled"][data-rt-id="' + id + '"]').removeAttr('disabled'); + var $input = $('input[type="text"][disabled="disabled"][data-rt-id="' + id + '"]').removeAttr('disabled'); + $input.parent().parent().addClass('editing'); $('span.edit[data-rt-id="' + id + '"]').css('visibility', 'hidden'); }); APP.editable.col.forEach(function (id) { - $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); - $('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled'); + var $input = $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); + $input.parent().addClass('editing'); + $('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled'); $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked); }); }; @@ -274,10 +277,17 @@ define([ Render.setValue(object, id, input.value); change(null, null, null, 50); break; - case 'checkbox': - debug("checkbox[tr-id='%s'] %s", id, input.checked); + case 'number': + debug("checkbox[tr-id='%s'] %s", id, input.value); if (APP.editable.col.indexOf(x) >= 0 || x === APP.userid) { - Render.setValue(object, id, input.checked); + var value = parseInt(input.value); + + if (isNaN(value)) { + console.error("Got NaN?!"); + break; + } + + Render.setValue(object, id, value); change(); } else { debug('checkbox locked'); @@ -290,12 +300,14 @@ define([ }; var hideInputs = function (target, isKeyup) { + if (APP.locked) { return; } if (!isKeyup && $(target).is('[type="text"]')) { return; } $('.lock[data-rt-id!="' + APP.userid + '"]').addClass('fa-lock').removeClass('fa-unlock').attr('title', Messages.poll_locked); var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td'); $cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true); + $cells.removeClass('editing'); $('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible'); APP.editable.col = [APP.userid]; APP.editable.row = []; @@ -349,9 +361,13 @@ define([ }; var handleClick = function (e, isKeyup) { + if (APP.locked) { return; } + e.stopPropagation(); if (!APP.ready) { return; } + if (!isKeyup && e.which !== 1) { return; } // only allow left clicks + var target = e && e.target; if (!target) { return void debug("NO TARGET"); } @@ -369,10 +385,19 @@ define([ hideInputs(target, isKeyup); break; } + if ($(target).is('input[type="number"]')) { console.error("number input focused?"); break; } + handleInput(target); break; + case 'LABEL': + var input = $('input[type="number"][id=' + $(target).attr('for') + ']'); + var value = parseInt(input.val()); + + input.val((value + 1) % 4); + + handleInput(input[0]); + break; case 'SPAN': - //case 'LABEL': if (shouldLock) { break; } @@ -421,6 +446,15 @@ define([ }); }; + var showHelp = function(help) { + if (typeof help === 'undefined') { help = !$('#howItWorks').is(':visible'); } + + var msg = (help ? Messages.poll_hide_help_button : Messages.poll_show_help_button); + + $('#howItWorks').toggle(help); + $('#help').text(msg).attr('title', msg); + }; + var Title; var UserList; @@ -457,7 +491,7 @@ var ready = function (info, userid, readOnly) { APP.$createRow = $('#create-option').click(function () { Render.createRow(proxy, function (empty, id) { change(null, null, null, null, function() { - $('.edit[data-rt-id="' + id + '"]').click(); + handleSpan($('.edit[data-rt-id="' + id + '"]')[0]); }); }); }); @@ -465,7 +499,7 @@ var ready = function (info, userid, readOnly) { APP.$createCol = $('#create-user').click(function () { Render.createColumn(proxy, function (empty, id) { change(null, null, null, null, function() { - $('.lock[data-rt-id="' + id + '"]').click(); + handleSpan($('.lock[data-rt-id="' + id + '"]')[0]); }); }); }); @@ -486,12 +520,16 @@ var ready = function (info, userid, readOnly) { publish(true); }); - // #publish button is removed in readonly APP.$admin = $('#admin') .click(function () { publish(false); }); + APP.$help = $('#help') + .click(function () { + showHelp(); + }); + // Title if (APP.proxy.info.defaultTitle) { Title.updateDefaultTitle(APP.proxy.info.defaultTitle); @@ -527,7 +565,10 @@ var ready = function (info, userid, readOnly) { .click(handleClick) .on('keyup', function (e) { handleClick(e, true); }); - $(window).click(hideInputs); + $(window).click(function(e) { + if (e.which !== 1) { return; } + hideInputs(); + }); proxy .on('change', ['info'], function (o, n, p) { @@ -570,14 +611,33 @@ var ready = function (info, userid, readOnly) { UserList.getLastName(APP.toolbar.$userNameButton, isNew); }; +var setEditable = function (editable) { + APP.locked = !editable; + + if (editable === false) { + // disable all the things + $('.realtime input, .realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked); + $('span.edit, span.remove').hide(); + $('span.lock').addClass('fa-lock').removeClass('fa-unlock') + .attr('title', Messages.poll_locked) + .css({'cursor': 'default'}); + } else { + // enable + $('span.edit, span.remove').show(); + $('span.lock').css({'cursor': ''}); + $('.realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked); + unlockElements(); + } +}; + var disconnect = function () { - //setEditable(false); // TODO + setEditable(false); APP.toolbar.failed(); Cryptpad.alert(Messages.common_connectionLost, undefined, true); }; var reconnect = function (info) { - //setEditable(true); // TODO + setEditable(true); APP.toolbar.reconnecting(info.myId); Cryptpad.findOKButton().click(); }; @@ -632,7 +692,7 @@ var create = function (info) { /* add a forget button */ var forgetCb = function (err) { if (err) { return; } - disconnect(); + setEditable(false); }; var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); $rightside.append($forgetPad); @@ -698,12 +758,13 @@ var create = function (info) { Cryptpad.setAttribute(HIDE_INTRODUCTION_TEXT, "1", function (e) { if (e) { console.error(e); } }); - } else if (value === "1") { - $('#howItWorks').hide(); + showHelp(true); + } else { + showHelp(false); } }); - //Cryptpad.onLogout(function () { setEditable(false); }); TODO + Cryptpad.onLogout(function () { setEditable(false); }); }); Cryptpad.onError(function (info) { if (info) { diff --git a/www/poll/poll.css b/www/poll/poll.css index 3e534d5e2..979f7f2b8 100644 --- a/www/poll/poll.css +++ b/www/poll/poll.css @@ -170,6 +170,10 @@ div.realtime table { border-collapse: collapse; width: calc(100% - 1px); } +form.realtime table .editing, +div.realtime table .editing { + background-color: #88b8cc; +} form.realtime table tr td:first-child, div.realtime table tr td:first-child { position: absolute; @@ -225,40 +229,63 @@ div.realtime table tr td.checkbox-cell div.checkbox-contain label { height: 100%; width: 100%; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable), -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable), +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) { display: none; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover { font-weight: bold; - background-color: #FA5858; color: #000; display: block; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover:after, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover:after { height: 100%; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.yes, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.yes { + background-color: #46E981; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.uncommitted, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.uncommitted { + background: #ddd; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.mine, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"]:not(.editable) ~ .cover.mine { + display: none; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover { + background-color: #FA5858; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover:after, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="0"] ~ .cover:after { content: "✖"; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover { background-color: #46E981; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after { +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover:after, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="1"] ~ .cover:after { content: "✔"; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted { - background: #ddd; +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover { + background-color: #ff5; } -form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine, -div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine { - display: none; +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover:after, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="2"] ~ .cover:after { + content: "~"; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover { + background-color: #ccc; +} +form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover:after, +div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="number"][value="3"] ~ .cover:after { + content: "?"; } form.realtime table input[type="text"], div.realtime table input[type="text"] { @@ -298,8 +325,8 @@ div.realtime table thead td input[type="text"][disabled] { color: #000; border: 1px solid transparent; } -form.realtime table tbody .text-cell, -div.realtime table tbody .text-cell { +form.realtime table tbody td:not(.editing) .text-cell, +div.realtime table tbody td:not(.editing) .text-cell { background: #aaa; } form.realtime table tbody .text-cell input[type="text"], diff --git a/www/poll/poll.less b/www/poll/poll.less index 699e96e03..95b5cfa52 100644 --- a/www/poll/poll.less +++ b/www/poll/poll.less @@ -4,10 +4,13 @@ @poll-th-bg: #aaa; @poll-th-user-bg: #999; @poll-td-bg: #aaa; +@poll-editing: #88b8cc; @poll-placeholder: #666; @poll-border-color: #555; @poll-cover-color: #000; @poll-fg: #000; +@poll-option-yellow: #ff5; +@poll-option-gray: #ccc; html, body { width: 100%; @@ -195,6 +198,9 @@ form.realtime, div.realtime { table { border-collapse: collapse; width: ~"calc(100% - 1px)"; + .editing { + background-color: @poll-editing; + } tr { td:first-child { position:absolute; @@ -247,7 +253,7 @@ form.realtime, div.realtime { } input { - &[type="checkbox"] { + &[type="number"] { &:not(.editable) { display: none; @@ -255,26 +261,21 @@ form.realtime, div.realtime { display: block; font-weight: bold; - background-color: @cp-red; color: @poll-cover-color; &:after { height: 100%; } - &:after { content: "✖"; } - display: block; &.yes { background-color: @cp-green; - &:after { content: "✔"; } } &.uncommitted { background: #ddd; } - &.mine { display: none; } @@ -282,6 +283,31 @@ form.realtime, div.realtime { } } } + + input[type="number"][value="0"] { + ~ .cover { + background-color: @cp-red; + &:after { content: "✖"; } + } + } + input[type="number"][value="1"] { + ~ .cover { + background-color: @cp-green; + &:after { content: "✔"; } + } + } + input[type="number"][value="2"] { + ~ .cover { + background-color: @poll-option-yellow; + &:after { content: "~"; } + } + } + input[type="number"][value="3"] { + ~ .cover { + background-color: @poll-option-gray; + &:after { content: "?"; } + } + } } } } @@ -327,8 +353,12 @@ form.realtime, div.realtime { } tbody { + td:not(.editing) { + .text-cell { + background: @poll-td-bg; + } + } .text-cell { - background: @poll-td-bg; //border-radius: 20px 0 0 20px; input[type="text"] { width: ~"calc(100% - 50px)"; diff --git a/www/poll/render.js b/www/poll/render.js index 8399d5b71..f84fdc964 100644 --- a/www/poll/render.js +++ b/www/poll/render.js @@ -70,7 +70,12 @@ var Renderer = function (Cryptpad) { }; var getCellValue = Render.getCellValue = function (obj, cellId) { - return Cryptpad.find(obj, ['table', 'cells'].concat([cellId])); + var value = Cryptpad.find(obj, ['table', 'cells'].concat([cellId])); + if (typeof value === 'boolean') { + return (value === true ? 1 : 0); + } else { + return value; + } }; var setRowValue = Render.setRowValue = function (obj, rowId, value) { @@ -234,16 +239,20 @@ var Renderer = function (Cryptpad) { disabled: 'disabled' }].concat(cols.map(function (col) { var id = [col, rows[i-1]].join('_'); - var val = cells[id] || false; + var val = cells[id]; var result = { 'data-rt-id': id, - type: 'checkbox', + type: 'number', autocomplete: 'nope', + value: '3', }; if (readOnly) { result.disabled = "disabled"; } - if (val) { result.checked = true; } + if (typeof val !== 'undefined') { + if (typeof val === 'boolean') { val = (val ? '1' : '0'); } + result.value = val; + } return result; })); }); @@ -297,9 +306,6 @@ var Renderer = function (Cryptpad) { attrs.id = cell['data-rt-id']; var labelClass = 'cover'; - if (cell.checked) { - labelClass += ' yes'; - } // TODO implement Yes/No/Maybe/Undecided return ['TD', {class:"checkbox-cell"}, [ @@ -326,7 +332,7 @@ var Renderer = function (Cryptpad) { ]]; } - if (cell && cell.type === 'checkbox') { + if (cell && cell.type === 'number') { return makeCheckbox(cell); } return ['TD', cell, []]; @@ -399,7 +405,7 @@ var Renderer = function (Cryptpad) { preDiffApply: function (info) { if (!diffIsInput(info)) { return; } switch (getInputType(info)) { - case 'checkbox': + case 'number': //console.log('checkbox'); //console.log("[preDiffApply]", info); break; diff --git a/www/register/main.js b/www/register/main.js index bdaca5197..f0b9a2e9c 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -63,6 +63,7 @@ define([ var $register = $('button#register'); + var registering = false; var logMeIn = function (result) { if (Test.testing) { Test.passed(); @@ -79,6 +80,7 @@ define([ Cryptpad.whenRealtimeSyncs(result.realtime, function () { Cryptpad.login(result.userHash, result.userName, function () { + registering = false; if (sessionStorage.redirectTo) { var h = sessionStorage.redirectTo; var parser = document.createElement('a'); @@ -95,6 +97,11 @@ define([ }; $register.click(function () { + if (registering) { + console.log("registration is already in progress"); + return; + } + var uname = $uname.val(); var passwd = $passwd.val(); var confirmPassword = $confirm.val(); @@ -115,6 +122,7 @@ define([ function (yes) { if (!yes) { return; } + registering = true; // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up window.setTimeout(function () { Cryptpad.addLoadingScreen(Messages.login_hashing); @@ -127,20 +135,27 @@ define([ switch (err) { case 'NO_SUCH_USER': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_noSuchUser); + Cryptpad.alert(Messages.login_noSuchUser, function () { + registering = false; + }); }); break; case 'INVAL_USER': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalUser); + Cryptpad.alert(Messages.login_invalUser, function () { + registering = false; + }); }); break; case 'INVAL_PASS': Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalPass); + Cryptpad.alert(Messages.login_invalPass, function () { + registering = false; + }); }); break; case 'ALREADY_REGISTERED': + // logMeIn should reset registering = false Cryptpad.removeLoadingScreen(function () { Cryptpad.confirm(Messages.register_alreadyRegistered, function (yes) { if (!yes) { return; } @@ -155,6 +170,7 @@ define([ }); break; default: // UNHANDLED ERROR + registering = false; Cryptpad.errorLoadingScreen(Messages.login_unhandledError); } return; diff --git a/www/settings/index.html b/www/settings/index.html index 28b36675e..d628be352 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -108,7 +108,7 @@
- + diff --git a/www/slide/inner.html b/www/slide/inner.html index 8fde658df..1726cd2f1 100644 --- a/www/slide/inner.html +++ b/www/slide/inner.html @@ -40,18 +40,19 @@
- - - - - + diff --git a/www/whiteboard/index.html b/www/whiteboard/index.html index d9496c344..7311e5973 100644 --- a/www/whiteboard/index.html +++ b/www/whiteboard/index.html @@ -25,7 +25,7 @@ - +
 
diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index dce656473..1949549af 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -97,7 +97,7 @@ window.canvas = canvas; var updateBrushWidth = function () { var val = $width.val(); canvas.freeDrawingBrush.width = Number(val); - $widthLabel.text(val); + $widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val])); createCursor(); }; updateBrushWidth(); @@ -108,7 +108,7 @@ window.canvas = canvas; var val = $opacity.val(); brush.opacity = Number(val); canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity); - $opacityLabel.text(val); + $opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val])); createCursor(); }; updateBrushOpacity(); @@ -338,12 +338,12 @@ window.canvas = canvas; }); $rightside.append($forget); - makeColorButton($rightside); var editHash; if (!readOnly) { editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); + makeColorButton($rightside); } if (!readOnly) { Cryptpad.replaceHash(editHash); } };