diff --git a/.gitignore b/.gitignore index fc1136152..741aedaf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ datastore +tasks www/bower_components/* node_modules /config.js diff --git a/config.example.js b/config.example.js index 49ba68541..2c234aee4 100644 --- a/config.example.js +++ b/config.example.js @@ -194,6 +194,18 @@ module.exports = { */ }, + /* some features may require that the server be able to schedule tasks + far into the future, such as: + > "three months from now, this channel should expire" + To disable these features, set 'enableTaskScheduling' to false + */ + enableTaskScheduling: true, + + /* if you would like the list of scheduled tasks to be stored in + a custom location, change the path below: + */ + taskPath: './tasks', + /* * By default, CryptPad also contacts our accounts server once a day to check for changes in * the people who have accounts. This check-in will also send the version of your CryptPad diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 612994d54..65b306f42 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -79,7 +79,7 @@ define(req, function(Util, Default, Language) { }); Object.keys(translation).forEach(function (k) { if (/^_/.test(k) || k === 'driveReadme') { return; } - if (!Default[k]) { + if (typeof Default[k] === "undefined") { missing.push([code, k, 0]); } }); diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 40ea81467..4de6e6594 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -100,6 +100,7 @@ define([ h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog), + h('a.nav-item.nav-link', { href: '/features.html'}, Msg.features), h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact), h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about), ].concat(rightLinks)) @@ -332,6 +333,11 @@ define([ h('td') ]), ]) + ]), + h('div#cp-features-register', [ + h('a', { + href: '/register/' + }, h('button.cp-features-register-button', 'Register for free')) ]) ]), infopageFooter() @@ -601,6 +607,9 @@ define([ setHTML(h('p.register-explanation'), Msg.register_explanation) ]), h('div#userForm.form-group.hidden.col-md-6', [ + h('a', { + href: '/features.html' + }, Msg.register_whyRegister), h('input.form-control#username', { type: 'text', autocomplete: 'off', diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index c7b795e4f..aa94beb28 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -139,5 +139,14 @@ } } } + .cp-creation-settings { + justify-content: left; + a { + color: #0275d8; + &:hover { + color: lighten(#0275d8, 10%); + } + } + } } } diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less index 12f26ef4b..4c11183c4 100644 --- a/customize.dist/src/less2/include/limit-bar.less +++ b/customize.dist/src/less2/include/limit-bar.less @@ -8,9 +8,10 @@ width: 100%; margin-top: 20px; .cp-limit-bar { - padding: 5px; + display: inline-flex; + justify-content: center; + align-items: center; - display: inline-block; max-width: 100%; margin: 3px; box-sizing: border-box; @@ -18,7 +19,6 @@ background: white; position: relative; text-align: center; - vertical-align: middle; width: ~"calc(100% - 6px)"; height: 35px; line-height: 25px; diff --git a/customize.dist/src/less2/pages/page-features.less b/customize.dist/src/less2/pages/page-features.less index cc44bc89d..37f2be245 100644 --- a/customize.dist/src/less2/pages/page-features.less +++ b/customize.dist/src/less2/pages/page-features.less @@ -50,3 +50,18 @@ table#cp-features-table { } } +#cp-features-register { + text-align: center; + padding: 20px; +} +.cp-features-register-button { + font-size: 20px; + color: #fff; + background: @cryptpad_color_blue; + border: 2px solid @cryptpad_color_blue; + border-radius: 0; + &:hover { + transform: scale(1.05); + } +} + diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 83e652588..2c4bb6ec4 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -377,7 +377,8 @@ define(function () { out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; - out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; + out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; + out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?"; out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?"; out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?"; @@ -392,7 +393,8 @@ define(function () { out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive"; out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here - out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + + out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads seront donc supprimés après 3 mois d\'inactivité (découvrez pourquoi). ' + + 'Ils sont stockés dans votre navigateur donc nettoyer votre historique peut les faire disparaître.
' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs."; out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.
" + @@ -415,6 +417,7 @@ define(function () { "Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur."; out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad"; out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur"; + out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}"; // File - Context menu out.fc_newfolder = "Nouveau dossier"; out.fc_rename = "Renommer"; @@ -474,6 +477,7 @@ define(function () { out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants."; out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?"; + out.register_whyRegister = "Pourquoi s'inscrire ?"; out.register_header = "Bienvenue dans CryptPad"; out.register_explanation = [ "

Faisons d'abord le point sur certaines choses

", @@ -567,7 +571,9 @@ define(function () { out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; out.upload_notEnoughSpace = "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier."; + out.upload_notEnoughSpaceBrief = "Pas assez d'espace"; out.upload_tooLarge = "Ce fichier dépasse la taille maximale autorisée."; + out.upload_tooLargeBrief = 'Fichier trop volumineux'; out.upload_choose = "Choisir un fichier"; out.upload_pending = "En attente"; out.upload_cancelled = "Annulé"; @@ -577,6 +583,7 @@ define(function () { out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier"; out.download_button = "Déchiffrer et télécharger"; out.download_mt_button = "Télécharger"; + out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible..."; out.todo_title = "CryptTodo"; out.todo_newTodoNamePlaceholder = "Décrivez votre tâche..."; @@ -707,6 +714,7 @@ define(function () { // features.html + out.features = "Fonctionnalités"; out.features_title = "Tableau des fonctionnalités"; out.features_feature = "Fonctionnalité"; out.features_anon = "Utilisateur anonyme"; @@ -839,6 +847,24 @@ define(function () { out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans votre page de préférences, où vous trouverez une case à cocher pour désactiver le retour d'expérience."; // Creation page + out.creation_404 = "Le pad auquel vous souhaitez accéder n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant."; + out.creation_ownedTitle = "Type de pad"; + out.creation_ownedTrue = "Pad possédé"; + out.creation_ownedFalse = "Pad ouvert"; + out.creation_owned1 = "Un pad possédé est un pad que vous pouvez supprimer du serveur à n'importe quel moment depuis votre CryptDrive. Une fois supprimé, personne d'autre ne peut y accéder, même si le pad est stocké dans un autre CryptDrive."; + out.creation_owned2 = "Un pad ouvert n'a pas de propriétaire et ne peut donc pas être supprimé du serveur par un utilisateur. Il pourra tout de même être supprimé automatiquement si sa date d'expiration est dépassée."; + out.creation_expireTitle = "Durée de vie"; + out.creation_expireTrue = "Ajouter durée de vie"; + out.creation_expireFalse = "Illimitée"; + out.creation_expireHours = "Heures"; + out.creation_expireDays = "Jours"; + out.creation_expireMonths = "Mois"; + out.creation_expire1 = "Par défault, un pad stocké dans le CryptDrive d'un utilisateur enregistré ne sera jamais supprimé du serveur, même s'il est inactif (à moins qu'il possède un propriétaire souhaitement le supprimer)."; + out.creation_expire2 = "Si vous le souhaitez, vous pouvez ajouter une durée de vie au pad afin d'être sûr qu'il soit supprimé du serveur, de manière permanente, à la date voulue."; + out.creation_createTitle = "Créer un pad"; + out.creation_createFromTemplate = "Depuis un modèle"; + out.creation_createFromScratch = "Nouveau pad vide"; + out.creation_settings = "Préférences des nouveaux pads"; // Properties about creation data out.creation_owners = "Propriétaires"; out.creation_ownedByOther = "Possédé par un autre utilisateur"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index f096341b6..32c61825f 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -381,7 +381,8 @@ define(function () { out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; - out.fm_deleteOwnedPads = "Are you sure you want to remove permanently this pad from the server?"; + out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?"; + out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; @@ -395,7 +396,8 @@ define(function () { out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (find out more). ' + - 'Sign up or Log in to keep them alive.'; + 'They are stored in your browser so clearing history may make them disappear.
' + + 'Sign up or Log in to keep them alive.
'; out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore."; out.fm_alert_backupUrl = "Backup link for this drive.
" + "It is highly recommended that you keep it secret.
" + @@ -417,6 +419,7 @@ define(function () { "This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server."; out.fm_padIsOwned = "You are the owner of this pad"; out.fm_padIsOwnedOther = "This pad is owned by another user"; + out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}"; // File - Context menu out.fc_newfolder = "New folder"; out.fc_rename = "Rename"; @@ -472,6 +475,7 @@ define(function () { 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_whyRegister = "Why signing up?"; out.register_header = "Welcome to CryptPad"; out.register_explanation = [ "

Lets go over a couple things first:

", @@ -717,6 +721,7 @@ define(function () { // features.html + out.features = "Features"; out.features_title = "Features table"; out.features_feature = "Feature"; out.features_anon = "Anonymous user"; @@ -738,6 +743,7 @@ define(function () { out.features_f_multiple = "Use on multiple devices"; out.features_f_multiple_notes = "Easy way to access your pads from any device"; out.features_f_logoutEverywhere = "Log out from other devices"; + out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain out.features_f_templates = "Use templates"; out.features_f_templates_notes = "Create templates and create new pads from your templates"; out.features_f_profile = "Create a profile"; @@ -873,6 +879,7 @@ define(function () { out.creation_createTitle = "Create a pad"; out.creation_createFromTemplate = "From template"; out.creation_createFromScratch = "From scratch"; + out.creation_settings = "New Pad settings"; // Properties about creation data out.creation_owners = "Owners"; out.creation_ownedByOther = "Owned by another user"; diff --git a/rpc.js b/rpc.js index 16360544b..94d4d4e7e 100644 --- a/rpc.js +++ b/rpc.js @@ -11,6 +11,8 @@ var Path = require("path"); var Https = require("https"); const Package = require('./package.json'); const Pinned = require('./pinned'); +const Saferphore = require("saferphore"); +const nThen = require("nthen"); var RPC = module.exports; @@ -355,6 +357,37 @@ var getMultipleFileSize = function (Env, channels, cb) { }); }; +/* accepts a list, and returns a sublist of channel or file ids which seem + to have been deleted from the server (file size 0) + + we might consider that we should only say a file is gone if fs.stat returns + ENOENT, but for now it's simplest to just rely on getFileSize... +*/ +var getDeletedPads = function (Env, channels, cb) { + if (!Array.isArray(channels)) { return cb('INVALID_LIST'); } + var L = channels.length; + + var sem = Saferphore.create(10); + var absentees = []; + + var job = function (channel, wait) { + return function (give) { + getFileSize(Env, channel, wait(give(function (e, size) { + if (e) { return; } + if (size === 0) { absentees.push(channel); } + }))); + }; + }; + + nThen(function (w) { + for (var i = 0; i < L; i++) { + sem.take(job(channels[i], w)); + } + }).nThen(function () { + cb(void 0, absentees); + }); +}; + var getTotalSize = function (Env, publicKey, cb) { var bytes = 0; return void getChannelList(Env, publicKey, function (channels) { @@ -1004,7 +1037,8 @@ var isUnauthenticatedCall = function (call) { 'GET_MULTIPLE_FILE_SIZE', 'IS_CHANNEL_PINNED', 'IS_NEW_CHANNEL', - 'GET_HISTORY_OFFSET' + 'GET_HISTORY_OFFSET', + 'GET_DELETED_PADS', ].indexOf(call) !== -1; }; @@ -1133,6 +1167,14 @@ RPC.create = function ( } respond(e, [null, dict, null]); }); + case 'GET_DELETED_PADS': + return void getDeletedPads(Env, msg[1], function (e, list) { + if (e) { + WARN(e, msg[1]); + return respond(e); + } + respond(e, [null, list, null]); + }); case 'IS_CHANNEL_PINNED': return void isChannelPinned(Env, msg[1], function (isPinned) { respond(null, [null, isPinned, null]); diff --git a/server.js b/server.js index 0a39888f5..e803a829e 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ var WebSocketServer = require('ws').Server; var NetfluxSrv = require('./node_modules/chainpad-server/NetfluxWebsocketSrv'); var Package = require('./package.json'); var Path = require("path"); +var nThen = require("nthen"); var config; try { @@ -213,35 +214,38 @@ if (config.httpSafePort) { var wsConfig = { server: httpServer }; -var createSocketServer = function (err, rpc) { - if(!config.useExternalWebsocket) { - if (websocketPort !== config.httpPort) { - console.log("setting up a new websocket server"); - wsConfig = { port: websocketPort}; - } - var wsSrv = new WebSocketServer(wsConfig); - Storage.create(config, function (store) { - NetfluxSrv.run(store, wsSrv, config, rpc); - }); - } -}; +var rpc; -var loadRPC = function (cb) { +var nt = nThen(function (w) { + if (!config.enableTaskScheduling) { return; } + var Tasks = require("./storage/tasks"); + console.log("loading task scheduler"); + Tasks.create(config, w(function (e, tasks) { + config.tasks = tasks; + })); +}).nThen(function (w) { config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; - - if (typeof(config.rpc) === 'string') { - // load pin store... - var Rpc = require(config.rpc); - Rpc.create(config, debuggable, function (e, rpc) { - if (e) { throw e; } - cb(void 0, rpc); - }); - } else { - cb(); + if (typeof(config.rpc) !== 'string') { return; } + // load pin store... + var Rpc = require(config.rpc); + Rpc.create(config, debuggable, w(function (e, _rpc) { + if (e) { + w.abort(); + throw e; + } + rpc = _rpc; + })); +}).nThen(function () { + if(config.useExternalWebsocket) { return; } + if (websocketPort !== config.httpPort) { + console.log("setting up a new websocket server"); + wsConfig = { port: websocketPort}; } -}; - -loadRPC(createSocketServer); + var wsSrv = new WebSocketServer(wsConfig); + Storage.create(config, function (store) { + NetfluxSrv.run(store, wsSrv, config, rpc); + }); +}); if (config.debugReplName) { require('replify')({ name: config.debugReplName, app: debuggableStore }); diff --git a/storage/tasks.js b/storage/tasks.js new file mode 100644 index 000000000..85df70a8d --- /dev/null +++ b/storage/tasks.js @@ -0,0 +1,94 @@ +var Fs = require("fs"); +var Path = require("path"); +var nacl = require("tweetnacl"); +var nThen = require("nthen"); + +var Tasks = module.exports; + +var encode = function (time, command, args) { + if (typeof(time) !== 'number') { return null; } + if (typeof(command) !== 'string') { return null; } + if (!Array.isArray(args)) { return [time, command]; } + return [time, command].concat(args); +}; + +var randomId = function () { + var bytes = Array.prototype.slice.call(nacl.randomBytes(16)); + return bytes.map(function (b) { + var n = Number(b & 0xff).toString(16); + return n.length === 1? '0' + n: n; + }).join(''); +}; + +var mkPath = function (env, id) { + return Path.join(env.root, id.slice(0, 2), id) + '.ndjson'; +}; + +var getFreeId = function (env, cb, tries) { + if (tries > 5) { return void cb('ETOOMANYTRIES'); } + + // generate a unique id + var id = randomId(); + + // derive a path from that id + var path = mkPath(env, id); + + Fs.stat(path, function (err) { + if (err && err.code === "ENOENT") { + cb(void 0, id); + } else { + getFreeId(env, cb); + } + }); +}; + +var write = function (env, task, cb) { + var str = JSON.stringify(task) + '\n'; + var id = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(str))).replace(/\//g, '-'); + + var path = mkPath(env, id); + nThen(function (w) { + // check if the file already exists... + Fs.stat(path, w(function (err) { + if (err && err.code === 'ENOENT') { return; } + w.abort(); cb(); + })); + }).nThen(function (w) { + // create the parent directory if it does not exist + var dir = id.slice(0, 2); + var dirpath = Path.join(env.root, dir); + + Fs.mkdir(dirpath, 0x1ff, w(function (err) { + if (err && err.code !== 'EEXIST') { + return void cb(err); + } + })); + }).nThen(function () { + // write the file to the path + Fs.writeFile(mkPath(env, id), str, function (e) { + if (e) { return void cb(e); } + cb(); + }); + }); +}; + +Tasks.create = function (config, cb) { + var env = { + root: config.taskPath || './tasks', + }; + + // make sure the path exists... + Fs.mkdir(env.root, 0x1ff, function (err) { + if (err && err.code !== 'EEXIST') { + throw err; + } + cb(void 0, { + write: function (time, command, args, cb) { + var task = encode(time, command, args); + write(env, task, cb); + }, + }); + }); +}; + + diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 24c46b790..91520c99a 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -42,6 +42,11 @@ define(function() { '#800080', // purple ]; + // Background color in the apps with centered content: + // - file app in view mode + // - rich text app when editor's width reduced in settings + config.appBackgroundColor = '#666'; + // Set enableTemplates to false to remove the button allowing users to save a pad as a template // and remove the template category in CryptDrive config.enableTemplates = true; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index d7d95e78a..870b19dfe 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -6,7 +6,7 @@ define([ ], function (Util, Messages, Crypto) { var Nacl = window.nacl; - var Hash = {}; + var Hash = window.CryptPad_Hash = {}; var uint8ArrayToHex = Util.uint8ArrayToHex; var hexToBase64 = Util.hexToBase64; @@ -176,7 +176,7 @@ Version 1 secret.keys = Crypto.createEditCryptor(); secret.key = Crypto.createEditCryptor().editKeyStr; }; - if (!secretHash && !/#/.test(window.location.href)) { + if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { generate(); return secret; } else { @@ -300,6 +300,8 @@ Version 1 var rHref = href || getRelativeHref(window.location.href); var parsed = parsePadUrl(rHref); if (!parsed.hash) { return false; } + // We can't have a stronger hash if we're already in edit mode + if (parsed.hashData && parsed.hashData.mode === 'edit') { return; } var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index f35b361ba..f20bb4224 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -317,11 +317,11 @@ define([ message = dialog.message(msg); } - var close = Util.once(function (el) { + var close = function (el) { var $el = $(el).fadeOut(150, function () { - $el.remove(); + $el.detach(); }); - }); + }; var navs = []; opt.buttons.forEach(function (b) { @@ -329,7 +329,7 @@ define([ var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name); $(button).click(function () { b.onClick(); - close($(this).parents('.alertify').first()); + close($(button).parents('.alertify').first()); }); if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); } navs.push(button); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a11ec5859..d31fe0a70 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1816,6 +1816,13 @@ define([ $button.click(function () { create(); }); + + // Settings button + var origin = common.getMetadataMgr().getPrivateData().origin; + $(h('div.cp-creation-settings', h('a', { + href: origin + '/settings/#creation', + target: '_blank' + }, Messages.creation_settings))).appendTo($creation); }; return UIElements; diff --git a/www/common/common-util.js b/www/common/common-util.js index 030536751..98eda7373 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -1,6 +1,6 @@ (function (window) { define([], function () { - var Util = {}; + var Util = window.CryptPad_Util = {}; // If once is true, after the event has been fired, any further handlers which are // registered will fire immediately, and this type of event cannot be fired twice. diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5ec198a44..fcfab50f0 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -195,6 +195,16 @@ define([ common.clearOwnedChannel = function (channel, cb) { postMessage("CLEAR_OWNED_CHANNEL", channel, cb); }; + common.removeOwnedChannel = function (channel, cb) { + postMessage("REMOVE_OWNED_CHANNEL", channel, cb); + }; + + common.getDeletedPads = function (cb) { + postMessage("GET_DELETED_PADS", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; common.uploadComplete = function (cb) { postMessage("UPLOAD_COMPLETE", null, function (obj) { @@ -550,6 +560,11 @@ define([ return void cb(null, hashes); } + if (hashes.editHash) { + // no need to find stronger if we already have edit hash + return void cb(null, hashes); + } + postMessage("GET_STRONGER_HASH", { href: window.location.href }, function (hash) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e00500472..3b61acaa5 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -94,9 +94,27 @@ define([ return list; }; - var getCanonicalChannelList = function () { - return Util.deduplicateString(getUserChannelList()).sort(); + var getExpirableChannelList = function () { + var list = []; + store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { + var data = store.userObject.getFileData(id); + var edPublic = store.proxy.edPublic; + + // Push channels owned by someone else or channel that should have expired + // because of the expiration time + if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || + (data.expire && data.expire < (+new Date()))) { + list.push(Hash.hrefToHexChannelId(data.href)); + } + }); + return list; + }; + + var getCanonicalChannelList = function (expirable) { + var list = expirable ? getExpirableChannelList() : getUserChannelList(); + return Util.deduplicateString(list).sort(); }; + ////////////////////////////////////////////////////////////////// /////////////////////// RPC ////////////////////////////////////// ////////////////////////////////////////////////////////////////// @@ -172,6 +190,34 @@ define([ }); }; + Store.removeOwnedChannel = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + store.rpc.removeOwnedChannel(data, function (err) { + cb({error:err}); + }); + }; + + var arePinsSynced = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(false); + var local = Hash.hashChannelList(list); + store.rpc.getServerHash(function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash === local); + }); + }; + + var resetPins = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(false); + store.rpc.reset(list, function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash); + }); + }; + Store.uploadComplete = function (data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.uploadComplete(function (err, res) { @@ -196,27 +242,6 @@ define([ }); }; - var arePinsSynced = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - var list = getCanonicalChannelList(); - var local = Hash.hashChannelList(list); - store.rpc.getServerHash(function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash === local); - }); - }; - - var resetPins = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - var list = getCanonicalChannelList(); - store.rpc.reset(list, function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash); - }); - }; - Store.uploadChunk = function (data, cb) { store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { cb({ @@ -309,6 +334,23 @@ define([ }); }; + Store.getDeletedPads = function (data, cb) { + if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } + var list = getCanonicalChannelList(true); + if (!Array.isArray(list)) { + return void cb({error: 'INVALID_FILE_LIST'}); + } + + store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { + if (e) { return void cb({error: e}); } + if (res && res.length && Array.isArray(res[0])) { + cb(res[0]); + } else { + cb({error: 'UNEXPECTED_RESPONSE'}); + } + }); + }; + Store.initAnonRpc = function (data, cb) { require([ '/common/rpc.js', @@ -321,8 +363,6 @@ define([ }); }; - - ////////////////////////////////////////////////////////////////// /////////////////////// Store //////////////////////////////////// ////////////////////////////////////////////////////////////////// @@ -579,6 +619,8 @@ define([ contains = true; pad.atime = +new Date(); pad.title = title; + pad.owners = owners; + pad.expire = expire; // If the href is different, it means we have a stronger one if (href !== pad.href) { isStronger = true; } @@ -871,6 +913,8 @@ define([ var userObject = store.userObject = UserObject.init(proxy.drive, { pinPads: Store.pinPads, unpinPads: Store.unpinPads, + removeOwnedChannel: Store.removeOwnedChannel, + edPublic: store.proxy.edPublic, loggedIn: store.loggedIn, log: function (msg) { postMessage("DRIVE_LOG", msg); diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js index f136e2a1e..5c07cd93f 100644 --- a/www/common/outer/chainpad-netflux-worker.js +++ b/www/common/outer/chainpad-netflux-worker.js @@ -57,27 +57,11 @@ define([], function () { // shim between chainpad and netflux var msgIn = function (peerId, msg) { return msg.replace(/^cp\|/, ''); - - /*try { - var decryptedMsg = Crypto.decrypt(msg, validateKey); - return decryptedMsg; - } catch (err) { - console.error(err); - return msg; - }*/ }; var msgOut = function (msg) { if (readOnly) { return; } return msg; - /*try { - var cmsg = Crypto.encrypt(msg); - if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; } - return cmsg; - } catch (err) { - console.log(msg); - throw err; - }*/ }; var onMsg = function(peer, msg, wc, network, direct) { @@ -93,7 +77,9 @@ define([], function () { if (parsed.channel === wc.id && !validateKey) { validateKey = parsed.validateKey; } - padData = parsed; + if (parsed.channel === wc.id) { + padData = parsed; + } // We have to return even if it is not the current channel: // we don't want to continue with other channels messages here return; diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 39895b68c..9fe45556e 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -31,6 +31,9 @@ define([ case 'CLEAR_OWNED_CHANNEL': { Store.clearOwnedChannel(data, cb); break; } + case 'REMOVE_OWNED_CHANNEL': { + Store.removeOwnedChannel(data, cb); break; + } case 'UPLOAD_CHUNK': { Store.uploadChunk(data, cb); break; } @@ -49,6 +52,9 @@ define([ case 'UNPIN_PADS': { Store.unpinPads(data, cb); break; } + case 'GET_DELETED_PADS': { + Store.getDeletedPads(data, cb); break; + } case 'GET_PINNED_USAGE': { Store.getPinnedUsage(data, cb); break; } diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 32e3e3b92..3293dcb36 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -17,8 +17,12 @@ define([ console.error("unpinPads was not provided"); }; var pinPads = config.pinPads; + var removeOwnedChannel = config.removeOwnedChannel || function () { + console.error("removeOwnedChannel was not provided"); + }; var loggedIn = config.loggedIn; var workgroup = config.workgroup; + var edPublic = config.edPublic; var ROOT = exp.ROOT; var FILES_DATA = exp.FILES_DATA; @@ -90,9 +94,14 @@ define([ exp.getFiles([FILES_DATA]).forEach(function (id) { if (filesList.indexOf(id) === -1) { var fd = exp.getFileData(id); - if (fd && fd.href) { - toClean.push(Hash.hrefToHexChannelId(fd.href)); + var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href); + // If trying to remove an owned pad, remove it from server also + if (fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { + removeOwnedChannel(channelId, function (obj) { + if (obj && obj.error) { console.error(obj.error); } + }); } + if (channelId) { toClean.push(channelId); } spliceFileData(id); } }); diff --git a/www/common/pinpad.js b/www/common/pinpad.js index 47eb97892..0bbaddd37 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -145,7 +145,22 @@ define([ if (response && response.length) { cb(void 0, response[0]); } else { - cb(); + cb('INVALID_RESPONSE'); + } + }); + }; + + exp.removeOwnedChannel = function (channel, cb) { + if (typeof(channel) !== 'string' || channel.length !== 32) { + // can't use this on files because files can't be owned... + return void cb('INVALID_ARGUMENTS'); + } + rpc.send('REMOVE_OWNED_CHANNEL', channel, function (e, response) { + if (e) { return void cb(e); } + if (response && response.length) { + cb(void 0, response[0]); // I haven't tested this... + } else { + cb('INVALID_RESPONSE'); } }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 55346db6a..0bbd40eea 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -501,6 +501,9 @@ define([ sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { Cryptpad.clearOwnedChannel(channel, cb); }); + sframeChan.on('Q_REMOVE_OWNED_CHANNEL', function (channel, cb) { + Cryptpad.removeOwnedChannel(channel, cb); + }); if (cfg.addRpc) { cfg.addRpc(sframeChan, Cryptpad, Utils); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index d2aaf90ea..029118b88 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -392,15 +392,12 @@ define([ UI.log(data.logText); }); - ctx.metadataMgr.onChange(function () { - try { - var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; - Feedback.init(feedback); - } catch (e) { Feedback.init(false); } - }); - ctx.metadataMgr.onReady(waitFor()); }).nThen(function () { + try { + var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; + Feedback.init(feedback); + } catch (e) { Feedback.init(false); } ctx.sframeChan.ready(); cb(funcs); }); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 96014c992..46aa93348 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -201,12 +201,17 @@ define({ // Inner drive needs to send command and receive updates from the async store 'Q_DRIVE_USEROBJECT': true, 'Q_DRIVE_GETOBJECT': true, + // Get the pads deleted from the server by other users to remove them from the drive + 'Q_DRIVE_GETDELETED': true, // Store's userObject need to send log messages to inner to display them in the UI 'EV_DRIVE_LOG': true, // Refresh the drive when the drive has changed ('change' or 'remove' events) 'EV_DRIVE_CHANGE': true, 'EV_DRIVE_REMOVE': true, + // Remove an owned pad from the server + 'Q_REMOVE_OWNED_CHANNEL': true, + // Notifications about connection and disconnection from the network 'EV_NETWORK_DISCONNECT': true, 'EV_NETWORK_RECONNECT': true, diff --git a/www/common/userObject.js b/www/common/userObject.js index ecead9f2b..223376963 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -380,6 +380,18 @@ define([ var trashpaths = _findFileInTrash([TRASH], file); return rootpaths.concat(templatepaths, trashpaths); }; + + // Get drive ids of files from their channel ids + exp.findChannels = function (channels) { + var allFilesList = files[FILES_DATA]; + var channels64 = channels.slice().map(Util.hexToBase64); + return getFiles([FILES_DATA]).filter(function (k) { + var data = allFilesList[k]; + var parsed = Hash.parsePadUrl(data.href); + return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1; + }); + }; + exp.search = function (value) { if (typeof(value) !== "string") { return []; } value = value.trim(); diff --git a/www/drive/inner.js b/www/drive/inner.js index b2a1a774b..11d96df42 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -802,6 +802,7 @@ define([ hide.push('properties'); hide.push('rename'); hide.push('openparent'); + hide.push('hashtag'); } if (containsFolder && paths.length > 1) { // Cannot open multiple folders @@ -911,6 +912,7 @@ define([ //var actions = []; var toShow = filterContextMenu(menuType, paths); var $actions = $contextMenu.find('a'); + $contextMenu.data('paths', paths); $actions = $actions.filter(function (i, el) { return toShow.some(function (className) { return $(el).is(className); }); }); @@ -922,9 +924,6 @@ define([ } else { $a.text($(el).text()); } - $(el).data('paths', paths); - //$(el).data('path', path); - //:$(el).data('element', $element); $container.append($a); $a.click(function() { $(el).click(); }); }); @@ -1435,6 +1434,7 @@ define([ case FILES_DATA: pName = FILES_DATA_NAME; break; case SEARCH: pName = SEARCH_NAME; break; case RECENT: pName = RECENT_NAME; break; + case OWNED: pName = OWNED_NAME; break; default: pName = name; } return pName; @@ -1509,6 +1509,11 @@ define([ if (!APP.loggedIn) { msg = Messages.fm_info_anonymous; $box.html(msg); + $box.find('a[target!="_blank"]').click(function (e) { + e.preventDefault(); + var href = $(this).attr('href'); + common.gotoURL(href); + }); return $box; } if (!msg || APP.store['hide-info-' + path[0]] === '1') { @@ -2682,11 +2687,13 @@ define([ $contextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove) .attr('data-icon', 'fa-eraser'); } - var deletePaths = function (paths) { - var pathsList = []; - paths.forEach(function (p) { pathsList.push(p.path); }); - var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); - if (paths.length === 1) { + var deletePaths = function (paths, pathsList) { + pathsList = pathsList || []; + if (paths) { + paths.forEach(function (p) { pathsList.push(p.path); }); + } + var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]); + if (pathsList.length === 1) { msg = Messages.fm_removePermanentlyDialog; } UI.confirm(msg, function(res) { @@ -2695,6 +2702,33 @@ define([ filesOp.delete(pathsList, refresh); }); }; + var deleteOwnedPaths = function (paths, pathsList) { + pathsList = pathsList || []; + if (paths) { + paths.forEach(function (p) { pathsList.push(p.path); }); + } + var msgD = pathsList.length === 1 ? Messages.fm_deleteOwnedPad : + Messages.fm_deleteOwnedPads; + UI.confirm(msgD, function(res) { + $(window).focus(); + if (!res) { return; } + // Try to delete each selected pad from server, and delete from drive if no error + var n = nThen(function () {}); + pathsList.forEach(function (p) { + var el = filesOp.find(p); + var data = filesOp.getFileData(el); + var parsed = Hash.parsePadUrl(data.href); + var channel = Util.base64ToHex(parsed.hashData.channel); + n = n.nThen(function (waitFor) { + sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel, + waitFor(function (e) { + if (e) { return void console.error(e); } + filesOp.delete([p], refresh); + })); + }); + }); + }); + }; $contextMenu.on("click", "a", function(e) { e.stopPropagation(); var paths = $contextMenu.data('paths'); @@ -2720,27 +2754,7 @@ define([ moveElements(pathsList, [TRASH], false, refresh); } else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { - var msgD = Messages.fm_deleteOwnedPads; - UI.confirm(msgD, function(res) { - $(window).focus(); - if (!res) { return; } - // Try to delete each selected pad from server, and delete from drive if no error - var n = nThen(function () {}); - paths.forEach(function (p) { - var el = filesOp.find(p.path); - var data = filesOp.getFileData(el); - var parsed = Hash.parsePadUrl(data.href); - var channel = Util.base64ToHex(parsed.hashData.channel); - n = n.nThen(function (waitFor) { - sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, - waitFor(function (e) { - if (e) { return void console.error(e); } - filesOp.delete([p.path], refresh); - })); - }); - }); - }); - return; + deleteOwnedPaths(paths); } else if ($(this).hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { @@ -2878,18 +2892,11 @@ define([ if (!$(elmt).data('path')) { return; } paths.push($(elmt).data('path')); }); - // If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently, + if (!paths.length) { return; } + // If we are in the trash or anon pad or if we are holding the "shift" key, + // delete permanently if (!APP.loggedIn || isTrash || e.shiftKey) { - var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); - if (paths.length === 1) { - msg = Messages.fm_removePermanentlyDialog; - } - - UI.confirm(msg, function(res) { - $(window).focus(); - if (!res) { return; } - filesOp.delete(paths, refresh); - }); + deletePaths(null, paths); return; } // else move to trash @@ -2975,6 +2982,19 @@ define([ refresh(); UI.removeLoadingScreen(); + + sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { + var ids = filesOp.findChannels(data); + var titles = []; + ids.forEach(function (id) { + var title = filesOp.getTitle(id); + titles.push(title); + var paths = filesOp.findFile(id); + filesOp.delete(paths, refresh); + }); + if (!titles.length) { return; } + UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')])); + }); }; var setHistory = function (bool, update) { @@ -3091,7 +3111,6 @@ define([ throw new Error("Corrupted drive"); } andThen(common, proxy); - UI.removeLoadingScreen(); var onDisconnect = APP.onDisconnect = function (noAlert) { setEditable(false); diff --git a/www/drive/main.js b/www/drive/main.js index 552a6a0c6..61afafb2f 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -56,6 +56,12 @@ define([ cb(obj); }); }); + sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) { + Cryptpad.getDeletedPads(function (err, obj) { + if (err) { return void console.error(err); } + cb(obj); + }); + }); Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); }); diff --git a/www/file/inner.js b/www/file/inner.js index 2dbe765ce..b4b327998 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -224,6 +224,8 @@ define([ if (decrypting) { return; } decrypting = true; displayFile(ev, sizeMb, function (err) { + $appContainer.css('background-color', + common.getAppConfig().appBackgroundColor); if (err) { UI.alert(err); } }); }; diff --git a/www/pad/inner.js b/www/pad/inner.js index b54dd7dd3..6945f6d45 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -31,6 +31,7 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/bower_components/chainpad/chainpad.dist.js', + '/customize/application_config.js', '/bower_components/diff-dom/diffDOM.js', @@ -50,7 +51,8 @@ define([ ApiConfig, Hash, Util, - ChainPad) + ChainPad, + AppConfig) { var DiffDom = window.diffDOM; @@ -592,7 +594,8 @@ define([ } // Used in ckeditor-config.js Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs; - var newCss = '.cke_body_width { background: #666; height: 100%; }' + + var backColor = AppConfig.appBackgroundColor; + var newCss = '.cke_body_width { background: '+ backColor +'; height: 100%; }' + '.cke_body_width body {' + 'max-width: 50em; padding: 10px 30px; margin: 0 auto; min-height: 100%;'+ 'box-sizing: border-box;'+ diff --git a/www/settings/inner.js b/www/settings/inner.js index 8df9c9564..5105ad2b7 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -75,7 +75,7 @@ define([ } }; - if (!AppConfig.dislayCreationScreen) { + if (!AppConfig.displayCreationScreen) { delete categories.creation; } if (AppConfig.disableFeedback) { @@ -789,7 +789,7 @@ define([ var $categories = $('
', {'class': 'cp-sidebarlayout-categories'}) .appendTo(APP.$leftside); APP.$usage = $('
', {'class': 'usage'}).appendTo(APP.$leftside); - var active = 'account'; + var active = privateData.category || 'account'; Object.keys(categories).forEach(function (key) { var $category = $('
', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); if (key === 'account') { $category.append($('', {'class': 'fa fa-user-o'})); } diff --git a/www/settings/main.js b/www/settings/main.js index 42f501075..6b4dd8b8b 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -66,9 +66,18 @@ define([ Cryptpad.mergeAnonDrive(cb); }); }; + var category; + if (window.location.hash) { + category = window.location.hash.slice(1); + window.location.hash = ''; + } + var addData = function (obj) { + if (category) { obj.category = category; } + }; SFCommonO.start({ noRealtime: true, - addRpc: addRpc + addRpc: addRpc, + addData: addData }); }); }); diff --git a/www/todo/inner.js b/www/todo/inner.js index 8d5de7778..6e6bcf297 100644 --- a/www/todo/inner.js +++ b/www/todo/inner.js @@ -70,6 +70,10 @@ define([ var makeCheckbox = function (id, cb) { var entry = APP.lm.proxy.data[id]; + if (!entry || typeof(entry) !== 'object') { + return void console.log('entry undefined'); + } + var checked = entry.state === 1 ? 'cp-app-todo-task-checkbox-checked fa-check-square-o': 'cp-app-todo-task-checkbox-unchecked fa-square-o'; @@ -108,6 +112,9 @@ define([ .appendTo($taskDiv); var entry = APP.lm.proxy.data[el]; + if (!entry || typeof(entry) !== 'object') { + return void console.log('entry undefined'); + } if (entry.state) { $taskDiv.addClass('cp-app-todo-task-complete'); diff --git a/www/worker/inner.js b/www/worker/inner.js index 7b2186d2c..3f7df3f16 100644 --- a/www/worker/inner.js +++ b/www/worker/inner.js @@ -32,6 +32,7 @@ define([ sFrameChan.onReady(waitFor()); }).nThen(function (/*waitFor*/) { var $container = $('#cp-app-worker-container'); + $('', {href:'http://localhost:3000/worker/', target:'_blank'}).text('other').appendTo($container); var $bar = $('.cp-toolbar-container'); var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle']; diff --git a/www/worker/worker.js b/www/worker/worker.js index 095ef8980..f1e0c8a0f 100644 --- a/www/worker/worker.js +++ b/www/worker/worker.js @@ -29,16 +29,17 @@ require.config({ }); var i = 0; +var id = Math.floor(Math.random()*100000); onconnect = function(e) { console.log(e); console.log(i); var port = e.ports[0]; console.log('here'); - require([ - '/common/outer/async-store.js' - ], function (Store) { - console.log(Store); + //require([ + // '/common/outer/async-store.js' + //], function (Store) { + //console.log(Store); console.log(self.Proxy); var n = i; port.postMessage({state: 'READY'}); @@ -46,8 +47,9 @@ onconnect = function(e) { console.log('worker received'); console.log(e.data); port.postMessage('hello CryptPad'+n); + port.postMessage('This is '+id); }; - var data = { + /*var data = { query: function (cmd, data, cb) { console.log(cmd, data); }, @@ -64,7 +66,7 @@ onconnect = function(e) { }); port.postMessage('Store is connected!'); port.postMessage('Your username is ' + ret.store.proxy['cryptpad.username']); - }); + });*/ i++; - }); + //}); };