diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 63cc96f18..1ac8a8bab 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -12,6 +12,8 @@ define(function() { */ config.notificationTimeout = 5000; + config.enablePinning = true; + config.whiteboardPalette = [ '#000000', // black '#FFFFFF', // white diff --git a/package.json b/package.json index db3969c10..809f34ee0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "scripts": { "lint": "jshint --config .jshintrc --exclude-path .jshintignore .", "test": "node TestSelenium.js", - "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css", + "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css", "template": "cd customize.dist/src && node build.js" } } diff --git a/rpc.js b/rpc.js index 7ad81acb3..e1901ea05 100644 --- a/rpc.js +++ b/rpc.js @@ -144,16 +144,11 @@ var getChannelList = function (store, publicKey, cb) { pins[pin] = false; }); - if (!parsed[1] || parsed[1].length) { - break; - } - else { + if (parsed[1] && parsed[1].length) { parsed[1].forEach(function (channel) { pins[channel] = true; }); - break; } - break; default: console.error('invalid message read from store'); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 2e3e1c12c..c904b7678 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -6,9 +6,11 @@ define([ '/bower_components/alertifyjs/dist/js/alertify.js', '/common/clipboard.js', '/customize/application_config.js', + '/common/pinpad.js', /* TODO +load pinpad dynamically only after you know that it will be needed */ '/bower_components/jquery/dist/jquery.min.js', -], function (Config, Messages, Store, Crypto, Alertify, Clipboard, AppConfig) { +], function (Config, Messages, Store, Crypto, Alertify, 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 about pads to your local storage for future use and improved usability. @@ -25,6 +27,8 @@ define([ var store; + var PINNING_ENABLED = AppConfig.enablePinning; + var rpc; var find = common.find = function (map, path) { return (map && path.reduce(function (p, n) { @@ -36,6 +40,11 @@ define([ if (store) { return store; } throw new Error("Store is not ready!"); }; + var getProxy = common.getProxy = function () { + if (store && store.getProxy()) { + return store.getProxy().proxy; + } + }; var getNetwork = common.getNetwork = function () { if (store) { if (store.getProxy() && store.getProxy().info) { @@ -168,7 +177,6 @@ define([ return typeof getUserHash() === "string"; }; - // var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; var isArray = common.isArray = $.isArray; var fixHTML = common.fixHTML = function (str) { @@ -206,7 +214,7 @@ define([ return hexArray.join(""); }; - var deduplicate = common.deduplicateString = function (array) { + var deduplicateString = common.deduplicateString = function (array) { var a = array.slice(); for(var i=0; i= 56) { @@ -289,14 +296,6 @@ define([ throw new Error("Unable to parse the key"); } var version = hashArray[1]; - /*if (version === "1") { - secret.channel = base64ToHex(hashArray[2]); - secret.key = hashArray[3].replace(/-/g, '/'); - if (secret.channel.length !== 32 || secret.key.length !== 24) { - common.alert("The channel key and/or the encryption key is invalid"); - throw new Error("The channel key and/or the encryption key is invalid"); - } - }*/ if (version === "1") { var mode = hashArray[2]; if (mode === 'edit') { @@ -492,12 +491,6 @@ define([ var untitledIndex = 1; var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' '); return name; - /* - * Pad titles are shared in the document so it does not make sense anymore to avoid duplicates - if (isNameAvailable(name, parsed, recentPads)) { return name; } - while (!isNameAvailable(name + ' - ' + untitledIndex, parsed, recentPads)) { untitledIndex++; } - return name + ' - ' + untitledIndex; - */ }; var isDefaultName = common.isDefaultName = function (parsed, title) { var name = getDefaultName(parsed, []); @@ -579,6 +572,7 @@ define([ // STORAGE /* commit a list of pads to localStorage */ + // TODO integrate pinning if enabled var setRecentPads = common.setRecentPads = function (pads, cb) { getStore().setDrive(storageKey, pads, function (err, data) { cb(err, data); @@ -605,6 +599,7 @@ define([ // STORAGE + // TODO integrate pinning if enabled var forgetPad = common.forgetPad = function (href, cb) { var parsed = parsePadUrl(href); @@ -686,6 +681,8 @@ define([ var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) { return findStronger(href, recents); }; + + // TODO integrate pinning var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); @@ -821,12 +818,14 @@ define([ // local name? common.ready = function (f) { - var state = 0; - + var block = 0; var env = {}; var cb = function () { - f(void 0, env); + block--; + if (!block) { + f(void 0, env); + } }; if (sessionStorage[newPadNameKey]) { @@ -841,6 +840,9 @@ define([ Store.ready(function (err, storeObj) { store = common.store = env.store = storeObj; + var proxy = getProxy(); + var network = getNetwork(); + $(function() { // Race condition : if document.body is undefined when alertify.js is loaded, Alertify // won't work. We have to reset it now to make sure it uses a correct "body" @@ -864,8 +866,34 @@ define([ } }; + if (PINNING_ENABLED && isLoggedIn()) { + console.log("logged in. pads will be pinned"); + block++; + + // TODO setTimeout in case rpc doesn't + // activate in reasonable time? + Pinpad.create(network, proxy, function (e, call) { + if (e) { + console.error(e); + return cb(); + } + + console.log('RPC handshake complete'); + rpc = env.rpc = call; + + // TODO check if pin list is up to date + // if not, reset + cb(); + }); + } else if (PINNING_ENABLED) { + console.log('not logged in. pads will not be pinned'); + } else { + console.log('pinning disabled'); + } + // Everything's ready, continue... if($('#pad-iframe').length) { + block++; var $iframe = $('#pad-iframe'); var iframe = $iframe[0]; var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; @@ -876,6 +904,8 @@ define([ $iframe.load(cb); return; } + + block++; cb(); }); }, common); @@ -966,6 +996,7 @@ define([ /* * Buttons */ + // TODO integrate pinning if enabled var renamePad = common.renamePad = function (title, callback) { if (title === null) { return; } @@ -982,30 +1013,6 @@ define([ } callback(null, title); }); - /* Pad titles are shared in the document. We don't check for duplicates anymore. - common.causesNamingConflict(title, function (err, conflicts) { - if (err) { - console.log("Unable to determine if name caused a conflict"); - console.error(err); - callback(err, title); - return; - } - - if (conflicts) { - common.alert(Messages.renameConflict); - return; - } - - common.setPadTitle(title, function (err, data) { - if (err) { - console.log("unable to set pad title"); - console.log(err); - return; - } - callback(null, title); - }); - }); - */ }; var getUserChannelList = common.getUserChannelList = function () { @@ -1047,6 +1054,10 @@ define([ return list; }; + var getCanonicalChannelList = common.getCanonicalChannelList = function () { + return deduplicateString(getUserChannelList()).sort(); + }; + var createButton = common.createButton = function (type, rightside, data, callback) { var button; var size = "17px"; diff --git a/www/common/pinpad.js b/www/common/pinpad.js index e40270311..420b65452 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -1,69 +1,12 @@ define([ - '/common/cryptpad-common.js', '/common/rpc.js', - '/bower_components/tweetnacl/nacl-fast.min.js' -], function (Cryptpad, Rpc) { +], function (Rpc) { var Nacl = window.nacl; - var uniqueChannelList = function (list) { - list = list || Cryptpad.getUserChannelList(); - return Cryptpad.deduplicateString(list).sort(); - }; - - var localChannelsHash = function (fileList) { - var uniqueList = uniqueChannelList(fileList); - var hash = Nacl.util.encodeBase64(Nacl - .hash(Nacl.util.decodeUTF8( JSON.stringify(uniqueList) ))); - return hash; - }; - - var getServerHash = function (rpc, edPublic, cb) { - rpc.send('GET_HASH', edPublic, function (e, hash) { - cb(e, hash[0]); - }); - }; - - var getFileSize = function (rpc, file, cb) { - rpc.send('GET_FILE_SIZE', file, cb); - }; - - var getFileListSize = function (rpc, cb) { - return rpc.send('GET_TOTAL_SIZE', undefined, cb); - }; - - var pinChannel = function (rpc, channel, cb) { - rpc.send('PIN', channel, cb); - }; - - var unpinChannel = function (rpc, channel, cb) { - rpc.send('UNPIN', channel, cb); - }; - - var reset = function (rpc, cb) { - var list = uniqueChannelList(); - rpc.send('RESET', list, cb); - }; - - /* -1. every time you want to pin or unpid a pad you send a message to the server -2. the server sends back a hash of the sorted list of your pinned pads -3. you hash your sorted list of pinned pads that you should have according to your drive -4. compare them, if same - AWESOME - if they are not - UNPIN all, send all - */ - - // Don't use create until Cryptpad is ready - // (use Cryptpad.ready) - var create = function (cb) { - // you will need to communicate with the server - // use an already established - var network = Cryptpad.getNetwork(); - - // your user proxy contains credentials you will need to make RPC calls - var proxy = Cryptpad.getStore().getProxy().proxy; + var create = function (network, proxy, cb) { + if (!network) { return void cb('INVALID_NETWORK'); } + if (!proxy) { return void cb('INVALID_PROXY'); } var edPrivate = proxy.edPrivate; var edPublic = proxy.edPublic; @@ -74,32 +17,52 @@ define([ if (e) { return void cb(e); } var exp = {}; + + // expose the supplied publicKey as an identifier exp.publicKey = edPublic; + + // expose the RPC module's raw 'send' command exp.send = rpc.send; - exp.uniqueChannelList = uniqueChannelList; + // you can ask the server to pin a particular channel for you + exp.pin = function (channel, cb) { + rpc.send('PIN', channel, cb); + }; - exp.getFileSize = function (file, cb) { - getFileSize(rpc, file, cb); + // you can also ask to unpin a particular channel + exp.unpin = function (channel, cb) { + rpc.send('UNPIN', channel, cb); }; - exp.getFileListSize = function (cb) { - getFileListSize(rpc, cb); + + // This implementation must match that on the server + // it's used for a checksum + exp.hashChannelList = function (list) { + return Nacl.util.encodeBase64(Nacl.hash(Nacl.util + .decodeUTF8(JSON.stringify(list)))); }; + + // ask the server what it thinks your hash is exp.getServerHash = function (cb) { - getServerHash(rpc, edPublic, cb); + rpc.send('GET_HASH', edPublic, function (e, hash) { + cb(e, hash[0]); + }); }; - exp.pin = function (channel, cb) { - pinChannel(rpc, channel, cb); - }; - exp.unpin = function (channel, cb) { - unpinChannel(rpc, channel, cb); + // if local and remote hashes don't match, send a reset + exp.reset = function (list, cb) { + rpc.send('RESET', list, cb); }; - exp.reset = function (cb) { - reset(rpc, cb); + + // get the total stored size of a channel's patches (in bytes) + exp.getFileSize = function (file, cb) { + rpc.send('GET_FILE_SIZE', file, cb); }; - exp.localChannelsHash = localChannelsHash; + // get the combined size of all channels (in bytes) for all the + // channels which the server has pinned for your publicKey + exp.getFileListSize = function (cb) { + rpc.send('GET_TOTAL_SIZE', undefined, cb); + }; cb(e, exp); }); diff --git a/www/common/rpc.js b/www/common/rpc.js index 77566d2cb..b69a5ce5a 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -53,7 +53,8 @@ types of messages: var pending = ctx.pending[txid]; if (!(parsed && parsed.slice)) { - return void console.error('MALFORMED_RPC_RESPONSE'); + // RPC responses are arrays. this message isn't meant for us. + return; } var response = parsed.slice(2); @@ -70,9 +71,8 @@ types of messages: } } pending(void 0, response); - } else { - console.log("No callback provided"); } + //else { console.log("No callback provided"); } }; var create = function (network, edPrivateKey, edPublicKey, cb) { diff --git a/www/examples/pin/main.js b/www/examples/pin/main.js index 903435d60..1853b9e4c 100644 --- a/www/examples/pin/main.js +++ b/www/examples/pin/main.js @@ -9,46 +9,16 @@ define([ Cryptpad: Cryptpad, }; - var then = function (call) { - call.getFileSize('26f014b2ab959418605ea37a6785f317', function (e, msg) { - if (e) { - if (e === 'ENOENT') { return; } - return void console.error(e); - } - console.error("EXPECTED ENOENT"); - console.log(msg); - }); - - call.getFileSize('pewpew', function (e, msg) { - if (e) { - if (e === 'INVALID_CHAN') { return; } - return void console.error(e); - } - console.log(msg); - }); - - var list = Cryptpad.getUserChannelList(); - if (list.length) { - call.getFileSize(list[0], function (e, msg) { - if (e) { - return void console.error(e); - } - console.log(msg); - }); - } - call.getServerHash(function (e, hash) { - if (e) { return void console.error(e); } - console.log("the server believes your user hash is [%s]", hash); - }); - }; - var synchronize = function (call) { - var localHash = call.localChannelsHash(); + // provide a sorted list of unique channels + var list = Cryptpad.getCanonicalChannelList(); + + var localHash = call.hashChannelList(list); var serverHash; call.getFileListSize(function (e, bytes) { if (e) { return void console.error(e); } - console.log("%s total bytes used", bytes); + console.log("total %sK bytes used", bytes / 1000); }); call.getServerHash(function (e, hash) { @@ -59,26 +29,22 @@ define([ return console.log("all your pads are pinned. There is nothing to do"); } - call.reset(function (e, response) { + call.reset(list, function (e, response) { if (e) { return console.error(e); } else { return console.log('reset pin list. new hash is [%s]', response); } }); - -/* - console.log(JSON.stringify({ - local: localHash, - remote: serverHash, - }, null, 2));*/ }); }; $(function () { Cryptpad.ready(function (err, env) { - Pinpad.create(function (e, call) { + var network = Cryptpad.getNetwork(); + var proxy = Cryptpad.getStore().getProxy().proxy; + + Pinpad.create(network, proxy, function (e, call) { if (e) { return void console.error(e); } - // then(call); synchronize(call); }); }); diff --git a/www/whiteboard/index.html b/www/whiteboard/index.html index cd6071558..746bfccfc 100644 --- a/www/whiteboard/index.html +++ b/www/whiteboard/index.html @@ -11,82 +11,7 @@ id="favicon" /> - +
diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index 21ff6a032..e9f9ff9df 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -106,10 +106,9 @@ define([ var $picker = $('', { type: 'color', value: '#FFFFFF', - }) - .css({ - visibility: 'hidden' - }) + }) + // TODO confirm that this is safe to remove + //.css({ visibility: 'hidden' }) .on('change', function () { var color = this.value; cb(color); @@ -242,6 +241,7 @@ define([ }; var addColorToPalette = function (color, i) { + if (readOnly) { return; } var $color = $('', { 'class': 'palette-color', }) @@ -252,7 +252,6 @@ define([ var c = rgb2hex($color.css('background-color')); setColor(c); }) - // FIXME double click doesn't seem to work in chromium currently .on('dblclick', function (e) { e.preventDefault(); pickColor(rgb2hex($color.css('background-color')), function (c) { @@ -263,19 +262,17 @@ define([ config.onLocal(); setColor(c); }); - // TODO commit chosen color to pad metadata: - // json.metadata.palette[i] }); $colors.append($color); }; - palette.forEach(addColorToPalette); var updatePalette = function (newPalette) { palette = newPalette; - $colors.html(' '); + $colors.html(''); palette.forEach(addColorToPalette); }; + updatePalette(palette); var suggestName = function (fallback) { if (document.title === defaultName) { diff --git a/www/whiteboard/whiteboard.css b/www/whiteboard/whiteboard.css new file mode 100644 index 000000000..5fe1b6841 --- /dev/null +++ b/www/whiteboard/whiteboard.css @@ -0,0 +1,82 @@ +.hidden { + display: none; +} +html, +body { + padding: 0px; + margin: 0px; + box-sizing: border-box; +} +body { + display: flex; + flex-flow: column; + height: 100%; + background: url('/customize/bg3.jpg') no-repeat center center; + background-size: cover; + background-position: center; +} +#canvas-area { + flex: 1; + display: flex; +} +.canvas-container { + border: 1px solid black; + margin: auto; + background: white; +} +#controls { + display: block; + position: relative; + border-top: 1px solid black; + background: white; + height: 100px; + line-height: 100px; + padding-bottom: 5px; +} +#controls #width { + position: relative; + vertical-align: middle; +} +#controls #clear, +#controls #toggleDraw { + display: inline; + vertical-align: middle; +} +#controls .selected { + margin-left: 20px; + display: inline-block; + height: 135px; + width: 135px; + z-index: 9001; + text-align: center; +} +#controls .selected img { + vertical-align: middle; +} +/* Colors */ +#colors { + position: relative; + vertical-align: middle; + z-index: 100; + background: white; + display: flex; + justify-content: space-between; +} +#colors span.palette-color { + height: 4vw; + width: 4vw; + display: inline-block; + margin: 5px; + border: 1px solid black; + vertical-align: top; +} +#color-picker { + display: block; +} +#pickers { + visibility: hidden; + position: absolute; + width: 0; + height: 0; + z-index: -5; +} diff --git a/www/whiteboard/whiteboard.less b/www/whiteboard/whiteboard.less new file mode 100644 index 000000000..75c979456 --- /dev/null +++ b/www/whiteboard/whiteboard.less @@ -0,0 +1,97 @@ +.middle () { + position: relative; + vertical-align: middle; +} +.hidden { + display: none; +} + +html, body{ + padding: 0px; + margin: 0px; + box-sizing: border-box; +} +body { + display: flex; + flex-flow: column; + height: 100%; + background: url('/customize/bg3.jpg') no-repeat center center; + background-size: cover; + background-position: center; +} + +// created in the html +#canvas-area { + flex: 1; + display: flex; +} +// created by fabricjs. styled so defaults don't break anything +.canvas-container { + border: 1px solid black; + margin: auto; + background: white; +} + +// contains user tools +#controls { + display: block; + position: relative; + border-top: 1px solid black; + background: white; + height: 100px; + line-height: 100px; + padding-bottom: 5px; + + #width { + .middle; + } + #clear, #toggleDraw { + display: inline; + vertical-align: middle; + } + .selected { + margin-left: 20px; + display: inline-block; + height: 135px; + width: 135px; + z-index: 9001; + text-align: center; + img { + vertical-align: middle; + } + } +} + +/* Colors */ +#colors { + .middle; + z-index: 100; + background: white; + display: flex; + justify-content: space-between; + + span.palette-color { + height: 4vw; + width: 4vw; + display: inline-block; + margin: 5px; + border: 1px solid black; + vertical-align: top; + } +} + +// used in the toolbar if supported +#color-picker { + display: block; +} + +// input[type=color] must exist in the dom to work correctly +// styled so that they don't break layouts +#pickers { + visibility: hidden; + position: absolute; + width: 0; + height: 0; + z-index: -5; +} +