Merge branch 'staging' into oo2

This commit is contained in:
yflory
2018-09-03 10:39:34 +02:00
197 changed files with 20203 additions and 8239 deletions

View File

@@ -9,9 +9,14 @@ define([
'/common/common-thumbnail.js',
'/common/wire.js',
'/common/flat-dom.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
'/common/media-tag.js',
'/common/outer/login-block.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag, Block) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;
var Nacl = window.nacl;
var assertions = 0;
var failed = false;
@@ -132,6 +137,40 @@ define([
strungJSON(orig);
});
HTML_list.forEach(function (sel) {
var el = $(sel)[0];
var pred = function (el) {
if (el.nodeName === 'DIV') {
return true;
}
};
var filter = function (x) {
console.log(x);
if (x[1]['class']) {
x[1]['class'] = x[1]['class'].replace(/cke/g, '');
}
return x;
};
assert(function (cb) {
// FlatDOM output
var map = Flat.fromDOM(el, pred, filter);
// Hyperjson output
var hj = Hyperjson.fromDOM(el, pred, filter);
var x = Flat.toDOM(map);
var y = Hyperjson.toDOM(hj);
console.error(x.outerHTML);
console.error(y.outerHTML);
cb(x.outerHTML === y.outerHTML);
}, "Test equality of FlatDOM and HyperJSON");
});
// check that old hashes parse correctly
assert(function (cb) {
//if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug
@@ -223,6 +262,33 @@ define([
hd.type === 'invite');
}, "test support for invite urls");
// test support for V2
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
!parsed.hashData.present);
}, "test support for version 2 hash failed to parse");
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
parsed.hashData.embed &&
parsed.hashData.password);
}, "test support for password in version 2 hash failed to parse");
assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);
@@ -234,6 +300,35 @@ define([
!secret.hashData.present);
}, "test support for ugly tracking query paramaters in url");
assert(function (cb) {
var keys = Block.genkeys(Nacl.randomBytes(64));
var hash = Block.getBlockHash(keys);
var parsed = Block.parseBlockHash(hash);
cb(parsed &&
parsed.keys.symmetric.length === keys.symmetric.length);
}, 'parse a block hash');
assert(function (cb) {
try {
MediaTag(void 0).on('progress').on('decryption');
return void cb(true);
} catch (e) {
console.error(e);
return void cb(false);
}
}, 'check that MediaTag does the right thing when passed no value');
assert(function (cb) {
try {
MediaTag(document.createElement('div')).on('progress').on('decryption');
return void cb(true);
} catch (e) {
console.error(e);
return void cb(false);
}
}, 'check that MediaTag does the right thing when passed no value');
assert(function (cb) {
// TODO
return cb(true);

View File

@@ -1,16 +1,14 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/browser.less";
@import (reference) "../../customize/src/less2/include/markdown.less";
@import (reference) "../../customize/src/less2/include/framework.less";
.framework_main(
@bg-color: @colortheme_code-bg,
@warn-color: @colortheme_code-warn,
@color: @colortheme_code-color
);
// body
&.cp-app-code {
.framework_main(
@bg-color: @colortheme_code-bg,
@warn-color: @colortheme_code-warn,
@color: @colortheme_code-color
);
display: flex;
flex-flow: column;
max-height: 100%;

View File

@@ -37,6 +37,8 @@ define([
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
'less!/code/app-code.less'
], function (
$,
DiffMd,
@@ -108,7 +110,7 @@ define([
return;
}
$previewContainer.removeClass('cp-app-code-preview-isempty');
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
DiffMd.apply(DiffMd.render(editor.getValue()), $preview, framework._.sfCommon);
} catch (e) { console.error(e); }
};
var drawPreview = Util.throttle(function () {
@@ -331,13 +333,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

View File

@@ -4,9 +4,11 @@ const define = (x:any, y:any) => {};
const require = define;
*/
define([
'/api/config'
], function (Config) { /*::});module.exports = (function() {
'/api/config',
'/bower_components/nthen/index.js'
], function (Config, nThen) { /*::});module.exports = (function() {
const Config = (undefined:any);
const nThen = (undefined:any);
*/
var module = { exports: {} };
@@ -49,6 +51,7 @@ define([
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
}
ua[0] = ua[0].replace(/^\/\.\.\//, '/');
var out = ua.join('#');
//console.log(url + " --> " + out);
return out;
@@ -91,17 +94,36 @@ define([
};
var lessEngine;
var tempCache = { key: Math.random() };
var getLessEngine = function (cb) {
if (lessEngine) {
cb(lessEngine);
} else {
require(['/bower_components/less/dist/less.min.js'], function (Less) {
if (lessEngine) { return void cb(lessEngine); }
lessEngine = Less;
Less.functions.functionRegistry.add('LessLoader_currentFile', function () {
return new Less.tree.UnicodeDescriptor('"' +
fixURL(this.currentFileInfo.filename) + '"');
});
var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
//console.log("xhr: " + url);
return doXHR(url, type, callback, errback);
var cached = tempCache[url];
if (cached && cached.res) {
var res = cached.res;
return void setTimeout(function () { callback(res[0], res[1]); });
}
if (cached) { return void cached.queue.push(callback); }
cached = tempCache[url] = { queue: [ callback ], res: undefined };
return doXHR(url, type, function (text, lastModified) {
cached.res = [ text, lastModified ];
var queue = cached.queue;
cached.queue = [];
queue.forEach(function (f) {
setTimeout(function () { f(text, lastModified); });
});
}, errback);
};
cb(lessEngine);
});
@@ -117,19 +139,38 @@ define([
});
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/) {
cacheGet(url, function (css) {
if (css) {
inject(css, url);
return void cb();
var loadSubmodulesAndInject = function (css, url, cb, stack) {
inject(css, url);
nThen(function (w) {
css.replace(/\-\-LessLoader_require\:\s*"([^"]*)"\s*;/g, function (all, u) {
u = u.replace(/\?.*$/, '');
module.exports.load(u, w(), stack);
return '';
});
}).nThen(function () { cb(); });
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/, stack /*:?Array<string>*/) {
var btime = stack ? null : +new Date();
stack = stack || [];
if (stack.indexOf(url) > -1) { return void cb(); }
var timeout = setTimeout(function () { console.log('failed', url); }, 10000);
var done = function () {
clearTimeout(timeout);
if (btime) {
console.log("Compiling [" + url + "] took " + (+new Date() - btime) + "ms");
}
cb();
};
stack.push(url);
cacheGet(url, function (css) {
if (css) { return void loadSubmodulesAndInject(css, url, done, stack); }
console.log('CACHE MISS ' + url);
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
if (!css) { return void console.error(err); }
var output = fixAllURLs(css, url);
cachePut(url, output);
inject(output, url);
cb();
loadSubmodulesAndInject(output, url, done, stack);
});
});
};

View File

@@ -9,9 +9,9 @@ define(function() {
/* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide
*/
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard',
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
'oodoc', 'ooslide', 'oocell', 'file', 'todo', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'oocell'];
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
@@ -85,6 +85,8 @@ define(function() {
oodoc: 'fa-file-word-o',
ooslide: 'fa-file-powerpoint-o',
oocell: 'fa-file-excel-o',
kanban: 'fa-columns',
drive: 'fa-hdd-o',
};
// Ability to create owned pads and expiring pads through a new pad creation screen.
@@ -121,5 +123,18 @@ define(function() {
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs.
// Warning: This is an experimental feature. It will be enabled by default once we're sure it's stable.
config.disableWorkers = true;
// Shared folder are in a beta-test state. They are likely to disappear from a user's drive
// spontaneously, resulting in the deletion of the entire folder's content.
// We highly recommend to keep them disabled until they are stable enough to be enabled
// by default by the CryptPad developers.
config.disableSharedFolders = true;
return config;
});

View File

@@ -34,7 +34,7 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
};
window.onerror = function (e) {

View File

@@ -3,6 +3,7 @@ define(function () {
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
@@ -11,6 +12,7 @@ define(function () {
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen'
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated'
};
});

View File

@@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server
// it's used for a checksum
@@ -19,22 +20,53 @@ define([
.decodeUTF8(JSON.stringify(list))));
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) {
return secret.channel + secret.key;
}
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/';
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return;
if (version === 1) {
if (!data.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(secret.channel) +
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
}
if (version === 2) {
if (!data.editKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
}
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
if (!data.viewKeyStr) { return; }
return '/1/view/' + hexToBase64(secret.channel) +
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
}
if (version === 2) {
if (!data.viewKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
}
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
}
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
};
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@@ -43,6 +75,34 @@ define([
return s.replace(/\/+/g, '/');
};
Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function (type, password) {
var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@@ -52,29 +112,60 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var options;
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
parsed.getHash = function () { return hash; };
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
return parsed;
}
if (hashArr[1] && hashArr[1] === '1') {
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
var options = hashArr.slice(5);
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@@ -83,6 +174,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/');
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
if (['user'].indexOf(type) !== -1) {
@@ -125,17 +235,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
var hash = ret.hashData.getHash(options);
url += '#' + hash;
return url;
};
@@ -153,12 +255,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
var getRelativeHref = Hash.getRelativeHref = function (href) {
Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@@ -170,11 +273,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (type, secretHash) {
Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@@ -191,7 +296,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@@ -203,9 +307,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
}
else if (parsed.version === 1) {
secret.version = 0;
} else if (parsed.version === 1) {
// New hash
secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@@ -222,11 +327,43 @@ Version 1
}
}
} else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs
secret.channel = parsed.channel;
secret.keys = { fileKeyStr: parsed.key };
secret.channel = base64ToHex(parsed.channel);
secret.keys = {
fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
} else if (parsed.version === 2) {
// New hash
secret.version = 2;
secret.type = type;
secret.password = password;
if (parsed.type === "pad") {
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
}
@@ -234,51 +371,47 @@ Version 1
return secret;
};
Hash.getHashes = function (channel, secret) {
Hash.getHashes = function (secret) {
var hashes = {};
if (!secret.keys) {
secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
console.error('e');
return hashes;
} else if (!secret.keys) {
secret.keys = {};
}
if (secret.keys.editKeyStr) {
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
var createChannelId = Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function () {
// 16 byte channel Id
var channelId = Util.hexToBase64(createChannelId());
// 18 byte encryption key
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
return '/1/edit/' + [channelId, key].join('/') + '/';
};
// STORAGE
Hash.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findWeaker = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a weaker hash if we're already in view mode
if (parsed.hashData && parsed.hashData.mode === 'view') { return; }
var weaker;
Object.keys(recents).some(function (id) {
var pad = recents[id];
var p = parsePadUrl(pad.href);
if (pad.href || !pad.roHref) {
// This pad has an edit link, so it can't be weaker
return;
}
var p = parsePadUrl(pad.roHref);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -287,27 +420,31 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href;
weaker = pad;
return true;
}
return;
});
return weaker;
};
var findStronger = Hash.findStronger = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findStronger = function (href, channel, recents) {
var parsed = parsePadUrl(href);
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];
if (!pad.href) {
// This pad doesn't have an edit link, so it can't be stronger
return;
}
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -316,37 +453,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href;
stronger = pad;
return true;
}
return;
});
return stronger;
};
Hash.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents);
};
Hash.hrefToHexChannelId = function (href) {
Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
parsed = parsed.hashData;
if (parsed.version === 0) {
return parsed.channel;
} else if (parsed.version !== 1 && parsed.version !== 2) {
console.error("parsed href had no version");
console.error(parsed);
return;
}
var channel = parsed.channel;
if (!channel) { return; }
var hex = base64ToHex(channel);
return hex;
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {

View File

@@ -1,3 +1,12 @@
if (!document.querySelector("#alertifyCSS")) {
// Prevent alertify from injecting CSS, we create our own in alertify.less.
// see: https://github.com/alertifyjs/alertify.js/blob/v1.0.11/src/js/alertify.js#L414
var head = document.getElementsByTagName("head")[0];
var css = document.createElement("span");
css.id = "alertifyCSS";
css.setAttribute('data-but-why', 'see: common-interface.js');
head.insertBefore(css, head.firstChild);
}
define([
'jquery',
'/customize/messages.js',
@@ -6,15 +15,18 @@ define([
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/tippy.min.js',
'/common/tippy/tippy.min.js',
'/customize/pages.js',
'/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js',
'/common/jquery-ui/jquery-ui.min.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
'css!/common/tippy/tippy.css',
'css!/common/jquery-ui/jquery-ui.min.css'
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Test) {
Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
@@ -141,13 +153,15 @@ define([
};
dialog.frame = function (content) {
return h('div.alertify', {
return $(h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
])
]);
])).click(function (e) {
e.stopPropagation();
})[0];
};
/**
@@ -182,11 +196,17 @@ define([
]);
};
UI.tokenField = function (target) {
UI.tokenField = function (target, autocomplete) {
var t = {
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
var $t = t.tokenfield = $(t.element).tokenfield({
autocomplete: {
source: autocomplete,
delay: 100
},
showAutocompleteOnFocus: false
});
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
@@ -209,10 +229,17 @@ define([
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
// Close the suggest list when a token is added because we're going to wipe the input
var $input = $t.closest('.tokenfield').find('.token-input');
$input.autocomplete('close');
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
if (t === ev.attrs.value) {
ev.preventDefault();
return ((val = t));
}
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
@@ -240,7 +267,7 @@ define([
return t;
};
dialog.tagPrompt = function (tags, cb) {
dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
@@ -254,7 +281,7 @@ define([
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
var field = UI.tokenField(input, existing).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
@@ -395,7 +422,7 @@ define([
stopListening(listener);
cb();
});
listener = listenForKeys(close, close, ok);
listener = listenForKeys(close, close);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
@@ -409,7 +436,8 @@ define([
cb = cb || function () {};
opt = opt || {};
var input = dialog.textInput();
var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput();
var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock;
input.value = typeof(def) === 'string'? def: '';
var message;
@@ -425,7 +453,7 @@ define([
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([
message,
input,
inputBlock,
dialog.nav([ cancel, ok, ]),
]);
@@ -512,6 +540,50 @@ define([
Alertify.error(Util.fixHTML(msg));
};
UI.passwordInput = function (opts, displayEye) {
opts = opts || {};
var attributes = merge({
type: 'password'
}, opts);
var input = h('input.cp-password-input', attributes);
var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
var eye = h('span.fa.fa-eye.cp-password-reveal');
$(reveal).find('input').on('change', function () {
if($(this).is(':checked')) {
$(input).prop('type', 'text');
$(input).focus();
return;
}
$(input).prop('type', 'password');
$(input).focus();
});
$(eye).mousedown(function () {
$(input).prop('type', 'text');
$(input).focus();
}).mouseup(function(){
$(input).prop('type', 'password');
$(input).focus();
}).mouseout(function(){
$(input).prop('type', 'password');
$(input).focus();
});
if (displayEye) {
$(reveal).hide();
} else {
$(eye).hide();
}
return h('span.cp-password-container', [
input,
reveal,
eye
]);
};
/*
* spinner
*/
@@ -539,48 +611,100 @@ define([
var LOADING = 'cp-loading';
var getRandomTip = function () {
/*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};*/
var loading = {
error: false,
driveState: 0,
padState: 0
};
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
var hideLogo = config.hideLogo;
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
var todo = function () {
var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-drive'),
h('p.cp-loading-progress-pad')
]);
$(progress).hide();
$loading.find('.cp-loading-container').append(progress);
} else if (config.noProgress) {
$loading.find('.cp-loading-progress').remove();
}
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
$('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else {
$('#' + LOADING).find('p').text('');
$('#' + LOADING).find('#cp-loading-message').hide().text('');
}
$container = $loading.find('.cp-loading-container');
loading.error = false;
};
if ($('#' + LOADING).length) {
todo();
} else {
$loading = $(Pages.loadingScreen());
$container = $loading.find('.cp-loading-container');
if (hideLogo) {
$loading.find('img').hide();
} else {
$loading.find('img').show();
}
var $spinner = $loading.find('.cp-loading-spinner-container');
$spinner.show();
$('body').append($loading);
Loading();
todo();
}
if (Messages.tips && !hideTips) {
var $loadingTip = $('<div>', {'id': 'cp-loading-tip'});
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
});
$('body').append($loadingTip);
};
UI.updateLoadingProgress = function (data, isDrive) {
var $loading = $('#' + LOADING);
if (!$loading.length || loading.error) { return; }
$loading.find('.cp-loading-progress').show();
var $progress;
if (isDrive) {
// Drive state
if (loading.driveState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-drive');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, drive is loaded
if (data.state === -1) {
loading.driveState = -1;
$progress.remove();
} else {
if (data.state < loading.driveState) { return; } // We should not display old data
// Update the current state
loading.driveState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
} else {
// Pad state
if (loading.padState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-pad');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, pad is loaded
if (data.state === -1) {
loading.padState = -1;
$progress.remove();
} else {
if (data.state < loading.padState) { return; } // We should not display old data
// Update the current state
loading.padState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_pad_'+data.state] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
}
};
UI.removeLoadingScreen = function (cb) {
@@ -591,7 +715,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb);
loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@@ -605,18 +729,27 @@ define([
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
var $loading = $('#' + LOADING);
if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
loading.error = true;
$loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
if (transparent) { $loading.css('opacity', 0.9); }
var $error = $loading.find('#cp-loading-message').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
$loading.hide();
loading.error = false;
if (typeof(exitable) === "function") { exitable(); }
}
});
@@ -637,7 +770,7 @@ define([
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
var href = data.href || data.roHref;
var type = data.type;
if (!href && !type) { return $icon; }
@@ -660,18 +793,40 @@ define([
});
};
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
$.extend(true, Tippy.defaults, {
placement: 'bottom',
performance: true,
delay: [delay, 0],
//sticky: true,
theme: 'cryptpad',
arrow: true,
maxWidth: '200px',
flip: true,
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
}
},
//arrowType: 'round',
dynamicTitle: true,
arrowTransform: 'scale(2)',
zIndex: 100000001
});
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
delay: [delay, 0],
sticky: true
var opts = {
distance: 15
};
Array.prototype.slice.apply(el.attributes).filter(function (obj) {
return /^data-tippy-/.test(obj.name);
}).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
});
Tippy(el, opts);
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
@@ -680,13 +835,13 @@ define([
var out = false;
var xId = $(x).attr('aria-describedby');
if (xId) {
if (xId.indexOf('tippy-tooltip-') === 0) {
if (xId.indexOf('tippy-') === 0) {
return true;
}
}
$(x).find('[aria-describedby]').each(function (i, el) {
var id = el.getAttribute('aria-describedby');
if (id.indexOf('tippy-tooltip-') !== 0) { return; }
if (id.indexOf('tippy-') !== 0) { return; }
out = true;
});
return out;
@@ -720,5 +875,61 @@ define([
});
};
UI.createCheckbox = Pages.createCheckbox;
UI.createRadio = Pages.createRadio;
UI.cornerPopup = function (text, actions, footer, opts) {
opts = opts || {};
var minimize = h('div.cp-corner-minimize.fa.fa-window-minimize');
var maximize = h('div.cp-corner-maximize.fa.fa-window-maximize');
var popup = h('div.cp-corner-container', [
minimize,
maximize,
h('div.cp-corner-filler', { style: "width:130px;" }),
h('div.cp-corner-filler', { style: "width:90px;" }),
h('div.cp-corner-filler', { style: "width:60px;" }),
h('div.cp-corner-filler', { style: "width:40px;" }),
h('div.cp-corner-filler', { style: "width:20px;" }),
h('div.cp-corner-text', text),
h('div.cp-corner-actions', actions),
Pages.setHTML(h('div.cp-corner-footer'), footer)
]);
$(minimize).click(function () {
$(popup).addClass('cp-minimized');
});
$(maximize).click(function () {
$(popup).removeClass('cp-minimized');
});
if (opts.hidden) {
$(popup).addClass('cp-minimized');
}
if (opts.big) {
$(popup).addClass('cp-corner-big');
}
var hide = function () {
$(popup).hide();
};
var show = function () {
$(popup).show();
};
var deletePopup = function () {
$(popup).remove();
};
$('body').append(popup);
return {
popup: popup,
hide: hide,
show: show,
delete: deletePopup
};
};
return UI;
});

View File

@@ -89,7 +89,7 @@ define([
};
/* Used to accept friend requests within apps other than /contacts/ */
Msg.addDirectMessageHandler = function (cfg) {
Msg.addDirectMessageHandler = function (cfg, href) {
var network = cfg.network;
var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
@@ -97,13 +97,12 @@ define([
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = Hash.parsePadUrl(window.location.href);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
var chan = secret.channel;
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var decryptMsg;
try {
decryptMsg = Crypto.decrypt(message, key);
@@ -113,7 +112,7 @@ define([
if (!decryptMsg) { return; }
// Parse
msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; }
if (msg[1] !== chan) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
@@ -197,15 +196,14 @@ define([
var network = cfg.network;
var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var chan = secret.channel;
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {

View File

@@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, b64);
Thumb.setPadThumbnail(common, opts.href, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@@ -240,25 +240,25 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
var getKey = function (href) {
var parsed = Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
var getKey = function (type, channel) {
return 'thumbnail-' + type + '-' + channel;
};
Thumb.setPadThumbnail = function (common, href, b64, cb) {
Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {};
var k = getKey(href);
var parsed = Hash.parsePadUrl(href);
channel = channel || common.getMetadataMgr().getPrivateData().channel;
var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb);
};
Thumb.displayThumbnail = function (common, href, $container, cb) {
Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
var k = getKey(href);
var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var secret = Hash.getSecrets('file', parsed.hash, password);
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
var key = secret.keys && secret.keys.cryptKey;
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') { return; }
@@ -270,7 +270,7 @@ define([
if (!v) {
v = 'EMPTY';
}
Thumb.setPadThumbnail(common, href, v, function (err) {
Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,21 @@ define([], function () {
}).join('');
};
// given an array of Uint8Arrays, return a new Array with all their values
Util.uint8ArrayJoin = function (AA) {
var l = 0;
var i = 0;
for (; i < AA.length; i++) { l += AA[i].length; }
var C = new Uint8Array(l);
i = 0;
for (var offset = 0; i < AA.length; i++) {
C.set(AA[i], offset);
offset += AA[i].length;
}
return C;
};
Util.deduplicateString = function (array) {
var a = array.slice();
for(var i=0; i<a.length; i++) {
@@ -122,17 +137,14 @@ define([], function () {
else if (bytes >= oneMegabyte) { return 'MB'; }
};
// given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var CB = Util.once(cb);
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onerror = function (err) { CB(err); };
xhr.onload = function () {
if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR');
@@ -142,6 +154,22 @@ define([], function () {
xhr.send(null);
};
Util.dataURIToBlob = function (dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// write the ArrayBuffer to a blob, and you're done
var bb = new Blob([ab], {type: mimeString});
return bb;
};
Util.throttle = function (f, ms) {
var to;
var g = function () {

View File

@@ -5,6 +5,7 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }
@@ -20,9 +21,9 @@ define([
}
};
var makeConfig = function (hash) {
var makeConfig = function (hash, password) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Hash.getSecrets('pad', hash);
var secret = Hash.getSecrets('pad', hash, password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(),
@@ -47,8 +48,10 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
var config = makeConfig(hash);
config.onReady = function (info) {
var rt = Session.session = info.realtime;
@@ -64,9 +67,11 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback');
}
opt = opt || {};
var config = makeConfig(hash);
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
Session.network = info.network;

File diff suppressed because it is too large Load Diff

View File

@@ -41,10 +41,15 @@ define([
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
// DEPRECATED
// Mediatag using markdown syntax should not be used anymore so they don't support
// password-protected files
console.log('DEPRECATED: mediatag using markdown syntax!');
var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
var secret = Hash.getSecrets('file', parsed.hash);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) {
mt += mediaMap[src];
}
@@ -161,7 +166,8 @@ define([
return patch;
};
DiffMd.apply = function (newHtml, $content) {
DiffMd.apply = function (newHtml, $content, common) {
var contextMenu = common.importMediaTagMenu();
var id = $content.attr('id');
if (!id) { throw new Error("The element must have a valid id"); }
var pattern = /(<media-tag src="([^"]*)" data-crypto-key="([^"]*)">)<\/media-tag>/g;
@@ -187,6 +193,11 @@ define([
DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))');
$mts.each(function (i, el) {
$(el).contextmenu(function (e) {
e.preventDefault();
$(contextMenu.menu).data('mediatag', $(el));
contextMenu.show(e);
});
MediaTag(el);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {

View File

@@ -1,8 +1,9 @@
@import (once) '../customize/src/less2/include/colortheme-all.less';
@import '../customize/src/less2/include/modal.less';
@import (reference) '../customize/src/less2/include/colortheme-all.less';
@import (reference) '../customize/src/less2/include/modal.less';
.fileDialog_main () {
#fileDialog {
.modal_main();
display: none;
.cp-modal {
.fileContainer {

View File

@@ -17,8 +17,7 @@ define([], function () {
return data;
};
var identity = function (x) { return x; };
Flat.fromDOM = function (dom) {
Flat.fromDOM = function (dom, predicate, filter) {
var data = {
map: {},
};
@@ -34,14 +33,21 @@ define([], function () {
return id;
}
if (!el || !el.attributes) { return; }
if (predicate) {
if (!predicate(el)) { return; } // shortcircuit
}
id = uid();
data.map[id] = [
var temp = [
el.tagName,
getAttrs(el),
slice(el.childNodes).map(function (e) {
return process(e);
}).filter(identity)
}).filter(Boolean)
];
data.map[id] = filter? filter(temp): temp;
return id;
};

File diff suppressed because one or more lines are too long

6
www/common/jquery-ui/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,94 +1,3 @@
define([], function () {
var loadingStyle = (function(){/*
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 1;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
}
@media screen and (max-height: 450px) {
#cp-loading .cp-loading-cryptofist {
display: none;
}
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-container">',
'<img class="cp-loading-cryptofist" src="/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + '">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-circle-o-notch fa-spin fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',
'</div>'
].join('');
var intr;
var append = function () {
if (!document.body) { return; }
clearInterval(intr);
document.body.appendChild(elem);
};
intr = setInterval(append, 100);
append();
require(['/customize/loading.js'], function (Loading) {
Loading();
});

File diff suppressed because one or more lines are too long

View File

@@ -6,84 +6,6 @@ define([
], function (Crypt, FO, Hash, Realtime) {
var exp = {};
var getType = function (el) {
if (el === null) { return "null"; }
return Array.isArray(el) ? "array" : typeof(el);
};
var findAvailableKey = function (obj, key) {
if (typeof (obj[key]) === "undefined") { return key; }
var i = 1;
var nkey = key;
while (typeof (obj[nkey]) !== "undefined") {
nkey = key + '_' + i;
i++;
}
return nkey;
};
var createFromPath = function (proxy, oldFo, path, id) {
var root = proxy.drive;
if (!oldFo.isFile(id)) { return; }
var error = function (msg) {
console.error(msg || "Unable to find that path", path);
};
if (oldFo.isInTrashRoot(path)) {
id = oldFo.find(path.slice(0,3));
path.pop();
}
var next, nextRoot;
path.forEach(function (p, i) {
if (!root) { return; }
if (typeof(p) === "string") {
if (getType(root) !== "object") { root = undefined; error(); return; }
if (i === path.length - 1) {
root[Hash.createChannelId()] = id;
return;
}
next = getType(path[i+1]);
nextRoot = getType(root[p]);
if (nextRoot !== "undefined") {
if (next === "string" && nextRoot === "object" || next === "number" && nextRoot === "array") {
root = root[p];
return;
}
p = findAvailableKey(root, p);
}
if (next === "number") {
root[p] = [];
root = root[p];
return;
}
root[p] = {};
root = root[p];
return;
}
// Path contains a non-string element: it's an array index
if (typeof(p) !== "number") { root = undefined; error(); return; }
if (getType(root) !== "array") { root = undefined; error(); return; }
if (i === path.length - 1) {
if (root.indexOf(id) === -1) { root.push(id); }
return;
}
next = getType(path[i+1]);
if (next === "number") {
error('2 consecutives arrays in the user object');
root = undefined;
//root.push([]);
//root = root[root.length - 1];
return;
}
root.push({});
root = root[root.length - 1];
return;
});
};
exp.anonDriveIntoUser = function (proxyData, fsHash, cb) {
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
if (!fsHash || !proxyData.loggedIn) {
@@ -105,49 +27,38 @@ define([
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
loggedIn: proxyData.loggedIn,
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
loggedIn: true,
outer: true
});
var onMigrated = function () {
oldFo.fixFiles();
var newFo = proxyData.userObject;
var oldRecentPads = parsed.drive[newFo.FILES_DATA];
var newRecentPads = proxy.drive[newFo.FILES_DATA];
var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
var newHrefs = Object.keys(newRecentPads).map(function (id) {
return newRecentPads[id].href;
});
oldFo.fixFiles(true);
var manager = proxyData.manager;
var oldFiles = oldFo.getFiles([oldFo.FILES_DATA]);
oldFiles.forEach(function (id) {
var href = oldRecentPads[id].href;
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
if (newHrefs.indexOf(href) !== -1) { return; }
// If we have a stronger version, do not add the current href
if (Hash.findStronger(href, newRecentPads)) { return; }
// If we have a weaker version, replace the href by the new one
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
var weaker = Hash.findWeaker(href, newRecentPads);
if (weaker) {
// Update RECENTPADS
newRecentPads.some(function (pad) {
if (pad.href === weaker) {
pad.href = href;
return true;
}
return;
var data = oldFo.getFileData(id);
var channel = data.channel;
var datas = manager.findChannel(channel, true);
// Do not migrate a pad if we already have it, it would create a duplicate
// in the drive
if (datas.length !== 0) {
// We want to merge a read-only pad: it can't be stronger than what
// we already have so abort
if (!data.href) { return; }
// We want to merge an edit pad: check if we have the same channel
// but read-only and upgrade it in that case
datas.forEach(function (pad) {
if (!pad.href) { data.href = pad.href; }
});
// Update the file in the drive
newFo.replace(weaker, href);
return;
}
// Here it means we have a new href, so we should add it to the drive at its old location
var paths = oldFo.findFile(id);
if (paths.length === 0) { return; }
// Add the file data in our array and use the id to add the file
var data = oldFo.getFileData(id);
// Here it means we have a new href, so we should add it to the drive
if (data) {
newFo.pushData(data, function (err, id) {
if (err) { return void console.error("Cannot import file:", data, err); }
createFromPath(proxy, oldFo, paths[0], id);
manager.addPad(null, data, function (err) {
if (err) {
return void console.error("Cannot import file:", data, err);
}
});
}
});
@@ -159,7 +70,12 @@ define([
Realtime.whenRealtimeSyncs(proxyData.realtime, cb);
}
};
oldFo.migrate(onMigrated);
if (oldFo && typeof(oldFo.migrate) === 'function') {
oldFo.migrate(onMigrated);
} else {
console.log('oldFo.migrate is not a function');
onMigrated();
}
return;
}
if (typeof(cb) === "function") { cb(); }

View File

@@ -34,6 +34,10 @@ define(['json.sortify'], function (Sortify) {
}
if (!metadataObj.users) { metadataObj.users = {}; }
if (!metadataLazyObj.users) { metadataLazyObj.users = {}; }
if (!metadataObj.type) { metadataObj.type = meta.doc.type; }
if (!metadataLazyObj.type) { metadataLazyObj.type = meta.doc.type; }
var mdo = {};
// We don't want to add our user data to the object multiple times.
//var containsYou = false;
@@ -60,7 +64,9 @@ define(['json.sortify'], function (Sortify) {
if (metadataObj.title !== rememberedTitle) {
rememberedTitle = metadataObj.title;
titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
titleChangeHandlers.forEach(function (f) {
f(metadataObj.title, metadataObj.defaultTitle);
});
}
changeHandlers.forEach(function (f) { f(); });

View File

@@ -1,106 +1,193 @@
define(['/common/common-feedback.js'], function (Feedback) {
define([
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/bower_components/nthen/index.js',
], function (Feedback, Hash, Util, nThen) {
// Start migration check
// Versions:
// 1: migrate pad attributes
// 2: migrate indent settings (codemirror)
return function (userObject) {
return function (userObject, cb, progress) {
var version = userObject.version || 0;
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
nThen(function () {
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}).nThen(function () {
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}).nThen(function () {
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}).nThen(function () {
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
// Migration 5: dates to Number
var migrateDates = function () {
var data = userObject.drive && userObject.drive.filesData;
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
}).nThen(function () {
// Migration 5: dates to Number
var migrateDates = function () {
var data = userObject.drive && userObject.drive.filesData;
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
}
}
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
}).nThen(function (waitFor) {
var addChannelId = function () {
var data = userObject.drive.filesData;
var el, parsed;
var n = nThen(function () {});
var padsLength = Object.keys(data).length;
Object.keys(data).forEach(function (k, i) {
n = n.nThen(function (w) {
setTimeout(w(function () {
el = data[k];
parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; }
if (!el.channel) {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
progress(6, Math.round(100*i/padsLength));
console.log('Adding missing channel in filesData ', el.channel);
}
}));
});
});
n.nThen(waitFor(function () {
Feedback.send('Migrate-6', true);
userObject.version = version = 6;
}));
};
if (version < 6) {
addChannelId();
}
}).nThen(function (waitFor) {
var addRoHref = function () {
var data = userObject.drive.filesData;
var el, parsed;
var n = nThen(function () {});
var padsLength = Object.keys(data).length;
Object.keys(data).forEach(function (k, i) {
n = n.nThen(function (w) {
setTimeout(w(function () {
el = data[k];
if (!el.href || (el.roHref && false)) {
// Already migrated
return void progress(7, Math.round(100*i/padsLength));
}
parsed = Hash.parsePadUrl(el.href);
if (parsed.hashData.type !== "pad") {
// No read-only mode for files
return void progress(7, Math.round(100*i/padsLength));
}
if (parsed.hashData.mode === "view") {
// This is a read-only pad in our drive
el.roHref = el.href;
delete el.href;
console.log('Move href to roHref in filesData ', el.roHref);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
var hash = Hash.getViewHashFromKeys(secret);
if (hash) {
// Version 0 won't have a view hash available
el.roHref = '/' + parsed.type + '/#' + hash;
console.log('Adding missing roHref in filesData ', el.href);
}
}
progress(6, Math.round(100*i/padsLength));
}));
});
});
n.nThen(waitFor(function () {
Feedback.send('Migrate-7', true);
userObject.version = version = 7;
}));
};
if (version < 7) {
addRoHref();
}
/*}).nThen(function (waitFor) {
// Test progress bar in the loading screen
var i = 0;
var w = waitFor();
var it = setInterval(function () {
i += 5;
if (i >= 100) { w(); clearInterval(it); i = 100;}
progress(0, i);
}, 500);
progress(0, 0);*/
}).nThen(function () {
cb();
});
};
});

View File

@@ -0,0 +1,51 @@
@import (reference) "../../customize/src/less2/include/framework.less";
// body
body.cp-app-oocell, body.cp-app-oodoc, body.cp-app-ooslide {
display: flex;
flex-flow: column;
&.cp-app-oocell {
.framework_main(
@bg-color: @colortheme_oocell-bg,
@warn-color: @colortheme_oocell-warn,
@color: @colortheme_oocell-color
);
}
&.cp-app-oodoc {
.framework_main(
@bg-color: @colortheme_oodoc-bg,
@warn-color: @colortheme_oodoc-warn,
@color: @colortheme_oodoc-color
);
}
&.cp-app-ooslide {
.framework_main(
@bg-color: @colortheme_ooslide-bg,
@warn-color: @colortheme_ooslide-warn,
@color: @colortheme_ooslide-color
);
}
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-oo-container {
flex: 1;
height: 100%;
background-color: lightgrey;
display: flex;
}
#ooframe {
flex: 1;
border:none;
margin:0;
padding:0;
}
}

View File

@@ -18,7 +18,7 @@ define([
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'less!/common/onlyoffice/app-oo.less',
], function (
$,
Toolbar,

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,10 @@ define([], function () {
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
var removeCp = function (str) {
return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
};
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
@@ -68,7 +72,11 @@ define([], function () {
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, '');
// NOTE: Hash version 0 contains a 32 characters nonce followed by a pipe
// at the beginning of each message on the server.
// We have to make sure our regex ignores this nonce using {0,20} (our IDs
// should only be 8 characters long)
return removeCp(msg);
};
var msgOut = function (msg) {
@@ -120,6 +128,8 @@ define([], function () {
lastKnownHash = msg.slice(0,64);
var isCp = /^cp\|/.test(msg);
var message = msgIn(peer, msg);
verbose(message);
@@ -130,7 +140,7 @@ define([], function () {
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
onMessage(message);
onMessage(peer, message, validateKey, isCp);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
@@ -233,7 +243,6 @@ define([], function () {
};
network.on('disconnect', function (reason) {
console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
@@ -256,7 +265,8 @@ define([], function () {
};
return {
start: start
start: start,
removeCp: removeCp
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);

View File

@@ -58,6 +58,14 @@ define([
localStorage[Constants.userHashKey] = sHash;
};
LocalStore.getBlockHash = function () {
return localStorage[Constants.blockHashKey];
};
LocalStore.setBlockHash = function (hash) {
localStorage[Constants.blockHashKey] = hash;
};
LocalStore.getAccountName = function () {
return localStorage[Constants.userNameKey];
};
@@ -66,10 +74,6 @@ define([
return typeof getUserHash() === "string";
};
LocalStore.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }
@@ -92,10 +96,11 @@ define([
});
};
var logoutHandlers = [];
LocalStore.logout = function (cb) {
LocalStore.logout = function (cb, isDeletion) {
[
Constants.userNameKey,
Constants.userHashKey,
Constants.blockHashKey,
'loginToken',
'plan',
].forEach(function (k) {
@@ -108,13 +113,15 @@ define([
// Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes
if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash());
LocalStore.setFSHash(Hash.createRandomHash('drive'));
}
eraseTempSessionValues();
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
if (!isDeletion) {
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb);

View File

@@ -0,0 +1,147 @@
define([
'/common/common-util.js',
'/api/config',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util, ApiConfig) {
var Nacl = window.nacl;
var Block = {};
Block.join = Util.uint8ArrayJoin;
// publickey <base64 string>
// signature <base64 string>
// block <base64 string>
// [b64_public, b64_sig, b64_block [version, nonce, content]]
Block.seed = function () {
return Nacl.hash(Nacl.util.decodeUTF8('pewpewpew'));
};
// should be deterministic from a seed...
Block.genkeys = function (seed) {
if (!(seed instanceof Uint8Array)) {
throw new Error('INVALID_SEED_FORMAT');
}
if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
throw new Error('INVALID_SEED_LENGTH');
}
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, // 32 bytes ...
};
};
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Nacl.util.decodeUTF8(content);
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
return Block.join([
[0],
nonce,
Nacl.secretbox(u8, nonce, keys.symmetric)
]);
};
// (uint8Array block) => payload object
Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
try {
return JSON.parse(Nacl.util.encodeUTF8(plaintext));
} catch (e) {
console.error(e);
return;
}
};
// (Uint8Array block) => signature
Block.sign = function (ciphertext, keys) {
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
};
Block.serialize = function (content, keys) {
// encrypt the content
var ciphertext = Block.encrypt(0, content, keys);
// generate a detached signature
var sig = Block.sign(ciphertext, keys);
// serialize {publickey, sig, ciphertext}
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
ciphertext: Nacl.util.encodeBase64(ciphertext),
};
};
Block.remove = function (keys) {
// sign the hash of the text 'DELETE_BLOCK'
var sig = Nacl.sign.detached(Nacl.hash(
Nacl.util.decodeUTF8('DELETE_BLOCK')), keys.sign.secretKey);
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
};
};
var urlSafeB64 = function (u8) {
return Nacl.util.encodeBase64(u8).replace(/\//g, '-');
};
Block.getBlockUrl = function (keys) {
var publicKey = urlSafeB64(keys.sign.publicKey);
// 'block/' here is hardcoded because it's hardcoded on the server
// if we want to make CryptPad work in server subfolders, we'll need
// to update this path derivation
return (ApiConfig.fileHost || window.location.origin)
+ '/block/' + publicKey.slice(0, 2) + '/' + publicKey;
};
Block.getBlockHash = function (keys) {
var absolute = Block.getBlockUrl(keys);
var symmetric = urlSafeB64(keys.symmetric);
return absolute + '#' + symmetric;
};
var decodeSafeB64 = function (b64) {
try {
return Nacl.util.decodeBase64(b64.replace(/\-/g, '/'));
} catch (e) {
console.error(e);
return;
}
};
Block.parseBlockHash = function (hash) {
if (typeof(hash) !== 'string') { return; }
var parts = hash.split('#');
if (parts.length !== 2) { return; }
try {
return {
href: parts[0],
keys: {
symmetric: decodeSafeB64(parts[1]),
}
};
} catch (e) {
console.error(e);
return;
}
};
return Block;
});

View File

@@ -0,0 +1,103 @@
define([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js',
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var sendMsg = Util.mkEvent();
var create = function () {
var Rpc = SRpc();
var postMessage = function (data) {
sendMsg.fire(data);
};
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
};
return {
query: function (data) {
msgEv.fire({data: data});
},
onMessage: function (cb) {
sendMsg.reg(function (data) {
setTimeout(function () {
cb(data);
});
});
},
create: create
};
});

View File

@@ -0,0 +1,178 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SW INIT');
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SW Channel created');
var clientId = client.id;
self.tabs[clientId].chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SW Connect callback');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
self.tabs[clientId].channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: self.tabs[clientId].channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
self.tabs[client.id].msgEv = msgEv;
self.tabs[client.id].close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
self.addEventListener('message', function (e) {
var cId = e.source.id;
if (e.data === "INIT") {
if (tabs[cId]) { return; }
tabs[cId] = {
client: e.source
};
init(e.source, function () {
postMsg(e.source, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (tabs[cId] && tabs[cId].close) {
console.log('leave');
tabs[cId].close();
}
} else if (self.tabs[cId] && self.tabs[cId].msgEv) {
self.tabs[cId].msgEv.fire(e);
}
});
self.addEventListener('install', function (e) {
debug('V1 installing…');
self.skipWaiting();
});
self.addEventListener('activate', function (e) {
debug('V1 now ready to handle fetches!');
});

View File

@@ -0,0 +1,180 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.port.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SharedW INIT');
require.config({
waitSeconds: 600
});
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SharedW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SharedW Channel created');
var clientId = client.id;
client.chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SharedW connecting to store...');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
self.store = data.returned;
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
client.channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: client.channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
client.msgEv = msgEv;
client.close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
onconnect = function(e) {
debug('New SharedWorker client');
var port = e.ports[0];
var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
var client = self.tabs[cId] = {
id: cId,
port: port
};
port.onmessage = function (e) {
if (e.data === "INIT") {
if (client.init) { return; }
client.init = true;
init(client, function () {
postMsg(client, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (client && client.close) {
console.log('leave');
client.close();
}
} else if (client && client.msgEv) {
client.msgEv.fire(e);
}
};
};

View File

@@ -1,193 +1,103 @@
define([
'/common/outer/async-store.js'
], function (Store) {
var Rpc = {};
], function (AStore) {
Rpc.query = function (cmd, data, cb) {
switch (cmd) {
// READY
case 'CONNECT': {
Store.init(data, cb); break;
}
case 'DISCONNECT': {
Store.disconnect(data, cb); break;
}
case 'CREATE_README': {
Store.createReadme(data, cb); break;
}
case 'MIGRATE_ANON_DRIVE': {
Store.migrateAnonDrive(data, cb); break;
}
var create = function () {
var Store = AStore.create();
var Rpc = {};
var queries = Rpc.queries = {
// Ready
CONNECT: Store.init,
DISCONNECT: Store.disconnect,
CREATE_README: Store.createReadme,
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
// RPC
case 'INIT_RPC': {
Store.initRpc(data, cb); break;
}
case 'UPDATE_PIN_LIMIT': {
Store.updatePinLimit(data, cb); break;
}
case 'GET_PIN_LIMIT': {
Store.getPinLimit(data, cb); break;
}
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;
}
case 'UPLOAD_COMPLETE': {
Store.uploadComplete(data, cb); break;
}
case 'UPLOAD_STATUS': {
Store.uploadStatus(data, cb); break;
}
case 'UPLOAD_CANCEL': {
Store.uploadCancel(data, cb); break;
}
case 'PIN_PADS': {
Store.pinPads(data, cb); break;
}
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;
}
INIT_RPC: Store.initRpc,
UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,
CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
UPLOAD_CHUNK: Store.uploadChunk,
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
REMOVE_LOGIN_BLOCK: Store.removeLoginBlock,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
GET_PINNED_USAGE: Store.getPinnedUsage,
// ANON RPC
case 'INIT_ANON_RPC': {
Store.initAnonRpc(data, cb); break;
}
case 'ANON_RPC_MESSAGE': {
Store.anonRpcMsg(data, cb); break;
}
case 'GET_FILE_SIZE': {
Store.getFileSize(data, cb); break;
}
case 'GET_MULTIPLE_FILE_SIZE': {
Store.getMultipleFileSize(data, cb); break;
}
INIT_ANON_RPC: Store.initAnonRpc,
ANON_RPC_MESSAGE: Store.anonRpcMsg,
GET_FILE_SIZE: Store.getFileSize,
GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize,
// Store
case 'GET': {
Store.get(data, cb); break;
}
case 'SET': {
Store.set(data, cb); break;
}
case 'ADD_PAD': {
Store.addPad(data, cb); break;
}
case 'SET_PAD_TITLE': {
Store.setPadTitle(data, cb); break;
}
case 'MOVE_TO_TRASH': {
Store.moveToTrash(data, cb); break;
}
case 'RESET_DRIVE': {
Store.resetDrive(data, cb); break;
}
case 'GET_METADATA': {
Store.getMetadata(data, cb); break;
}
case 'SET_DISPLAY_NAME': {
Store.setDisplayName(data, cb); break;
}
case 'SET_PAD_ATTRIBUTE': {
Store.setPadAttribute(data, cb); break;
}
case 'GET_PAD_ATTRIBUTE': {
Store.getPadAttribute(data, cb); break;
}
case 'SET_ATTRIBUTE': {
Store.setAttribute(data, cb); break;
}
case 'GET_ATTRIBUTE': {
Store.getAttribute(data, cb); break;
}
case 'LIST_ALL_TAGS': {
Store.listAllTags(data, cb); break;
}
case 'GET_TEMPLATES': {
Store.getTemplates(data, cb); break;
}
case 'GET_SECURE_FILES_LIST': {
Store.getSecureFilesList(data, cb); break;
}
case 'GET_PAD_DATA': {
Store.getPadData(data, cb); break;
}
case 'SET_INITIAL_PATH': {
Store.setInitialPath(data); break;
}
case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break;
}
GET: Store.get,
SET: Store.set,
ADD_PAD: Store.addPad,
SET_PAD_TITLE: Store.setPadTitle,
MOVE_TO_TRASH: Store.moveToTrash,
RESET_DRIVE: Store.resetDrive,
GET_METADATA: Store.getMetadata,
IS_ONLY_IN_SHARED_FOLDER: Store.isOnlyInSharedFolder,
SET_DISPLAY_NAME: Store.setDisplayName,
SET_PAD_ATTRIBUTE: Store.setPadAttribute,
GET_PAD_ATTRIBUTE: Store.getPadAttribute,
SET_ATTRIBUTE: Store.setAttribute,
GET_ATTRIBUTE: Store.getAttribute,
LIST_ALL_TAGS: Store.listAllTags,
GET_TEMPLATES: Store.getTemplates,
GET_SECURE_FILES_LIST: Store.getSecureFilesList,
GET_PAD_DATA: Store.getPadData,
GET_STRONGER_HASH: Store.getStrongerHash,
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
GET_SHARED_FOLDER: Store.getSharedFolder,
ADD_SHARED_FOLDER: Store.addSharedFolder,
// Messaging
case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break;
}
INVITE_FROM_USERLIST: Store.inviteFromUserlist,
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
// Messenger
case 'CONTACTS_GET_FRIEND_LIST': {
Store.messenger.getFriendList(data, cb); break;
}
case 'CONTACTS_GET_MY_INFO': {
Store.messenger.getMyInfo(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_INFO': {
Store.messenger.getFriendInfo(data, cb); break;
}
case 'CONTACTS_REMOVE_FRIEND': {
Store.messenger.removeFriend(data, cb); break;
}
case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
Store.messenger.openFriendChannel(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_STATUS': {
Store.messenger.getFriendStatus(data, cb); break;
}
case 'CONTACTS_GET_MORE_HISTORY': {
Store.messenger.getMoreHistory(data, cb); break;
}
case 'CONTACTS_SEND_MESSAGE': {
Store.messenger.sendMessage(data, cb); break;
}
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList,
CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo,
CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo,
CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend,
CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel,
CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus,
CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Pad
case 'SEND_PAD_MSG': {
Store.sendPadMsg(data, cb); break;
}
case 'JOIN_PAD': {
Store.joinPad(data, cb); break;
}
case 'GET_FULL_HISTORY': {
Store.getFullHistory(data, cb); break;
}
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
IS_NEW_CHANNEL: Store.isNewChannel,
// Drive
case 'DRIVE_USEROBJECT': {
Store.userObjectCommand(data, cb); break;
}
// Settings
case 'DELETE_ACCOUNT': {
Store.deleteAccount(data, cb); break;
}
case 'IS_NEW_CHANNEL': {
Store.isNewChannel(data, cb); break;
}
default: {
console.error("UNHANDLED_STORE_RPC");
DRIVE_USEROBJECT: Store.userObjectCommand,
// Settings,
DELETE_ACCOUNT: Store.deleteAccount,
};
break;
Rpc.query = function (cmd, data, cb) {
if (queries[cmd]) {
queries[cmd]('0', data, cb);
} else {
console.error('UNHANDLED_STORE_RPC');
}
}
};
// Internal calls
Rpc._removeClient = Store._removeClient;
Rpc._subscribeToDrive = Store._subscribeToDrive;
Rpc._subscribeToMessenger = Store._subscribeToMessenger;
return Rpc;
};
return Rpc;
return create;
});

View File

@@ -0,0 +1,4 @@
if (!self.crypto && !self.msCrypto) {
throw new Error("E_NOCRYPTO");
}
self.postMessage("OK");

View File

@@ -1,8 +1,9 @@
define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash) {
], function (FileCrypto, Hash, nThen) {
var Nacl = window.nacl;
var module = {};
@@ -10,83 +11,123 @@ define([
var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata;
var owned = file.owned;
// if it exists, path contains the new pad location in the drive
var path = file.path;
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var password = file.password;
var forceSave = file.forceSave;
var hash, secret, key, id, href;
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var getNewHash = function () {
hash = Hash.createRandomHash('file', password);
secret = Hash.getSecrets('file', hash, password);
key = secret.keys.cryptKey;
id = secret.channel;
href = '/file/#' + hash;
};
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
var getValidHash = function (cb) {
getNewHash();
common.getFileSize(href, password, function (err, size) {
if (err || typeof(size) !== "number") { throw new Error(err || "Invalid size!"); }
if (size === 0) { return void cb(); }
getValidHash();
});
};
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);
var edPublic;
nThen(function (waitFor) {
// Generate a hash and check if the resulting id is valid (not already used)
getValidHash(waitFor());
}).nThen(function (waitFor) {
if (!owned) { return; }
common.getMetadata(waitFor(function (err, m) {
edPublic = m.priv.edPublic;
metadata.owners = [edPublic];
}));
}).nThen(function () {
var next = FileCrypto.encrypt(u8, metadata, key);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
});
}
};
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
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);
// 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 = Hash.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
common.setPadTitle(title || "", href, path, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
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(id, owned, function (e) {
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 title = metadata.name;
if (noStore) { return void onComplete(href); }
var data = {
title: title || "",
href: href,
path: path,
password: password,
channel: id,
owners: metadata.owners,
forceSave: forceSave
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
common.setPadAttribute('owners', metadata.owners, null, href);
});
});
}
next(again);
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(estimate, function (e) {
if (e) {
return void console.error(e);
}
next(again);
});
});
}
next(again);
});
});
};
return module;
});

View File

@@ -14,15 +14,11 @@ define([
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
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 sharedFolder = config.sharedFolder;
var edPublic = config.edPublic;
var ROOT = exp.ROOT;
@@ -31,6 +27,7 @@ define([
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
var debug = exp.debug;
@@ -50,35 +47,31 @@ define([
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
exp.debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
// Check if we already have this shared folder in our drive
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
return files[SHARED_FOLDERS][k].channel === data.channel;
})) {
return void cb ('EEXISTS');
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
// Add the folder
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void cb("EAUTH");
}
var id = Util.createRandomInteger();
files[SHARED_FOLDERS][id] = data;
cb(null, id);
};
// FILES DATA
@@ -87,21 +80,21 @@ define([
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells
// us they're already removed
exp.checkDeletedFiles = function (isOwnPadRemoved) {
// Nothing in FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
// FILES_DATA. If there are owned pads, remove them from server too.
exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
// If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
if (!sharedFolder && fd.owners && fd.owners.indexOf(edPublic) !== -1
&& channelId) {
if (channelId) { ownedRemoved.push(channelId); }
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
@@ -111,20 +104,21 @@ define([
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
}
});
}
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (channelId) { toClean.push(channelId); }
spliceFileData(id);
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
} else {
spliceFileData(id);
}
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved);
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
@@ -138,7 +132,7 @@ define([
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
@@ -146,14 +140,11 @@ define([
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
var id = path[1];
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
return void cb();
}
var ids = [];
@@ -193,11 +184,69 @@ define([
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
// FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(cb); }
else { cb(); }
};
// Move
// From another drive
exp.copyFromOtherDrive = function (path, element, data, key) {
// Copy files data
// We have to remove pads that are already in the current proxy to make sure
// we won't create duplicates
var toRemove = [];
Object.keys(data).forEach(function (id) {
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
var found = false;
for (var i in files[FILES_DATA]) {
if (files[FILES_DATA][i].channel === d.channel) {
// Update href?
if (!files[FILES_DATA][i].href) { files[FILES_DATA][i].href = d.href; }
found = true;
break;
}
}
if (found) {
toRemove.push(id);
return;
}
files[FILES_DATA][id] = data[id];
});
// Remove existing pads from the "element" variable
if (exp.isFile(element) && toRemove.indexOf(element) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
return;
} else if (exp.isFolder(element)) {
var _removeExisting = function (root) {
for (var k in root) {
if (exp.isFile(root[k])) {
if (toRemove.indexOf(root[k]) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
delete root[k];
}
} else if (exp.isFolder(root[k])) {
_removeExisting(root[k]);
}
}
};
_removeExisting(element);
}
// Copy file or folder
var newParent = exp.find(path);
var tempName = exp.isFile(element) ? Hash.createChannelId() : key;
var newName = exp.getAvailableName(newParent, tempName);
newParent[newName] = element;
};
// From the same drive
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
@@ -260,7 +309,6 @@ define([
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
@@ -269,14 +317,7 @@ define([
};
// REPLACE
exp.replace = function (o, n) {
var idO = exp.getIdFromHref(o);
if (!idO || !exp.isFile(idO)) { return; }
var data = exp.getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// If all the occurences of an href are in the trash, remove them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = exp.getIdFromHref(href);
@@ -301,9 +342,9 @@ define([
};
exp.add = function (id, path) {
// TODO WW
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
id = Number(id);
var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
@@ -407,7 +448,6 @@ define([
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
@@ -427,7 +467,7 @@ define([
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
exp.fixFiles = function (silent) {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
@@ -436,6 +476,9 @@ define([
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
if (silent) { debug = function () {}; }
debug("Cleaning file system...");
var before = JSON.stringify(files);
@@ -471,6 +514,7 @@ define([
}
};
var fixTrashRoot = function () {
if (sharedFolder) { return; }
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
@@ -514,6 +558,7 @@ define([
}
};
var fixTemplate = function () {
if (sharedFolder) { return; }
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
@@ -545,7 +590,7 @@ define([
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
@@ -553,32 +598,73 @@ define([
for (var id in fd) {
id = Number(id);
var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
// Clean missing href
if (!el.href && !el.roHref) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
var parsed = Hash.parsePadUrl(el.href || el.roHref);
var secret;
// Clean invalid hash
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
// Clean invalid type
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
// If we have an edit link, check the view link
if (el.href && parsed.hashData.type === "pad") {
if (parsed.hashData.mode === "view") {
el.roHref = el.href;
delete el.href;
} else if (!el.roHref) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
} else {
var parsed2 = Hash.parsePadUrl(el.roHref);
if (!parsed2.hash || !parsed2.type) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
}
// Fix href
if (el.href && /^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
try {
if (!secret) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
}
el.channel = secret.channel;
console.log(el);
debug('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
@@ -590,6 +676,21 @@ define([
spliceFileData(id);
});
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; }
var sf = files[SHARED_FOLDERS];
var rootFiles = exp.getFiles([ROOT]);
var root = exp.find([ROOT]);
for (var id in sf) {
id = Number(id);
if (rootFiles.indexOf(id) === -1) {
console.log('missing' + id);
var newName = Hash.createChannelId();
root[newName] = id;
}
}
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
@@ -599,11 +700,10 @@ define([
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixTemplate();
fixFilesData();
fixDrive();
fixSharedFolders();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");

View File

@@ -0,0 +1,103 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var Rpc = SRpc();
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
onmessage = function (e) {
msgEv.fire(e);
};
});
});
});

View File

@@ -0,0 +1,151 @@
// This file provides the API for the channel for talking to and from the sandbox iframe.
define([
//'/common/sframe-protocol.js',
'/common/common-util.js'
], function (/*SFrameProtocol,*/ Util) {
var mkTxid = function () {
return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
};
var create = function (onMsg, postMsg, cb, isWorker) {
var chanLoaded;
var waitingData;
if (!isWorker) {
chanLoaded = false;
waitingData = [];
onMsg.reg(function (data) {
if (chanLoaded) { return; }
waitingData.push(data);
});
}
var evReady = Util.mkEvent(true);
var handlers = {};
var queries = {};
// list of handlers which are registered from the other side...
var insideHandlers = [];
var callWhenRegistered = {};
var chan = {};
// Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
chan.query = function (q, content, cb, opts) {
var txid = mkTxid();
opts = opts || {};
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
//console.log("Timeout making query " + q);
}, to);
queries[txid] = function (data, msg) {
clearTimeout(timeout);
delete queries[txid];
cb(undefined, data.content, msg);
};
evReady.reg(function () {
postMsg(JSON.stringify({
txid: txid,
content: content,
q: q
}));
});
};
// Fire an event. channel.event('EV_SOMETHING', { args: "whatever" });
var event = chan.event = function (e, content) {
evReady.reg(function () {
postMsg(JSON.stringify({ content: content, q: e }));
});
};
// Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... });
// If the type is a query, your handler will be invoked with a reply function that takes
// one argument (the content to reply with).
chan.on = function (queryType, handler, quiet) {
(handlers[queryType] = handlers[queryType] || []).push(function (data, msg) {
handler(data.content, function (replyContent) {
postMsg(JSON.stringify({
txid: data.txid,
content: replyContent
}));
}, msg);
});
if (!quiet) {
event('EV_REGISTER_HANDLER', queryType);
}
};
// If a particular handler is registered, call the callback immediately, otherwise it will be called
// when that handler is first registered.
// channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... });
chan.whenReg = function (queryType, cb, always) {
var reg = always;
if (insideHandlers.indexOf(queryType) > -1) {
cb();
} else {
reg = true;
}
if (reg) {
(callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb);
}
};
// Same as whenReg except it will invoke every time there is another registration, not just once.
chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); };
chan.on('EV_REGISTER_HANDLER', function (content) {
if (callWhenRegistered[content]) {
callWhenRegistered[content].forEach(function (f) { f(); });
delete callWhenRegistered[content];
}
insideHandlers.push(content);
});
chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
// Make sure both iframes are ready
var isReady =false;
chan.onReady = function (h) {
if (isReady) {
return void h();
}
if (typeof(h) !== "function") { return; }
chan.on('EV_RPC_READY', function () { isReady = true; h(); });
};
chan.ready = function () {
chan.whenReg('EV_RPC_READY', function () {
chan.event('EV_RPC_READY');
});
};
onMsg.reg(function (msg) {
var data = JSON.parse(msg.data);
if (typeof(data.q) === 'string' && handlers[data.q]) {
handlers[data.q].forEach(function (f) {
f(data || JSON.parse(msg.data), msg);
data = undefined;
});
} else if (typeof(data.q) === 'undefined' && queries[data.txid]) {
queries[data.txid](data, msg);
} else {
console.log("DROP Unhandled message");
console.log(msg.data, isWorker);
console.log(msg);
}
});
if (isWorker) {
evReady.fire();
} else {
chanLoaded = true;
waitingData.forEach(function (d) {
onMsg.fire(d);
});
waitingData = [];
}
cb(chan);
};
return { create: create };
});

View File

@@ -151,7 +151,7 @@ define([
};
exp.removeOwnedChannel = function (channel, cb) {
if (typeof(channel) !== 'string' || channel.length !== 32) {
if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
// can't use this on files because files can't be owned...
return void cb('INVALID_ARGUMENTS');
}
@@ -165,8 +165,30 @@ define([
});
};
exp.uploadComplete = function (cb) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
exp.removePins = function (cb) {
rpc.send('REMOVE_PINS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.uploadComplete = function (id, cb) {
rpc.send('UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.ownedUploadComplete = function (id, cb) {
rpc.send('OWNED_UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
@@ -192,13 +214,44 @@ define([
});
};
exp.uploadCancel = function (cb) {
rpc.send('UPLOAD_CANCEL', void 0, function (e) {
exp.uploadCancel = function (size, cb) {
rpc.send('UPLOAD_CANCEL', size, function (e) {
if (e) { return void cb(e); }
cb();
});
};
exp.writeLoginBlock = function (data, cb) {
if (!data) { return void cb('NO_DATA'); }
if (!data.publicKey || !data.signature || !data.ciphertext) {
console.log(data);
return void cb("MISSING_PARAMETERS");
}
rpc.send('WRITE_LOGIN_BLOCK', [
data.publicKey,
data.signature,
data.ciphertext
], function (e) {
cb(e);
});
};
exp.removeLoginBlock = function (data, cb) {
if (!data) { return void cb('NO_DATA'); }
if (!data.publicKey || !data.signature) {
console.log(data);
return void cb("MISSING_PARAMETERS");
}
rpc.send('REMOVE_LOGIN_BLOCK', [
data.publicKey, // publicKey
data.signature, // signature
], function (e) {
cb(e);
});
};
cb(e, exp);
});
};

1024
www/common/proxy-manager.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,7 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function (
$,
Hyperjson,
@@ -126,7 +125,7 @@ define([
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
state = newState;
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
throw new Error("Cannot transition from DISCONNECTED to " + newState);
throw new Error("Cannot transition from DISCONNECTED to " + newState); // FIXME we are getting "DISCONNECTED to READY" on prod
} else if (state !== STATE.READY && newState === STATE.HISTORY_MODE) {
throw new Error("Cannot transition from " + state + " to " + newState);
} else {
@@ -140,6 +139,11 @@ define([
toolbar.initializing();
return;
}
if (text) {
// text is a boolean here. It means we won't try to reconnect
toolbar.failed();
return;
}
toolbar.reconnecting();
});
break;
@@ -171,9 +175,12 @@ define([
}
};
var contentUpdate = function (newContent) {
var oldContent;
var contentUpdate = function (newContent, waitFor) {
if (JSONSortify(newContent) === JSONSortify(oldContent)) { return; }
try {
evContentUpdate.fire(newContent);
evContentUpdate.fire(newContent, waitFor);
setTimeout(function () { oldContent = newContent; });
} catch (e) {
console.log(e.stack);
UI.errorLoadingScreen(e.message);
@@ -192,46 +199,48 @@ define([
cpNfInner.metadataMgr.updateMetadata(meta);
newContent = normalize(newContent);
contentUpdate(newContent);
nThen(function (waitFor) {
contentUpdate(newContent, waitFor);
}).nThen(function () {
if (!readOnly) {
var newContent2NoMeta = normalize(contentGetter());
var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
var newContentStrNoMeta = JSONSortify(newContent);
if (!readOnly) {
var newContent2NoMeta = normalize(contentGetter());
var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
var newContentStrNoMeta = JSONSortify(newContent);
if (newContent2StrNoMeta !== newContentStrNoMeta) {
console.error("shjson2 !== shjson");
onLocal();
if (newContent2StrNoMeta !== newContentStrNoMeta) {
console.error("shjson2 !== shjson");
onLocal();
/* pushing back over the wire is necessary, but it can
result in a feedback loop, which we call a browser
fight */
// what changed?
var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
// log the changes
console.log(newContentStrNoMeta);
console.log(ops);
var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
/* pushing back over the wire is necessary, but it can
result in a feedback loop, which we call a browser
fight */
// what changed?
var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
// log the changes
console.log(newContentStrNoMeta);
console.log(ops);
var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
var fights = window.CryptPad_fights = window.CryptPad_fights || [];
var index = fights.indexOf(sop);
if (index === -1) {
fights.push(sop);
console.log("Found a new type of browser disagreement");
console.log("You can inspect the list in your " +
"console at `REALTIME_MODULE.fights`");
console.log(fights);
} else {
console.log("Encountered a known browser disagreement: " +
"available at `REALTIME_MODULE.fights[%s]`", index);
var fights = window.CryptPad_fights = window.CryptPad_fights || [];
var index = fights.indexOf(sop);
if (index === -1) {
fights.push(sop);
console.log("Found a new type of browser disagreement");
console.log("You can inspect the list in your " +
"console at `REALTIME_MODULE.fights`");
console.log(fights);
} else {
console.log("Encountered a known browser disagreement: " +
"available at `REALTIME_MODULE.fights[%s]`", index);
}
}
}
}
// Notify only when the content has changed, not when someone has joined/left
if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
common.notify();
}
// Notify only when the content has changed, not when someone has joined/left
if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
common.notify();
}
});
};
var setHistoryMode = function (bool, update) {
@@ -279,66 +288,71 @@ define([
var newContentStr = cpNfInner.chainpad.getUserDoc();
if (state === STATE.DELETED) { return; }
UI.updateLoadingProgress({ state: -1 }, false);
var newPad = false;
if (newContentStr === '') { newPad = true; }
if (!newPad) {
var newContent = JSON.parse(newContentStr);
cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
newContent = normalize(newContent);
contentUpdate(newContent);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
// We don't know exactly why this can happen but under no circumstances
// should we overwrite the content, so lets just try again.
console.log("userDoc is '' but this is not a new pad.");
console.log("Either this is an empty document which has not been touched");
console.log("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000);
return;
// contentUpdate may be async so we need an nthen here
nThen(function (waitFor) {
if (!newPad) {
var newContent = JSON.parse(newContentStr);
cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
newContent = normalize(newContent);
contentUpdate(newContent, waitFor);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
// We don't know exactly why this can happen but under no circumstances
// should we overwrite the content, so lets just try again.
console.log("userDoc is '' but this is not a new pad.");
console.log("Either this is an empty document which has not been touched");
console.log("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000);
return;
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
stateChange(STATE.READY);
firstConnection = false;
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
}).nThen(function () {
stateChange(STATE.READY);
firstConnection = false;
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
UI.removeLoadingScreen(emitResize);
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) {
var channelId = Hash.hrefToHexChannelId(href);
AppConfig.textAnalyzer(textContentGetter, channelId);
}
if (options.thumbnail && privateDat.thumbnails) {
if (hash) {
options.thumbnail.href = href;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();
};
Thumb.initPadThumbnails(common, options.thumbnail);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) {
AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
}
}
var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
if (options.thumbnail && privateDat.thumbnails) {
if (hash) {
options.thumbnail.href = href;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();
};
Thumb.initPadThumbnails(common, options.thumbnail);
}
}
var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
});
};
var onConnectionChange = function (info) {
if (state === STATE.DELETED) { return; }
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED, info.permanent);
/*if (info.state) {
UI.findOKButton().click();
} else {
@@ -379,13 +393,19 @@ define([
common.createButton('import', true, options, function (c, f) {
if (async) {
fi(c, f, function (content) {
contentUpdate(content);
onLocal();
nThen(function (waitFor) {
contentUpdate(content, waitFor);
}).nThen(function () {
onLocal();
});
});
return;
}
contentUpdate(fi(c, f));
onLocal();
nThen(function (waitFor) {
contentUpdate(fi(c, f), waitFor);
}).nThen(function () {
onLocal();
});
})
);
};
@@ -432,6 +452,9 @@ define([
nThen(function (waitFor) {
UI.addLoadingScreen();
SFCommon.create(waitFor(function (c) { common = c; }));
UI.updateLoadingProgress({
state: 1
}, false);
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
@@ -443,7 +466,7 @@ define([
patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer,
// cryptpad debug logging (default is 1)
// logLevel: 2,
logLevel: 1,
validateContent: options.validateContent || function (content) {
try {
JSON.parse(content);
@@ -457,10 +480,17 @@ define([
},
onRemote: onRemote,
onLocal: onLocal,
onInit: function () { stateChange(STATE.INITIALIZING); },
onInit: function () {
UI.updateLoadingProgress({
state: 2,
progress: 0.1
}, false);
stateChange(STATE.INITIALIZING);
},
onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange,
onError: onError
onError: onError,
updateLoadingProgress: UI.updateLoadingProgress
});
var privReady = Util.once(waitFor());
@@ -555,7 +585,9 @@ define([
onRemote: onRemote,
setHistory: setHistoryMode,
applyVal: function (val) {
contentUpdate(JSON.parse(val) || ["BODY",{},[]]);
contentUpdate(JSON.parse(val) || ["BODY",{},[]], function (h) {
return h;
});
},
$toolbar: $(toolbarContainer)
};
@@ -572,6 +604,9 @@ define([
toolbar.$rightside.append($templateButton);
}
var $importTemplateButton = common.createButton('importtemplate', true);
toolbar.$drawer.append($importTemplateButton);
/* add a forget button */
toolbar.$rightside.append(common.createButton('forget', true, {}, function (err) {
if (err) { return; }

View File

@@ -41,10 +41,11 @@ define([
var patchTransformer = config.patchTransformer;
var validateContent = config.validateContent;
var avgSyncMilliseconds = config.avgSyncMilliseconds;
var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 2;
var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 1;
var readOnly = config.readOnly || false;
var sframeChan = config.sframeChan;
var metadataMgr = config.metadataMgr;
var updateLoadingProgress = config.updateLoadingProgress;
config = undefined;
var chainpad = ChainPad.create({
@@ -64,6 +65,7 @@ define([
var myID;
var isReady = false;
var isHistory = 1;
var evConnected = Util.mkEvent(true);
var evInfiniteSpinner = Util.mkEvent(true);
@@ -79,10 +81,12 @@ define([
evInfiniteSpinner.fire();
}, 2000);
sframeChan.on('EV_RT_DISCONNECT', function () {
sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
isReady = false;
chainpad.abort();
onConnectionChange({ state: false });
// Permanent flag is here to choose if we wnat to display
// "reconnecting" or "disconnected" in the toolbar state
onConnectionChange({ state: false, permanent: isPermanent });
});
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;
@@ -112,11 +116,19 @@ define([
onLocal(true); // should be onBeforeMessage
}
chainpad.message(content);
if (isHistory && updateLoadingProgress) {
updateLoadingProgress({
state: 2,
progress: isHistory
}, false);
isHistory++;
}
cb('OK');
});
sframeChan.on('EV_RT_READY', function () {
if (isReady) { return; }
isReady = true;
isHistory = false;
chainpad.start();
setMyID({ myID: myID });
onReady({ realtime: chainpad });

View File

@@ -39,9 +39,11 @@ define([], function () {
});
// shim between chainpad and netflux
var msgIn = function (msg) {
var msgIn = function (peer, msg) {
try {
var decryptedMsg = Crypto.decrypt(msg, isNewHash);
var isHk = peer.length !== 32;
var key = isNewHash ? validateKey : false;
var decryptedMsg = Crypto.decrypt(msg, key, isHk);
return decryptedMsg;
} catch (err) {
console.error(err);
@@ -53,7 +55,16 @@ define([], function () {
if (readOnly) { return; }
try {
var cmsg = Crypto.encrypt(msg);
if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; }
if (msg.indexOf('[4') === 0) {
var id = '';
if (window.nacl) {
var hash = window.nacl.hash(window.nacl.util.decodeUTF8(msg));
id = window.nacl.util.encodeBase64(hash.slice(0, 8)) + '|';
} else {
console.log("Checkpoint sent without an ID. Nacl is missing.");
}
cmsg = 'cp|' + id + cmsg;
}
return cmsg;
} catch (err) {
console.log(msg);
@@ -67,8 +78,11 @@ define([], function () {
padRpc.sendPadMsg(msg, cb);
});
var onMessage = function(msg) {
var message = msgIn(msg);
var onMessage = function(msgObj) {
if (msgObj.validateKey && !validateKey) {
validateKey = msgObj.validateKey;
}
var message = msgIn(msgObj.user, msgObj.msg);
verbose(message);
@@ -98,8 +112,12 @@ define([], function () {
}
};
padRpc.onDisconnectEvent.reg(function () {
sframeChan.event('EV_RT_DISCONNECT');
padRpc.onDisconnectEvent.reg(function (permanent) {
sframeChan.event('EV_RT_DISCONNECT', permanent);
});
padRpc.onConnectEvent.reg(function (data) {
onOpen(data);
});
padRpc.onErrorEvent.reg(function (err) {
@@ -114,8 +132,6 @@ define([], function () {
owners: owners,
password: password,
expire: expire
}, function(data) {
onOpen(data);
});
};

View File

@@ -21,16 +21,18 @@ define([
var chan = {};
// Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
chan.query = function (q, content, cb) {
chan.query = function (q, content, cb, opts) {
if (!otherWindow) { throw new Error('not yet initialized'); }
if (!SFrameProtocol[q]) {
throw new Error('please only make queries are defined in sframe-protocol.js');
}
opts = opts || {};
var txid = mkTxid();
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
console.log("Timeout making query " + q);
}, 30000);
}, to);
queries[txid] = function (data, msg) {
clearTimeout(timeout);
delete queries[txid];

View File

@@ -329,14 +329,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' +
parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

View File

@@ -3,11 +3,13 @@ define([
'/file/file-crypto.js',
'/common/common-thumbnail.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb, UI, Util, Messages) {
], function ($, FileCrypto, Thumb, UI, UIElements, Util, h, Messages) {
var Nacl = window.nacl;
var module = {};
@@ -26,6 +28,7 @@ define([
module.create = function (common, config) {
var File = {};
var origin = common.getMetadataMgr().getPrivateData().origin;
var queue = File.queue = {
queue: [],
@@ -53,6 +56,7 @@ define([
data.name = file.metadata.name;
data.url = href;
data.password = file.password;
if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true;
}
@@ -219,29 +223,89 @@ define([
queue.next();
};
// Don't show the rename prompt if we don't want to store the file in the drive (avatar)
var showNamePrompt = !config.noStore;
var promptName = function (file, cb) {
// Get the upload options
var modalState = {
owned: true,
store: true
};
var fileUploadModal = function (file, cb) {
var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;
var ext = extIdx !== -1 ? file.name.slice(extIdx) : "";
var msg = Messages._getKey('upload_rename', [
Util.fixHTML(file.name),
Util.fixHTML(ext)
var createHelper = function (href, text) {
var q = h('a.fa.fa-question-circle', {
style: 'text-decoration: none !important;',
title: text,
href: origin + href,
target: "_blank",
'data-tippy-placement': "right"
});
return q;
};
var privateData = common.getMetadataMgr().getPrivateData();
var autoStore = Util.find(privateData, ['settings', 'general', 'autostore']) || 0;
var initialState = modalState.owned || modalState.store;
var initialDisabled = modalState.owned ? { disabled: true } : {};
var manualStore = autoStore === 1 ? undefined :
UI.createCheckbox('cp-upload-store', Messages.autostore_forceSave, initialState, {
input: initialDisabled
});
// Ask for name, password and owner
var content = h('div', [
h('h4', Messages.upload_modal_title),
UIElements.setHTML(h('label', {for: 'cp-upload-name'}),
Messages._getKey('upload_modal_filename', [ext])),
h('input#cp-upload-name', {type: 'text', placeholder: name}),
h('label', {for: 'cp-upload-password'}, Messages.creation_passwordValue),
UI.passwordInput({id: 'cp-upload-password'}),
h('span', {
style: 'display:flex;align-items:center;justify-content:space-between'
}, [
UI.createCheckbox('cp-upload-owned', Messages.upload_modal_owner, modalState.owned),
createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
]),
manualStore
]);
UI.prompt(msg, name, function (newName) {
if (newName === null) {
showNamePrompt = false;
return void cb (file.name);
$(content).find('#cp-upload-owned').on('change', function () {
var val = $(content).find('#cp-upload-owned').is(':checked');
if (val) {
$(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true);
} else {
$(content).find('#cp-upload-store').prop('disabled', false);
}
if (!newName || !newName.trim()) { return void cb (file.name); }
});
UI.confirm(content, function (yes) {
if (!yes) { return void cb(); }
// Get the values
var newName = $(content).find('#cp-upload-name').val();
var password = $(content).find('#cp-upload-password').val() || undefined;
var owned = $(content).find('#cp-upload-owned').is(':checked');
var forceSave = owned || $(content).find('#cp-upload-store').is(':checked');
modalState.owned = owned;
modalState.store = forceSave;
// Add extension to the name if needed
if (!newName || !newName.trim()) { newName = file.name; }
var newExtIdx = newName.lastIndexOf('.');
var newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : "";
if (newExt !== ext) { newName += ext; }
cb(newName);
}, {cancel: Messages.doNotAskAgain}, true);
cb({
name: newName,
password: password,
owned: owned,
forceSave: forceSave
});
});
};
var handleFileState = {
queue: [],
inProgress: false
@@ -253,17 +317,25 @@ define([
var thumb;
var file_arraybuffer;
var name = file.name;
var finish = function () {
var metadata = {
name: name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: file_arraybuffer,
metadata: metadata,
dropEvent: e
});
var password;
var owned = true;
var forceSave;
var finish = function (abort) {
if (!abort) {
var metadata = {
name: name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: file_arraybuffer,
metadata: metadata,
password: password,
owned: owned,
forceSave: forceSave,
dropEvent: e
});
}
handleFileState.inProgress = false;
if (handleFileState.queue.length) {
var next = handleFileState.queue.shift();
@@ -271,9 +343,17 @@ define([
}
};
var getName = function () {
if (!showNamePrompt) { return void finish(); }
promptName(file, function (newName) {
name = newName;
// If "noStore", it means we don't want to store this file in our drive (avatar)
// In this case, we don't want a password or a filename, and we own the file
if (config.noStore) { return void finish(); }
// Otherwise, ask for password, name and ownership
fileUploadModal(file, function (obj) {
if (!obj) { return void finish(true); }
name = obj.name;
password = obj.password;
owned = obj.owned;
forceSave = obj.forceSave;
finish();
});
};
@@ -340,6 +420,8 @@ define([
var editor = config.ckeditor;
editor.document.on('drop', function (ev) {
var dropped = ev.data.$.dataTransfer.files;
editor.document.focus();
if (!dropped || !dropped.length) { return; }
onFileDrop(dropped, ev);
ev.data.preventDefault(true);
});

View File

@@ -1,26 +1,36 @@
define([
'jquery',
'/common/common-interface.js',
'/bower_components/nthen/index.js',
//'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad/chainpad.dist.js',
], function ($, UI, ChainPad /* JsonOT */) {
], function ($, UI, nThen, ChainPad /* JsonOT */) {
//var ChainPad = window.ChainPad;
var History = {};
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
var loadHistory = function (config, common, cb) {
var createRealtime = function () {
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
var createRealtime = function (config) {
return ChainPad.create({
userName: 'history',
validateContent: function (content) {
@@ -33,36 +43,45 @@ define([
}
},
initialState: '',
//patchTransformer: ChainPad.NaiveJSONTransformer,
//logLevel: 0,
//transformFunction: JsonOT.validate,
logLevel: config.debug ? 2 : 0,
noPrune: true
});
};
var realtime = createRealtime();
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
var loadFullHistory = function (config, common, cb) {
var realtime = createRealtime(config);
common.getFullHistory(realtime, function () {
cb(null, realtime);
});
};
loadFullHistory = loadFullHistory;
/*var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 30000);*/
var fillChainPad = function (realtime, messages) {
messages.forEach(function (m) {
realtime.message(m);
});
};
common.getFullHistory(realtime, function () {
//window.clearTimeout(to);
cb(null, realtime);
});
};
var allMessages = [];
var lastKnownHash;
var isComplete = false;
var loadMoreHistory = function (config, common, cb) {
if (isComplete) { return void cb ('EFULL'); }
var realtime = createRealtime(config);
var sframeChan = common.getSframeChannel();
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
sframeChan.query('Q_GET_HISTORY_RANGE', {
lastKnownHash: lastKnownHash
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
lastKnownHash = data.lastKnownHash;
isComplete = data.isFull;
Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat
fillChainPad(realtime, allMessages);
cb (null, realtime, data.isFull);
});
};
// config.setHistory(bool, bool)
// - bool1: history value
@@ -84,21 +103,20 @@ define([
};
config.setHistory(true);
var onReady = function () { };
var Messages = common.Messages;
var realtime;
var states = [];
var c = states.length - 1;
var c = 0;//states.length - 1;
var $hist = $toolbar.find('.cp-toolbar-history');
var $left = $toolbar.find('.cp-toolbar-leftside');
var $right = $toolbar.find('.cp-toolbar-rightside');
var $cke = $toolbar.find('.cke_toolbox_main');
$hist.html('').show();
$hist.html('').css('display', 'flex');
$left.hide();
$right.hide();
$cke.hide();
@@ -107,29 +125,78 @@ define([
var onUpdate;
var update = function () {
var update = function (newRt) {
realtime = newRt;
if (!realtime) { return []; }
states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); }
return states;
};
var $loadMore, $version, get;
// Get the content of the selected version, and change the version number
var get = function (i) {
var loading = false;
var loadMore = function (cb) {
if (loading) { return; }
loading = true;
$loadMore.removeClass('fa fa-ellipsis-h')
.append($('<span>', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'}));
loadMoreHistory(config, common, function (err, newRt, isFull) {
if (err === 'EFULL') {
$loadMore.off('click').hide();
get(c);
$version.show();
return;
}
loading = false;
if (err) { return void console.error(err); }
update(newRt);
$loadMore.addClass('fa fa-ellipsis-h').html('');
get(c);
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
if (cb) { cb(); }
});
};
get = function (i) {
i = parseInt(i);
if (isNaN(i)) { return; }
if (i < 0) { i = 0; }
if (i > states.length - 1) { i = states.length - 1; }
var val = states[i].getContent().doc;
if (i > 0) { i = 0; }
if (i < -(states.length - 2)) { i = -(states.length - 2); }
if (i <= -(states.length - 11)) {
loadMore();
}
var idx = states.length - 1 + i;
var val = states[idx].getContent().doc;
c = i;
if (typeof onUpdate === "function") { onUpdate(); }
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', '');
if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); }
if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); }
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' +
'.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous')
.css('visibility', '');
if (c === -(states.length-1)) {
$hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden');
}
if (c === 0) {
$hist.find('.cp-toolbar-history-next').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden');
}
var $pos = $hist.find('.cp-toolbar-history-pos');
var p = 100 * (1 - (-c / (states.length-2)));
$pos.css('margin-left', p+'%');
// Display the version when the full history is loaded
// Note: the first version is always empty and probably can't be displayed, so
// we can consider we have only states.length - 1 versions
$version.text(idx + ' / ' + (states.length-1));
if (config.debug) {
console.log(states[i]);
var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations;
console.log(states[idx]);
var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); });
}
@@ -148,6 +215,17 @@ define([
// Create the history toolbar
var display = function () {
$hist.html('');
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
title: Messages.history_restoreTitle
}).appendTo($hist);//.text(Messages.history_restore);
if (History.readOnly) { $rev.css('visibility', 'hidden'); }
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $fastPrev = $('<button>', {
'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
var $prev =$('<button>', {
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
title: Messages.history_prev
@@ -157,58 +235,73 @@ define([
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<label>').text(Messages.history_version).appendTo($nav);
var $cur = $('<input>', {
'class' : 'cp-toolbar-history-goto-input',
'type' : 'number',
'min' : '1',
'max' : states.length
}).val(c + 1).appendTo($nav).mousedown(function (e) {
// stopPropagation because the event would be cancelled by the dropdown menus
e.stopPropagation();
});
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
$('<br>').appendTo($nav);
var $fastNext = $('<button>', {
'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $close = $('<button>', {
'class':'cp-toolbar-history-close',
'class':'cp-toolbar-history-close fa fa-window-close',
title: Messages.history_closeTitle
}).text(Messages.history_closeTitle).appendTo($nav);
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (History.readOnly) { $rev.hide(); }
}).appendTo($hist);
var $bar = $('<div>', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
var $container = $('<div>', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
$('<div>', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
$version = $('<span>', {
'class': 'cp-toolbar-history-version'
}).prependTo($bar).hide();
$loadMore = $('<button>', {
'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
title: Messages.history_loadMore
}).click(function () {
loadMore(function () {
get(c);
});
}).prependTo($container);
// Load a version when clicking on the bar
$container.click(function (e) {
e.stopPropagation();
if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
var p = e.offsetX / $container.width();
var v = -Math.round((states.length - 1) * (1 - p));
render(get(v));
});
onUpdate = function () {
$cur.attr('max', states.length);
$cur.val(c+1);
$label2.text(' / ' + states.length);
// Called when a new version is loaded
};
var onKeyDown, onKeyUp;
var close = function () {
$hist.hide();
$left.show();
$right.show();
$cke.show();
$(window).trigger('resize');
$(window).off('keydown', onKeyDown);
$(window).off('keyup', onKeyUp);
};
// Buttons actions
// Version buttons
$prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); });
$cur.keydown(function (e) {
$fastPrev.click(function () { render(getPrevious(10)); });
$fastNext.click(function () { render(getNext(10)); });
onKeyDown = function (e) {
var p = function () { e.preventDefault(); };
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); }
}).keyup(function (e) { e.stopPropagation(); }).focus();
$cur.on('change', function () {
render( get($cur.val() - 1) );
});
};
onKeyUp = function (e) { e.stopPropagation(); };
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
// Close & restore buttons
$close.click(function () {
states = [];
close();
@@ -229,14 +322,17 @@ define([
};
// Load all the history messages into a new chainpad object
loadHistory(config, common, function (err, newRt) {
loadMoreHistory(config, common, function (err, newRt, isFull) {
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
History.loading = false;
if (err) { throw new Error(err); }
realtime = newRt;
update();
update(newRt);
c = states.length - 1;
display();
onReady();
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
});
};

View File

@@ -24,6 +24,8 @@ define([
var Utils = {};
var AppConfig;
var Test;
var password;
var initialPathInDrive;
nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need...
@@ -88,7 +90,16 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor(), {
Cryptpad.loading.onDriveEvent.reg(function (data) {
if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); }
});
Cryptpad.ready(waitFor(function () {
if (sframeChan) {
sframeChan.event('EV_LOADING_INFO', {
state: -1
});
}
}), {
messenger: cfg.messaging,
driveEvents: cfg.driveEvents
});
@@ -113,6 +124,7 @@ define([
if (cfg.getSecrets) {
var w = waitFor();
// No password for drive, profile and todo
cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
secret = s;
Cryptpad.getShareHashes(secret, function (err, h) {
@@ -121,19 +133,83 @@ define([
});
}));
} else {
secret = Utils.Hash.getSecrets();
if (!secret.channel) {
// New pad: create a new random channel id
secret.channel = Utils.Hash.createChannelId();
var parsed = Utils.Hash.parsePadUrl(window.location.href);
var todo = function () {
secret = Utils.Hash.getSecrets(parsed.type, void 0, password);
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
};
// Prompt the password here if we have a hash containing /p/
// or get it from the pad attributes
var needPassword = parsed.hashData && parsed.hashData.password;
if (needPassword) {
// Check if we have a password, and check if it is correct (file exists).
// It we don't have a correct password, display the password prompt.
// Maybe the file has been deleted from the server or the password has been changed.
Cryptpad.getPadAttribute('password', waitFor(function (err, val) {
var askPassword = function (wrongPasswordStored) {
// Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password isn't correct
// or the pad has been deleted
var correctPassword = waitFor();
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
password = data;
var next = function (e, isNew) {
if (Boolean(isNew)) {
// Ask again in the inner iframe
// We should receive a new Q_PAD_PASSWORD_VALUE
cb(false);
} else {
todo();
if (wrongPasswordStored) {
// Store the correct password
Cryptpad.setPadAttribute('password', password, function () {
correctPassword();
}, parsed.getUrl());
} else {
correctPassword();
}
cb(true);
}
};
if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(window.location.href, password, function (e, size) {
next(e, size === 0);
});
return;
}
// Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(window.location.href, password, next);
});
sframeChan.event("EV_PAD_PASSWORD");
};
if (val) {
password = val;
Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) {
if (size !== 0) {
return void todo();
}
// Wrong password or deleted file?
askPassword(true);
}));
} else {
askPassword();
}
}), parsed.getUrl());
return;
}
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
// If no password, continue...
todo();
}
}).nThen(function (waitFor) {
// Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; }
if (realtime) {
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); }
isNewFile = Boolean(isNew);
}));
@@ -188,8 +264,14 @@ define([
},
isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen
forceCreationScreen: forceCreationScreen,
password: password,
channel: secret.channel,
enableSF: localStorage.CryptPad_SF === "1" // TODO to remove when enabled by default
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
}
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
if (cfg.addData) {
@@ -208,6 +290,10 @@ define([
Test.registerOuter(sframeChan);
Cryptpad.onNewVersionReconnect.reg(function () {
sframeChan.event("EV_NEW_VERSION");
});
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
@@ -253,10 +339,17 @@ define([
var title = currentTabTitle.replace(/\{title\}/g, currentTitle || 'CryptPad');
document.title = title;
};
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newData, cb) {
var newTitle = newData.title || newData.defaultTitle;
currentTitle = newTitle;
setDocumentTitle();
Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) {
var data = {
password: password,
title: newTitle,
channel: secret.channel,
path: initialPathInDrive // Where to store the pad if we don't have it in our drive
};
Cryptpad.setPadTitle(data, function (err) {
cb(err);
});
});
@@ -265,6 +358,50 @@ define([
setDocumentTitle();
});
Cryptpad.autoStore.onStoreRequest.reg(function (data) {
sframeChan.event("EV_AUTOSTORE_DISPLAY_POPUP", data);
});
sframeChan.on('Q_AUTOSTORE_STORE', function (obj, cb) {
var data = {
password: password,
title: currentTitle,
channel: secret.channel,
path: initialPathInDrive, // Where to store the pad if we don't have it in our drive
forceSave: true
};
Cryptpad.setPadTitle(data, function (err) {
cb(err);
});
});
sframeChan.on('Q_IS_PAD_STORED', function (data, cb) {
Cryptpad.getPadAttribute('title', function (err, data) {
cb (!err && typeof (data) === "string");
});
});
sframeChan.on('Q_IMPORT_MEDIATAG', function (obj, cb) {
var key = obj.key;
var channel = obj.channel;
var hash = Utils.Hash.getFileHashFromKeys({
version: 1,
channel: channel,
keys: {
fileKeyStr: key
}
});
var href = '/file/#' + hash;
var data = {
title: obj.name,
href: href,
channel: channel,
owners: obj.owners,
forceSave: true,
};
Cryptpad.setPadTitle(data, function (err) {
Cryptpad.setPadAttribute('fileType', obj.type, null, href);
cb(err);
});
});
sframeChan.on('Q_SETTINGS_SET_DISPLAY_NAME', function (newName, cb) {
Cryptpad.setDisplayName(newName, function (err) {
@@ -306,6 +443,7 @@ define([
Cryptpad.saveAsTemplate(Cryptget.put, data, cb);
});
// Messaging
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) {
Cryptpad.inviteFromUserlist(netfluxId, cb);
});
@@ -318,6 +456,7 @@ define([
sframeChan.event('EV_FRIEND_REQUEST', data);
});
// History
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
var crypto = Crypto.createEncryptor(secret.keys);
Cryptpad.getFullHistory({
@@ -325,17 +464,48 @@ define([
validateKey: secret.keys.validateKey
}, function (encryptedMsgs) {
cb(encryptedMsgs.map(function (msg) {
return crypto.decrypt(msg, true);
// The 3rd parameter "true" means we're going to skip signature validation.
// We don't need it since the message is already validated serverside by hk
return crypto.decrypt(msg, true, true);
}));
});
});
sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
var nSecret = secret;
if (cfg.isDrive) {
var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash();
if (hash) {
nSecret = Utils.Hash.getSecrets('drive', hash);
}
}
var channel = nSecret.channel;
var validate = nSecret.keys.validateKey;
var crypto = Crypto.createEncryptor(nSecret.keys);
Cryptpad.getHistoryRange({
channel: channel,
validateKey: validate,
lastKnownHash: data.lastKnownHash
}, function (data) {
cb({
isFull: data.isFull,
messages: data.messages.map(function (msg) {
// The 3rd parameter "true" means we're going to skip signature validation.
// We don't need it since the message is already validated serverside by hk
return crypto.decrypt(msg, true, true);
}),
lastKnownHash: data.lastKnownHash
});
});
});
// Store
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
var href;
if (readOnly && hashes.editHash) {
// If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) {
cb({
error: e,
@@ -349,6 +519,7 @@ define([
// If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.setPadAttribute(data.key, data.value, function (e) {
cb({error:e});
}, href);
@@ -373,6 +544,12 @@ define([
cb();
});
sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) {
Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) {
if (err) { return void cb({error: err}); }
cb(t);
});
});
// Present mode URL
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
@@ -429,6 +606,8 @@ define([
// File picker
var FP = {};
var initFilePicker = function (cfg) {
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!FP.$iframe) {
var config = {};
config.onFilePicked = function (data) {
@@ -447,7 +626,7 @@ define([
};
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config);
} else {
} else if (!cfg.hidden) {
FP.$iframe.show();
FP.picker.refresh(cfg);
}
@@ -469,9 +648,9 @@ define([
cb(templates.length > 0);
});
});
var getKey = function (href) {
var getKey = function (href, channel) {
var parsed = Utils.Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
return 'thumbnail-' + parsed.type + '-' + channel;
};
sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
Cryptpad.getSecureFilesList({
@@ -484,12 +663,13 @@ define([
var res = [];
nThen(function (waitFor) {
Object.keys(data).map(function (el) {
var k = getKey(data[el].href);
var k = getKey(data[el].href, data[el].channel);
Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) {
res.push({
id: el,
name: data[el].filename || data[el].title || '?',
thumbnail: thumb
thumbnail: thumb,
used: data[el].used || 0
});
}));
});
@@ -513,19 +693,6 @@ define([
}
});
sframeChan.on('Q_TAGS_GET', function (data, cb) {
Cryptpad.getPadTags(data, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_TAGS_SET', function (data) {
Cryptpad.resetTags(data.href, data.tags);
});
sframeChan.on('Q_PIN_GET_USAGE', function (data, cb) {
Cryptpad.isOverPinLimit(function (err, overLimit, data) {
cb({
@@ -546,6 +713,32 @@ define([
Cryptpad.removeOwnedChannel(channel, cb);
});
sframeChan.on('Q_GET_ALL_TAGS', function (data, cb) {
Cryptpad.listAllTags(function (err, tags) {
cb({
error: err,
tags: tags
});
});
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
var href = data.href || window.location.href;
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
});
sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
});
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.writeLoginBlock(data, cb);
});
sframeChan.on('Q_REMOVE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.removeLoginBlock(data, cb);
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
@@ -603,6 +796,24 @@ define([
});
}
// Chrome 68 on Mac contains a bug resulting in the page turning white after a few seconds
try {
if (navigator.platform.toUpperCase().indexOf('MAC') >= 0 &&
!localStorage.CryptPad_chrome68) {
var isChrome = !!window.chrome && !!window.chrome.webstore;
var getChromeVersion = function () {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
};
if (isChrome && getChromeVersion() === 68) {
sframeChan.whenReg('EV_CHROME_68', function () {
sframeChan.event("EV_CHROME_68");
localStorage.CryptPad_chrome68 = "1";
});
}
}
} catch (e) {}
// Join the netflux channel
@@ -630,7 +841,10 @@ define([
isNewHash: isNewHash,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) {
onConnect: function () {
var href = parsed.getUrl();
// Add friends requests handlers when we have the final href
Cryptpad.messaging.addHandlers(href);
if (window.location.hash && window.location.hash !== '#') {
/*window.location = parsed.getUrl({
present: parsed.hashData.present,
@@ -639,20 +853,36 @@ define([
return;
}
if (readOnly || cfg.noHash) { return; }
replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys));
replaceHash(Utils.Hash.getEditHashFromKeys(secret));
}
};
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k];
nThen(function (waitFor) {
if (isNewFile && cfg.owned && !window.location.hash) {
Cryptpad.getMetadata(waitFor(function (err, m) {
cpNfCfg.owners = [m.priv.edPublic];
}));
} else if (isNewFile && !cfg.useCreationScreen && window.location.hash) {
console.log("new file with hash in the address bar in an app without pcs and which requires owners");
sframeChan.onReady(function () {
sframeChan.query("EV_LOADING_ERROR", "DELETED");
});
waitFor.abort();
}
}).nThen(function () {
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k];
});
CpNfOuter.start(cpNfCfg);
});
CpNfOuter.start(cpNfCfg);
};
sframeChan.on('Q_CREATE_PAD', function (data, cb) {
if (!isNewFile || rtStarted) { return; }
// Create a new hash
var newHash = Utils.Hash.createRandomHash();
secret = Utils.Hash.getSecrets(parsed.type, newHash);
password = data.password;
var newHash = Utils.Hash.createRandomHash(parsed.type, password);
secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
// Update the hash in the address bar
var ohc = window.onhashchange;
@@ -664,7 +894,7 @@ define([
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret.channel, secret);
hashes = Utils.Hash.getHashes(secret);
readOnly = false;
updateMeta();
@@ -678,7 +908,7 @@ define([
nThen(function(waitFor) {
if (data.templateId) {
if (data.templateId === -1) {
Cryptpad.setInitialPath(['template']);
initialPathInDrive = ['template'];
return;
}
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
@@ -690,10 +920,11 @@ define([
// Pass rtConfig to useTemplate because Cryptput will create the file and
// we need to have the owners and expiration time in the first line on the
// server
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
Cryptpad.useTemplate(data.template, Cryptget, function () {
startRealtime();
cb();
}, rtConfig);
}, cryptputCfg);
return;
}
// Start realtime outside the iframe and callback
@@ -706,8 +937,8 @@ define([
Utils.Feedback.reportAppUsage();
if (!realtime) { return; }
if (isNewFile && cfg.useCreationScreen) { return; }
if (!realtime && !Test.testing) { return; }
if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; }
//if (isNewFile && Utils.LocalStore.isLoggedIn()
// && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }

View File

@@ -42,6 +42,9 @@ define([
exp.updateTitle = function (newTitle, cb) {
cb = cb || $.noop;
if (newTitle === exp.title) { return void cb(); }
if (newTitle === exp.defaultTitle) {
newTitle = "";
}
metadataMgr.updateTitle(newTitle);
titleUpdated = cb;
};
@@ -51,11 +54,16 @@ define([
if ($title) {
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
$title.find('input').val(md.title || md.defaultTitle);
$title.find('input').prop('placeholder', md.defaultTitle);
}
exp.defaultTitle = md.defaultTitle;
exp.title = md.title;
});
metadataMgr.onTitleChange(function (title) {
sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', title, function (err) {
metadataMgr.onTitleChange(function (title, defaultTitle) {
sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', {
title: title,
defaultTitle: defaultTitle
}, function (err) {
if (err === 'E_OVER_LIMIT') {
return void UI.alert(Messages.pinLimitNotPinned, null, true);
} else if (err) { return; }

View File

@@ -94,6 +94,7 @@ define([
funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen);
funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal);
funcs.onServerError = callWithCommon(UIElements.onServerError);
funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu);
// Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
@@ -112,22 +113,43 @@ define([
var origin = ctx.metadataMgr.getPrivateData().origin;
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
};
funcs.getMediatagFromHref = function (href) {
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
funcs.getMediatagFromHref = function (obj) {
var data = ctx.metadataMgr.getPrivateData();
var secret;
if (obj) {
secret = Hash.getSecrets('file', obj.hash, obj.password);
} else {
secret = Hash.getSecrets('file', data.availableHashes.fileHash, data.password);
}
if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Util.base64ToHex(secret.channel);
var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = secret.channel;
var origin = data.fileHost || data.origin;
var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + cryptKey + '">' +
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '">' +
'</media-tag>';
}
return;
};
funcs.getFileSize = function (href, cb) {
var channelId = Hash.hrefToHexChannelId(href);
funcs.importMediaTag = function ($mt) {
if (!$mt || !$mt.is('media-tag')) { return; }
var chanStr = $mt.attr('src');
var keyStr = $mt.attr('data-crypto-key');
var channel = chanStr.replace(/\/blob\/[0-9a-f]{2}\//i, '');
var key = keyStr.replace(/cryptpad:/i, '');
var metadata = $mt[0]._mediaObject._blob.metadata;
ctx.sframeChan.query('Q_IMPORT_MEDIATAG', {
channel: channel,
key: key,
name: metadata.name,
type: metadata.type,
owners: metadata.owners
}, function () {
UI.log(Messages.saved);
});
};
funcs.getFileSize = function (channelId, cb) {
funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) {
if (!data) { return void cb("No response"); }
if (data.error) { return void cb(data.error); }
@@ -170,6 +192,7 @@ define([
// Store
funcs.handleNewFile = function (waitFor) {
if (window.__CRYPTPAD_TEST__) { return; }
var priv = ctx.metadataMgr.getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
@@ -196,11 +219,18 @@ define([
ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned,
expire: cfg.expire,
password: cfg.password,
template: cfg.template,
templateId: cfg.templateId
}, cb);
};
funcs.isPadStored = function (cb) {
ctx.sframeChan.query("Q_IS_PAD_STORED", null, function (err, obj) {
cb (err || (obj && obj.error), obj);
});
};
funcs.sendAnonRpcMsg = function (msg, content, cb) {
ctx.sframeChan.query('Q_ANON_RPC_MESSAGE', {
msg: msg,
@@ -233,17 +263,20 @@ define([
});
};
funcs.getPadAttribute = function (key, cb) {
// href is optional here: if not provided, we use the href of the current tab
funcs.getPadAttribute = function (key, cb, href) {
ctx.sframeChan.query('Q_GET_PAD_ATTRIBUTE', {
key: key
key: key,
href: href
}, function (err, res) {
cb (err || res.error, res.data);
});
};
funcs.setPadAttribute = function (key, value, cb) {
funcs.setPadAttribute = function (key, value, cb, href) {
cb = cb || $.noop;
ctx.sframeChan.query('Q_SET_PAD_ATTRIBUTE', {
key: key,
href: href,
value: value
}, cb);
};
@@ -343,6 +376,27 @@ define([
window.open(bounceHref);
};
funcs.fixLinks = function (domElement) {
var origin = ctx.metadataMgr.getPrivateData().origin;
$(domElement).find('a[target="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
var href = $(this).attr('href');
var absolute = /^https?:\/\//i;
if (!absolute.test(href)) {
if (href.slice(0,1) !== '/') { href = '/' + href; }
href = origin + href;
}
funcs.openUnsafeURL(href);
});
$(domElement).find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
funcs.gotoURL($(this).attr('href'));
});
return $(domElement)[0];
};
funcs.whenRealtimeSyncs = evRealtimeSynced.reg;
var logoutHandlers = [];
@@ -397,19 +451,6 @@ define([
UI.addTooltips();
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
UI.errorLoadingScreen(Messages.onLogout, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
UI.confirm(confirmMsg, cb, null, true);
});
@@ -419,12 +460,63 @@ define([
UI.log(data.logText);
});
ctx.sframeChan.on("EV_PAD_PASSWORD", function () {
UIElements.displayPasswordPrompt(funcs);
});
ctx.sframeChan.on('EV_LOADING_INFO', function (data) {
UI.updateLoadingProgress(data, true);
});
ctx.sframeChan.on('EV_NEW_VERSION', function () {
var $err = $('<div>').append(Messages.newVersionError);
$err.find('a').click(function () {
funcs.gotoURL();
});
UI.findOKButton().click();
UI.errorLoadingScreen($err, true, true);
});
ctx.sframeChan.on('EV_AUTOSTORE_DISPLAY_POPUP', function (data) {
UIElements.displayStorePadPopup(funcs, data);
});
ctx.metadataMgr.onReady(waitFor());
}).nThen(function () {
try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback);
} catch (e) { Feedback.init(false); }
ctx.sframeChan.on('EV_LOADING_ERROR', function (err) {
if (err === 'DELETED') {
var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome;
UI.errorLoadingScreen(msg, false, function () {
funcs.gotoURL('/drive/');
});
}
});
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
var origin = ctx.metadataMgr.getPrivateData().origin;
var href = origin + "/login/";
var onLogoutMsg = Messages._getKey('onLogout', ['<a href="' + href + '" target="_blank">', '</a>']);
UI.errorLoadingScreen(onLogoutMsg, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('EV_CHROME_68', function () {
UI.alert(Messages.chrome68);
});
ctx.sframeChan.ready();
cb(funcs);
});

View File

@@ -74,6 +74,12 @@ define({
// Get the user's pin limit, usage and plan
'Q_PIN_GET_USAGE': true,
// Write/update the login block when the account password is changed
'Q_WRITE_LOGIN_BLOCK': true,
// Remove login blocks
'Q_REMOVE_LOGIN_BLOCK': true,
// Check the pin limit to determine if we can store the pad in the drive or if we should.
// display a warning
'Q_GET_PIN_LIMIT_STATUS': true,
@@ -84,6 +90,7 @@ define({
// Request the full history from the server when the users clicks on the history button.
// Callback is called when the FULL_HISTORY_END message is received in the outside.
'Q_GET_FULL_HISTORY': true,
'Q_GET_HISTORY_RANGE': true,
// When a (full) history message is received from the server.
'EV_RT_HIST_MESSAGE': true,
@@ -107,6 +114,10 @@ define({
'Q_GET_PAD_ATTRIBUTE': true,
'Q_SET_PAD_ATTRIBUTE': true,
// Check if a pad is only in a shared folder or (also) in the main drive.
// This allows us to change the behavior of some buttons (trash icon...)
'Q_IS_ONLY_IN_SHARED_FOLDER': true,
// Open/close the File picker (sent from the iframe to the outside)
'EV_FILE_PICKER_OPEN': true,
'EV_FILE_PICKER_CLOSE': true,
@@ -165,10 +176,6 @@ define({
// Put one entry in the parent sessionStorage
'Q_SESSIONSTORAGE_PUT': true,
// Set and get the tags using the tag prompt button
'Q_TAGS_GET': true,
'EV_TAGS_SET': true,
// Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads
// in the drive at registration.
'Q_MERGE_ANON_DRIVE': true,
@@ -200,10 +207,10 @@ define({
// Anonymous users can empty their drive and remove FS_hash from localStorage
'EV_BURN_ANON_DRIVE': true,
// Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true,
'Q_DRIVE_RESTORE': 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
@@ -218,6 +225,8 @@ define({
// Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true,
// Reload on new version
'EV_NEW_VERSION': true,
// Pad creation screen: create a pad with the selected attributes (owned, expire)
'Q_CREATE_PAD': true,
@@ -230,4 +239,32 @@ define({
// OnlyOffice: save a new version
'Q_OO_SAVE': true,
// Ask for the pad password when a pad is protected
'EV_PAD_PASSWORD': true,
'Q_PAD_PASSWORD_VALUE': true,
// Change pad password
'Q_PAD_PASSWORD_CHANGE': true,
// Migrate drive to owned drive
'Q_CHANGE_USER_PASSWORD': true,
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
// Critical error outside the iframe during loading screen
'EV_LOADING_ERROR': true,
// Chrome 68 bug...
'EV_CHROME_68': true,
// Get all existing tags
'Q_GET_ALL_TAGS': true,
// Store pads in the drive
'EV_AUTOSTORE_DISPLAY_POPUP': true,
'Q_AUTOSTORE_STORE': true,
'Q_IS_PAD_STORED': true,
// Import mediatag from a pad
'Q_IMPORT_MEDIATAG': true
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
www/common/tippy/tippy.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -420,7 +420,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-share-alt cp-toolbar-share-button',
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createShareModal({
@@ -449,7 +449,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-share-alt cp-toolbar-share-button',
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createFileShareModal({
@@ -576,9 +576,7 @@ define([
if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData();
var o = pd.origin;
var hashes = pd.availableHashes;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
var cid = pd.channel;
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) {

View File

@@ -13,10 +13,10 @@ define([
var UNSORTED = module.UNSORTED = "unsorted";
var TRASH = module.TRASH = "trash";
var TEMPLATE = module.TEMPLATE = "template";
var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders";
module.init = function (files, config) {
var exp = {};
var pinPads = config.pinPads;
var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
@@ -28,23 +28,35 @@ define([
exp.UNSORTED = UNSORTED;
exp.TRASH = TRASH;
exp.TEMPLATE = TEMPLATE;
exp.SHARED_FOLDERS = SHARED_FOLDERS;
var sharedFolder = exp.sharedFolder = config.sharedFolder;
exp.id = config.id;
// Logging
var logging = function () {
console.log.apply(console, arguments);
};
var log = config.log || logging;
var log = exp.log = config.log || logging;
var logError = config.logError || logging;
var debug = exp.debug = config.debug || logging;
exp.fixFiles = function () {}; // Overriden by OuterFO
var error = exp.error = function() {
exp.fixFiles();
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "fixFiles",
data: {}
}, function () {});
} else if (typeof (exp.fixFiles) === "function") {
exp.fixFiles();
}
console.error.apply(console, arguments);
exp.fixFiles();
};
// TODO: workgroup
var workgroup = config.workgroup;
if (pinPads) {
if (config.outer) {
// Extend "exp" with methods used only outside of the iframe (requires access to store)
OuterFO.init(config, exp, files);
}
@@ -69,7 +81,12 @@ define([
var compareFiles = function (fileA, fileB) { return fileA === fileB; };
var isSharedFolder = exp.isSharedFolder = function (element) {
if (sharedFolder) { return false; } // No recursive shared folders
return Boolean(files[SHARED_FOLDERS] && files[SHARED_FOLDERS][element]);
};
var isFile = exp.isFile = function (element, allowStr) {
if (isSharedFolder(element)) { return false; }
return typeof(element) === "number" ||
((typeof(files[OLD_FILES_DATA]) !== "undefined" || allowStr)
&& typeof(element) === "string");
@@ -78,15 +95,11 @@ define([
exp.isReadOnlyFile = function (element) {
if (!isFile(element)) { return false; }
var data = exp.getFileData(element);
var parsed = Hash.parsePadUrl(data.href);
if (!parsed) { return false; }
var pHash = parsed.hashData;
if (!pHash || pHash.type !== "pad") { return; }
return pHash && pHash.mode === 'view';
return Boolean(data.roHref && !data.href);
};
var isFolder = exp.isFolder = function (element) {
return typeof(element) === "object";
return typeof(element) === "object" || isSharedFolder(element);
};
exp.isFolderEmpty = function (element) {
if (!isFolder(element)) { return false; }
@@ -137,9 +150,11 @@ define([
// Data from filesData
var getTitle = exp.getTitle = function (file, type) {
if (workgroup) { debug("No titles in workgroups"); return; }
if (isSharedFolder(file)) {
return '??';
}
var data = getFileData(file);
if (!file || !data || !data.href) {
if (!file || !data || !(data.href || data.roHref)) {
error("getTitle called with a non-existing file id: ", file, data);
return;
}
@@ -209,14 +224,16 @@ define([
// GET FILES
var getFilesRecursively = function (root, arr) {
var getFilesRecursively = exp.getFilesRecursively = function (root, arr) {
arr = arr || [];
for (var e in root) {
if (isFile(root[e])) {
if (isFile(root[e]) || isSharedFolder(root[e])) {
if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); }
} else {
getFilesRecursively(root[e], arr);
}
}
return arr;
};
var _getFiles = {};
_getFiles['array'] = function (cat) {
@@ -228,6 +245,7 @@ define([
});
_getFiles['hrefArray'] = function () {
var ret = [];
if (sharedFolder) { return ret; }
getHrefArray().forEach(function (c) {
ret = ret.concat(_getFiles[c]());
});
@@ -242,7 +260,7 @@ define([
var root = files[TRASH];
var ret = [];
var addFiles = function (el) {
if (isFile(el.element)) {
if (isFile(el.element) || isSharedFolder(el.element)) {
if(ret.indexOf(el.element) === -1) { ret.push(el.element); }
} else {
getFilesRecursively(el.element, ret);
@@ -272,10 +290,15 @@ define([
if (!files[FILES_DATA]) { return ret; }
return Object.keys(files[FILES_DATA]).map(Number);
};
_getFiles[SHARED_FOLDERS] = function () {
var ret = [];
if (!files[SHARED_FOLDERS]) { return ret; }
return Object.keys(files[SHARED_FOLDERS]).map(Number);
};
var getFiles = exp.getFiles = function (categories) {
var ret = [];
if (!categories || !categories.length) {
categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA];
categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA, SHARED_FOLDERS];
}
categories.forEach(function (c) {
if (typeof _getFiles[c] === "function") {
@@ -288,11 +311,11 @@ define([
var getIdFromHref = exp.getIdFromHref = function (href) {
var result;
getFiles([FILES_DATA]).some(function (id) {
if (files[FILES_DATA][id].href === href) {
if (files[FILES_DATA][id].href === href ||
files[FILES_DATA][id].roHref === href) {
result = id;
return true;
}
return;
});
return result;
};
@@ -308,7 +331,7 @@ define([
}
};
if (isFile(root)) {
if (isFile(root) || isSharedFolder(root)) {
if (compareFiles(file, root)) {
if (paths.indexOf(path) === -1) {
paths.push(path);
@@ -328,6 +351,7 @@ define([
return _findFileInRoot([ROOT], file);
};
var _findFileInHrefArray = function (rootName, file) {
if (sharedFolder) { return []; }
if (!files[rootName]) { return []; }
var unsorted = files[rootName].slice();
var ret = [];
@@ -338,6 +362,7 @@ define([
return ret;
};
var _findFileInTrash = function (path, file) {
if (sharedFolder) { return []; }
var root = find(path);
var paths = [];
var addPaths = function (p) {
@@ -384,11 +409,9 @@ define([
// 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;
return channels.indexOf(data.channel) !== -1;
});
};
@@ -556,29 +579,29 @@ define([
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
exp.delete = function (paths, cb, nocheck, isOwnPadRemoved) {
exp.delete = function (paths, cb, nocheck) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "delete",
data: {
paths: paths,
nocheck: nocheck,
isOwnPadRemoved: isOwnPadRemoved
}
}, cb);
}
exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved);
if (typeof cb === "function") { cb(); }
cb = cb || function () {};
exp.deleteMultiplePermanently(paths, nocheck, cb);
//if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {
cb = cb || function () {};
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "emptyTrash"
}, cb);
}
files[TRASH] = {};
exp.checkDeletedFiles();
if(cb) { cb(); }
exp.checkDeletedFiles(cb);
};
// RENAME
@@ -592,7 +615,6 @@ define([
}
}, cb);
}
console.log(path, newName);
if (path.length <= 1) {
logError('Renaming `root` is forbidden');
return;
@@ -601,7 +623,7 @@ define([
var element = find(path);
// Folders
if (isFolder(element)) {
if (isFolder(element) && !isSharedFolder(element)) {
var parentPath = path.slice();
var oldName = parentPath.pop();
if (!newName || !newName.trim() || oldName === newName) { return; }
@@ -616,8 +638,13 @@ define([
return;
}
// Files
var data = files[FILES_DATA][element];
// Files or Shared folder
var data;
if (isSharedFolder(element)) {
data = files[SHARED_FOLDERS][element];
} else {
data = files[FILES_DATA][element];
}
if (!data) { return; }
if (!newName || newName.trim() === "") {
delete data.filename;
@@ -629,6 +656,21 @@ define([
if (typeof cb === "function") { cb(); }
};
// Tags
exp.getTagsList = function () {
var tags = {};
var data;
var pushTag = function (tag) {
tags[tag] = tags[tag] ? ++tags[tag] : 1;
};
for (var id in files[FILES_DATA]) {
data = files[FILES_DATA][id];
if (!data.tags || !Array.isArray(data.tags)) { continue; }
data.tags.forEach(pushTag);
}
return tags;
};
return exp;
};
return module;

View File

@@ -1,20 +1,14 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/avatar.less';
.toolbar_main(
@bg-color: @colortheme_friends-bg,
@warn-color: @colortheme_friends-warn,
@color: @colortheme_friends-color
);
.fileupload_main();
.alertify_main();
@import (reference) '../../customize/src/less2/include/avatar.less';
@import (reference) '../../customize/src/less2/include/framework.less';
// body
&.cp-app-contacts {
.framework_min_main(
@bg-color: @colortheme_friends-bg,
@warn-color: @colortheme_friends-warn,
@color: @colortheme_friends-color
);
@keyframes example {
0% {
background: rgba(0,0,0,0.1);

View File

@@ -11,8 +11,8 @@ define([
'/common/common-interface.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/contacts/app-contacts.less',
], function (
$,
Crypto,

View File

@@ -247,7 +247,7 @@ define([
// insert a newline if they're holding either
var val = this.value;
var start = this.selectionState;
var start = this.selectionStart;
var end = this.selectionEnd;
if (![start,end].some(function (x) {

View File

@@ -1,18 +1,11 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
.toolbar_main();
.fileupload_main();
.alertify_main();
.tokenfield_main();
@import (reference) '../../customize/src/less2/include/tokenfield.less';
@import (reference) '../../customize/src/less2/include/framework.less';
// body
&.cp-app-debug {
.tokenfield_main();
.framework_min_main();
display: flex;
flex-flow: column;
height: 100%;

View File

@@ -15,8 +15,8 @@ define([
'/bower_components/secure-fabric.js/dist/fabric.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/debug/app-debug.less',
], function (
$,
Crypto,

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }
.cp-app-drive-context { display: none; }
.cp-contextmenu { display: none; }
</style>
</head>
<body class="cp-app-drive">

File diff suppressed because it is too large Load Diff

View File

@@ -37,9 +37,25 @@ define([
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils, cb) {
var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() ||
Utils.LocalStore.getFSHash();
cb(null, Utils.Hash.getSecrets('drive', hash));
var hash = window.location.hash.slice(1);
var secret = Utils.Hash.getSecrets('drive', hash);
if (hash) {
// Add a shared folder!
// XXX password?
Cryptpad.addSharedFolder(secret, function (id) {
window.CryptPad_newSharedFolder = id;
// Update the hash in the address bar
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = "";
window.onhashchange = ohc;
ohc({reset:true});
cb(null, secret);
});
return;
}
// No password for drive
cb(null, secret);
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('EV_BURN_ANON_DRIVE', function () {
@@ -51,7 +67,16 @@ define([
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
Cryptpad.userObjectCommand(data, cb);
});
sframeChan.on('Q_DRIVE_RESTORE', function (data, cb) {
Cryptpad.restoreDrive(data, cb);
});
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
if (data && data.sharedFolder) {
Cryptpad.getSharedFolder(data.sharedFolder, function (obj) {
cb(obj);
});
return;
}
Cryptpad.getUserObject(function (obj) {
cb(obj);
});
@@ -81,8 +106,10 @@ define([
SFCommonO.start({
getSecrets: getSecrets,
noHash: true,
noRealtime: true,
driveEvents: true,
addRpc: addRpc
addRpc: addRpc,
isDrive: true,
});
});
});

View File

@@ -70,7 +70,7 @@ define([
module.test = function (assert) {
var config = {
pinPads: Cryptpad.pinPads,
outer: true,
workgroup: false,
testMode: true,
loggedIn: false
@@ -237,7 +237,8 @@ define([
&& typeof files.template[0] === "number"
&& typeof files.filesData[files.template[0]] === "object"
&& !files.filesData[files.template[0]].filename
&& files.filesData[files.template[0]].href === href3
&& !files.filesData[files.template[0]].href
&& files.filesData[files.template[0]].roHref === href3
&& typeof fileId2 === "number"
&& typeof files.filesData[fileId2] === "object"
&& files.filesData[fileId2].filename === "Trash"
@@ -392,11 +393,6 @@ define([
console.log("DRIVE operations: rename");
return cb();
}
fo.replace(href1, href2);
if (fo.getFileData(id1).href !== href2) {
console.log("DRIVE operations: replace");
return cb();
}
cb(true);
}, "DRIVE operations");

View File

@@ -1,160 +1,152 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (reference) '../../customize/src/less2/include/tokenfield.less';
@import (reference) '../../customize/src/less2/include/framework.less';
.toolbar_main(
@bg-color: @colortheme_file-bg,
@warn-color: @colortheme_file-warn,
@color: @colortheme_file-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();
&.cp-app-file {
@button-border: 2px;
.framework_min_main(
@bg-color: @colortheme_file-bg,
@warn-color: @colortheme_file-warn,
@color: @colortheme_file-color
);
.tokenfield_main();
/*html, body {
margin: 0px;
height: 100%;
}*/
@button-border: 2px;
// body
display: flex;
flex-flow: column;
#cp-app-file-content {
flex: 1;
// body
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
min-height: 0;
}
#cp-app-file-content.ready {
//background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
#cp-app-file-upfile, #cp-app-file-dlfile {
display: block;
height: 100%;
width: 100%;
border: @button-border solid black;
}
.cp-app-file-input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
media-tag {
img {
max-width: 100%;
max-height: ~"calc(100vh - 96px)";
}
}
#cp-app-file-upload-form, #cp-app-file-download-form {
padding: 0px;
margin: 0px;
position: relative;
width: 50vh;
height: 50vh;
display: block;
margin: 50px auto;
max-width: 80vw;
label {
line-height: ~"calc(50vh - 20px)";
text-align: center;
position: relative;
padding: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 50vh;
box-sizing: border-box;
}
}
#cp-app-file-download-form {
label {
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
word-wrap: break-word;
span {
width: 50vh;
max-width: 80vw;
text-align: center;
line-height: 1.5em;
}
}
}
.cp-app-file-hovering {
background-color: rgba(255, 0, 115, 0.5) !important;
}
.cp-app-file-block {
display: block;
}
.cp-app-file-hidden {
display: none;
}
.cp-app-file-input + label {
//border: 2px solid black;
//background-color: rgba(50, 50, 50, .10);
display: block;
}
.cp-app-file-input:focus + label,
.cp-app-file-input + label:hover {
//background-color: rgba(50, 50, 50, 0.30);
}
#cp-app-file-dlprogress {
position: absolute;
top: 0;
left: 0;
height: 100%;
transition: width 200ms;
width: 0%;
max-width: 100%;
max-height: 100%;
background-color: rgba(255, 0, 115, 0.75);
z-index: 10000;
display: block;
}
#cp-app-file-download-view {
flex: 1;
display: flex;
min-height: 0;
align-items: center;
justify-content: center;
flex-flow: column;
media-tag {
#cp-app-file-content {
flex: 1;
min-height: 0;
max-width: 100vw;
display: flex;
align-items: center;
justify-content: center;
&> * {
max-height: 100%;
align-items: center;
flex-flow: column;
min-height: 0;
}
#cp-app-file-content.ready {
//background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
#cp-app-file-upfile, #cp-app-file-dlfile {
display: block;
height: 100%;
width: 100%;
border: @button-border solid black;
}
.cp-app-file-input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
media-tag {
img {
max-width: 100%;
max-height: ~"calc(100vh - 96px)";
}
}
}
#cp-app-file-upload-form, #cp-app-file-download-form {
padding: 0px;
margin: 0px;
position: relative;
width: 50vh;
height: 50vh;
display: block;
margin: 50px auto;
max-width: 80vw;
label {
line-height: ~"calc(50vh - 20px)";
text-align: center;
position: relative;
padding: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 50vh;
box-sizing: border-box;
}
}
#cp-app-file-download-form {
label {
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
word-wrap: break-word;
span {
width: 50vh;
max-width: 80vw;
text-align: center;
line-height: 1.5em;
}
}
}
.cp-app-file-hovering {
background-color: rgba(255, 0, 115, 0.5) !important;
}
.cp-app-file-block {
display: block;
}
.cp-app-file-hidden {
display: none;
}
.cp-app-file-input + label {
//border: 2px solid black;
//background-color: rgba(50, 50, 50, .10);
display: block;
}
.cp-app-file-input:focus + label,
.cp-app-file-input + label:hover {
//background-color: rgba(50, 50, 50, 0.30);
}
#cp-app-file-dlprogress {
position: absolute;
top: 0;
left: 0;
height: 100%;
transition: width 200ms;
width: 0%;
max-width: 100%;
max-height: 100%;
background-color: rgba(255, 0, 115, 0.75);
z-index: 10000;
display: block;
}
#cp-app-file-download-view {
flex: 1;
display: flex;
min-height: 0;
align-items: center;
justify-content: center;
flex-flow: column;
media-tag {
flex: 1;
min-height: 0;
max-width: 100vw;
display: flex;
align-items: center;
justify-content: center;
&> * {
max-height: 100%;
max-width: 100%;
}
}
}
}

View File

@@ -2,7 +2,7 @@ define([
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
var Nacl = window.nacl;
var PARANOIA = true;
//var PARANOIA = true;
var plainChunkLength = 128 * 1024;
var cypherChunkLength = 131088;
@@ -36,24 +36,11 @@ define([
var increment = function (N) {
var l = N.length;
while (l-- > 1) {
if (PARANOIA) {
if (typeof(N[l]) !== 'number') {
throw new Error('E_UNSAFE_TYPE');
}
if (N[l] > 255) {
throw new Error('E_OUT_OF_BOUNDS');
}
}
/* jshint probably suspects this is unsafe because we lack types
/* our linter suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
N[l] = 0;
// you don't need to worry about this running out.
// you'd need a REAAAALLY big file
if (l === 0) {
throw new Error('E_NONCE_TOO_LARGE');
}
}
};
@@ -78,6 +65,7 @@ define([
xhr.setRequestHeader('Range', 'bytes=0-1');
xhr.responseType = 'arraybuffer';
xhr.onerror= function () { return CB('XHR_ERROR'); };
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res = new Uint8Array(xhr.response);
@@ -160,19 +148,21 @@ define([
}
var takeChunk = function (cb) {
var start = i * cypherChunkLength + 2 + metadataLength;
var end = start + cypherChunkLength;
i++;
var box = new Uint8Array(u8.subarray(start, end));
setTimeout(function () {
var start = i * cypherChunkLength + 2 + metadataLength;
var end = start + cypherChunkLength;
i++;
var box = new Uint8Array(u8.subarray(start, end));
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
increment(nonce);
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
increment(nonce);
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
_progress(end);
cb(void 0, plaintext);
_progress(end);
cb(void 0, plaintext);
});
};
var chunks = [];
@@ -219,7 +209,7 @@ define([
var state = 0;
var next = function (cb) {
if (state === 2) { return void cb(); }
if (state === 2) { return void setTimeout(cb); }
var start;
var end;
@@ -238,7 +228,9 @@ define([
.concat(slice(box)));
state++;
return void cb(void 0, prefixed);
return void setTimeout(function () {
cb(void 0, prefixed);
});
}
// encrypt the rest of the file...
@@ -253,7 +245,9 @@ define([
// regular data is done
if (i * plainChunkLength >= u8.length) { state = 2; }
return void cb(void 0, box);
setTimeout(function () {
cb(void 0, box);
});
};
return next;

View File

@@ -16,8 +16,8 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/file/app-file.less',
], function (
$,
@@ -37,6 +37,9 @@ define([
var Nacl = window.nacl;
var APP = window.APP = {};
MediaTag.setDefaultConfig('download', {
text: Messages.download_mt_button
});
var andThen = function (common) {
var $appContainer = $('#cp-app-file-content');
@@ -54,16 +57,14 @@ define([
var uploadMode = false;
var secret;
var hexFileName;
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
if (!priv.filehash) {
uploadMode = true;
} else {
secret = Hash.getSecrets('file', priv.filehash);
secret = Hash.getSecrets('file', priv.filehash, priv.password);
if (!secret.keys) { throw new Error("You need a hash"); }
hexFileName = Util.base64ToHex(secret.channel);
}
var Title = common.createTitle({});
@@ -86,28 +87,41 @@ define([
toolbar.$rightside.html('');
if (!uploadMode) {
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
var key = secret.keys && secret.keys.cryptKey;
var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') {
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable);
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () {
common.gotoURL('/file/');
});
}
return void console.error(e);
}
// Add pad attributes when the file is saved in the drive
Title.onTitleChange(function () {
var owners = metadata.owners;
if (owners) {
common.setPadAttribute('owners', owners);
}
common.setPadAttribute('fileType', metadata.type);
});
// Save to the drive or update the acces time
var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle);
toolbar.addElement(['pageTitle'], {pageTitle: title});
toolbar.$rightside.append(common.createButton('forget', true));
toolbar.$rightside.append(common.createButton('properties', true));
if (common.isLoggedIn()) {
toolbar.$rightside.append(common.createButton('hashtag', true));
}
common.setPadAttribute('fileType', metadata.type);
var displayFile = function (ev, sizeMb, CB) {
var called_back;
var cb = function (e) {
@@ -117,38 +131,21 @@ define([
};
var $mt = $dlview.find('media-tag');
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Util.base64ToHex(secret.channel);
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('src', src);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false;
$(window.document).on('decryption', function (e) {
/* FIXME
we're listening for decryption events and assuming that only
the main media-tag exists. In practice there is also your avatar
and there could be other things in the future, so we should
figure out a generic way target media-tag decryption events.
*/
var decrypted = e.originalEvent;
if (decrypted.callback) {
decrypted.callback();
}
MediaTag($mt[0]).on('complete', function (decrypted) {
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
$dlButton.addClass('btn btn-success');
var text = Messages.download_mt_button + '<br>';
text += '<b>' + Util.fixHTML(title) + '</b><br>';
text += '<em>' + Messages._getKey('formattedMB', [sizeMb]) + '</em>';
$dlButton.html(text);
if (!rightsideDisplayed) {
toolbar.$rightside
.append(common.createButton('export', true, {}, function () {
saveAs(decrypted.blob, decrypted.metadata.name);
saveAs(decrypted.content, decrypted.metadata.name);
}));
rightsideDisplayed = true;
}
@@ -171,42 +168,12 @@ define([
} else {
cb();
}
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
//UI.alert(error.message);
cb(error.message);
})
.on('decryptionProgress', function (e) {
var progress = e.originalEvent;
var p = progress.percent +'%';
}).on('progress', function (data) {
var p = data.progress +'%';
$progress.width(p);
}).on('error', function (err) {
console.error(err);
});
/**
* 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) {
@@ -233,8 +200,7 @@ define([
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick);
};
var href = priv.origin + priv.pathname + priv.filehash;
common.getFileSize(href, function (e, data) {
common.getFileSize(hexFileName, function (e, data) {
if (e) {
return void UI.errorLoadingScreen(e);
}
@@ -263,7 +229,7 @@ define([
dropArea: $form,
hoverArea: $label,
body: $body,
keepTable: true // Don't fadeOut the tbale with the uploaded files
keepTable: true // Don't fadeOut the table with the uploaded files
};
var FM = common.createFileManager(fmConfig);

View File

@@ -1,76 +1,85 @@
@import (once) '../../customize/src/less2/include/colortheme-all.less';
@import (once) '../../customize/src/less2/include/modal.less';
@import (once) '../../customize/src/less2/include/icon-colors.less';
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (reference) '../../customize/src/less2/include/colortheme-all.less';
@import (reference) '../../customize/src/less2/include/modal.less';
@import (reference) '../../customize/src/less2/include/icon-colors.less';
@import (reference) '../../customize/src/less2/include/fileupload.less';
@import (reference) '../../customize/src/less2/include/alertify.less';
@import (reference) '../../customize/src/less2/include/tippy.less';
@import (reference) '../../customize/src/less2/include/checkmark.less';
@import (reference) '../../customize/src/less2/include/password-input.less';
.iconColors_main();
.fileupload_main();
.alertify_main();
&.cp-app-filepicker {
.iconColors_main();
.fileupload_main();
.alertify_main();
.tippy_main();
.checkmark_main(20px);
.password_main();
.modal_main();
#cp-filepicker-dialog {
display: none;
.cp-modal {
.cp-filepicker-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
overflow-y: auto;
}
.cp-filepicker-content-element {
@darker: darken(@colortheme_modal-fg, 30%);
width: 125px;
//min-width: 200px;
//height: 1em;
padding: 10px;
margin: 5px;
display: inline-flex;
flex-flow: column;
box-sizing: content-box;
text-align: left;
line-height: 1em;
cursor: pointer;
background-color: #111;
color: @darker;
transition: all 0.1s;
&:hover {
color: @colortheme_modal-fg;
#cp-filepicker-dialog {
display: none;
.cp-modal {
.cp-filepicker-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
overflow-y: auto;
}
align-items: center;
.cp-filepicker-content-element {
@darker: darken(@colortheme_modal-fg, 30%);
img {
max-width: 100px;
max-height: 100px;
background: #fff;
}
width: 125px;
//min-width: 200px;
//height: 1em;
padding: 10px;
margin: 5px;
.cp-filepicker-content-element-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 20px;
line-height: 20px;
margin-top: 5px;
max-width: 100%;
}
.fa {
display: inline-flex;
flex-flow: column;
box-sizing: content-box;
text-align: left;
line-height: 1em;
cursor: pointer;
width: 100px;
height: 100px;
font-size: 70px;
text-align: center;
line-height: 100px;
background-color: @colortheme_modal-bg;
box-shadow: 2px 2px 5px #000;
color: @darker;
transition: all 0.1s;
&:hover {
color: @colortheme_modal-fg;
}
align-items: center;
img {
max-width: 100px;
max-height: 100px;
background: #fff;
}
.cp-filepicker-content-element-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 20px;
line-height: 20px;
margin-top: 5px;
max-width: 100%;
}
.fa {
cursor: pointer;
width: 100px;
height: 100px;
font-size: 70px;
text-align: center;
line-height: 100px;
}
}
}
}
}
}

View File

@@ -11,8 +11,8 @@ define([
'/customize/messages.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/filepicker/app-filepicker.less',
], function (
$,
Crypto,
@@ -40,13 +40,14 @@ define([
var parsed = Hash.parsePadUrl(data.url);
hideFileDialog();
if (parsed.type === 'file') {
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
sframeChan.event("EV_FILE_PICKED", {
type: parsed.type,
src: src,
name: data.name,
key: parsed.hashData.key
key: key
});
return;
}
@@ -68,8 +69,8 @@ define([
APP.FM = common.createFileManager(fmConfig);
// Create file picker
var onSelect = function (url, name) {
onFilePicked({url: url, name: name});
var onSelect = function (url, name, password) {
onFilePicked({url: url, name: name, password: password});
};
var data = {
FM: APP.FM
@@ -134,11 +135,13 @@ define([
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span);
$span.click(function () {
if (typeof onSelect === "function") { onSelect(data.href, name); }
if (typeof onSelect === "function") {
onSelect(data.href, name, data.password);
}
});
// Add thumbnail if it exists
common.displayThumbnail(data.href, $span);
common.displayThumbnail(data.href, data.channel, data.password, $span);
});
$input.focus();
};

174
www/kanban/app-kanban.less Normal file
View File

@@ -0,0 +1,174 @@
@import (reference) "../../customize/src/less2/include/browser.less";
@import (reference) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/tools.less";
// body
&.cp-app-kanban {
.framework_main(
@bg-color: @colortheme_kanban-bg,
@warn-color: @colortheme_kanban-warn,
@color: @colortheme_kanban-color
);
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
#cp-app-kanban-container {
flex: 1;
display: flex;
flex-flow: column;
}
#cp-app-kanban-editor {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#cp-app-kanban-content {
flex: 1;
overflow-y: auto;
display: flex;
flex-flow: column;
.kanban-container-outer {
flex: 1;
display: flex;
align-items: center;
min-height: -webkit-min-content;
min-height: min-content;
.kanban-container {
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
}
.kanban-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 5px 10px 10px;
}
.kanban-board {
header {
display: flex;
align-items: center;
padding: 13px 10px;
.kanban-title-board {
flex: 1;
margin-right: 10px;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#kanban-edit {
color: black;
font-weight: bold;
}
}
}
#kanban-edit {
width: 100%;
background: transparent;
border: 1px solid rgba(0,0,0,0.3);
}
@button-size: 50px;
#kanban-addboard {
margin: 30px;
border: 1px solid;
width: @button-size;
height: @button-size;
line-height: @button-size;
text-align: center;
background: @colortheme_kanban-bg;
align-self: flex-start;
font-size: 30px;
cursor: pointer;
.tools_unselectable();
}
.kanban-remove-item {
padding: 0 0.5em;
visibility: hidden;
}
.kanban-item:hover {
.kanban-remove-item {
visibility: visible;
}
}
.kanban-additem {
float: right;
background: #EEE;
padding: 5px .5rem 4px;
line-height: 1;
margin-bottom: 5px;
margin-right: 5px;
&:hover {
background: transparent;
}
}
.kanban-header-yellow {
background: #FC3;
}
.kanban-header-orange {
background: #F91;
}
.kanban-header-blue {
background: #0AC;
}
.kanban-header-red {
background: #E43;
}
.kanban-header-green {
background: #8C4;
}
.kanban-header-purple {
background: #c851ff;
}
.kanban-header-cyan {
background: #00ffff;
}
.kanban-header-lightgreen {
background: #c3ff5b;
}
.kanban-header-lightblue {
background: #adeeff;
}
@media (max-width: @browser_media-medium-screen) {
#cp-app-kanban-container {
flex: 1;
max-width: 100%;
resize: none;
}
}
&.cp-app-readonly {
.kanban-item, .kanban-title-board {
cursor: default !important;
.tools_unselectable();
}
.kanban-title-button, #kanban-addboard, .kanban-remove-item, .kanban-additem {
display: none !important;
}
}
}
}

37
www/kanban/index.html Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html,
body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position: fixed;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
width: 100%;
height: 100%;
border: none;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<iframe id="sbox-iframe">
</iframe>
</body>
</html>

23
www/kanban/inner.html Normal file
View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<script async data-bootload="/kanban/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden {
display: none;
}
</style>
</head>
<body class="cp-app-kanban">
<div id="cme_toolbox" class="cp-toolbar-container"></div>
<div id="cp-app-kanban-editor">
<div id="cp-app-kanban-container">
<div id="cp-app-kanban-content"></div>
</div>
</div>
</body>
</html>

418
www/kanban/inner.js Normal file
View File

@@ -0,0 +1,418 @@
define([
'jquery',
'json.sortify',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/sframe-app-framework.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/modes.js',
'/customize/messages.js',
'/kanban/jkanban.js',
'css!/kanban/jkanban.css',
'less!/kanban/app-kanban.less'
], function (
$,
Sortify,
nThen,
SFCommon,
Framework,
Util,
Hash,
UI,
Modes,
Messages)
{
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue'];
var addRemoveItemButton = function (framework, kanban) {
if (!kanban) { return; }
if (framework.isReadOnly() || framework.isLocked()) { return; }
var $container = $(kanban.element);
$container.find('.kanban-remove-item').remove();
$container.find('.kanban-board .kanban-item').each(function (i, el) {
var pos = kanban.findElementPosition(el);
var board = kanban.options.boards.find(function (b) {
return b.id === $(el.parentNode.parentNode).attr('data-id');
});
$('<button>', {
'class': 'kanban-remove-item btn btn-default fa fa-times',
title: Messages.kanban_removeItem
}).click(function (e) {
e.stopPropagation();
UI.confirm(Messages.kanban_removeItemConfirm, function (yes) {
if (!yes) { return; }
board.item.splice(pos, 1);
$(el).remove();
kanban.onChange();
});
}).appendTo($(el));
});
};
// Kanban code
var initKanban = function (framework, boards) {
var defaultBoards = [{
"id": "todo",
"title": Messages.kanban_todo,
"color": "blue",
"item": [{
"title": Messages._getKey('kanban_item', [1])
}, {
"title": Messages._getKey('kanban_item', [2])
}]
}, {
"id": "working",
"title": Messages.kanban_working,
"color": "orange",
"item": [{
"title": Messages._getKey('kanban_item', [3])
}, {
"title": Messages._getKey('kanban_item', [4])
}]
}, {
"id": "done",
"title": Messages.kanban_done,
"color": "green",
"item": [{
"title": Messages._getKey('kanban_item', [5])
}, {
"title": Messages._getKey('kanban_item', [6])
}]
}];
if (!boards) {
verbose("Initializing with default boards content");
boards = defaultBoards;
} else {
verbose("Initializing with boards content " + boards);
}
// Remove any existing elements
$(".kanban-container-outer").remove();
var getInput = function () {
return $('<input>', {
'type': 'text',
'id': 'kanban-edit',
'size': '30'
}).click(function (e) { e.stopPropagation(); });
};
var kanban = new window.jKanban({
element: '#cp-app-kanban-content',
gutter: '15px',
widthBoard: '300px',
buttonContent: '❌',
colors: COLORS,
readOnly: framework.isReadOnly(),
onChange: function () {
verbose("Board object has changed");
framework.localChange();
if (kanban) {
addRemoveItemButton(framework, kanban);
}
},
click: function (el) {
if (framework.isReadOnly() || framework.isLocked()) { return; }
if (kanban.inEditMode) {
$(el).focus();
verbose("An edit is already active");
//return;
}
kanban.inEditMode = true;
$(el).find('button').remove();
var name = $(el).text();
$(el).html('');
var $input = getInput().val(name).appendTo(el).focus();
$input[0].select();
var save = function () {
// Store the value
var name = $input.val();
// Remove the input
$(el).text(name);
// Save the value for the correct board
var board = $(el.parentNode.parentNode).attr("data-id");
var pos = kanban.findElementPosition(el);
kanban.getBoardJSON(board).item[pos].title = name;
kanban.onChange();
// Unlock edit mode
kanban.inEditMode = false;
};
$input.blur(save);
$input.keydown(function (e) {
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
save();
return;
}
if (e.which === 27) {
e.preventDefault();
e.stopPropagation();
$(el).text(name);
kanban.inEditMode = false;
addRemoveItemButton(framework, kanban);
return;
}
});
},
boardTitleClick: function (el, e) {
e.stopPropagation();
if (framework.isReadOnly() || framework.isLocked()) { return; }
if (kanban.inEditMode) {
$(el).focus();
verbose("An edit is already active");
//return;
}
kanban.inEditMode = true;
var name = $(el).text();
$(el).html('');
var $input = getInput().val(name).appendTo(el).focus();
$input[0].select();
var save = function () {
// Store the value
var name = $input.val();
// Remove the input
$(el).text(name);
// Save the value for the correct board
var board = $(el.parentNode.parentNode).attr("data-id");
kanban.getBoardJSON(board).title = name;
kanban.onChange();
// Unlock edit mode
kanban.inEditMode = false;
};
$input.blur(save);
$input.keydown(function (e) {
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
save();
return;
}
if (e.which === 27) {
e.preventDefault();
e.stopPropagation();
$(el).text(name);
kanban.inEditMode = false;
return;
}
});
},
colorClick: function (el) {
if (framework.isReadOnly() || framework.isLocked()) { return; }
verbose("in color click");
var board = $(el.parentNode).attr("data-id");
var boardJSON = kanban.getBoardJSON(board);
var currentColor = boardJSON.color;
verbose("Current color " + currentColor);
var index = kanban.options.colors.findIndex(function (element) {
return (element === currentColor);
}) + 1;
verbose("Next index " + index);
if (index >= kanban.options.colors.length) { index = 0; }
var nextColor = kanban.options.colors[index];
verbose("Next color " + nextColor);
boardJSON.color = nextColor;
$(el).removeClass("kanban-header-" + currentColor);
$(el).addClass("kanban-header-" + nextColor);
kanban.onChange();
},
buttonClick: function (el, boardId, e) {
e.stopPropagation();
if (framework.isReadOnly() || framework.isLocked()) { return; }
UI.confirm(Messages.kanban_deleteBoard, function (yes) {
if (!yes) { return; }
verbose("Delete board");
//var boardName = $(el.parentNode.parentNode).attr("data-id");
for (var index in kanban.options.boards) {
if (kanban.options.boards[index].id === boardId) {
break;
}
index++;
}
kanban.options.boards.splice(index, 1);
kanban.removeBoard(boardId);
kanban.onChange();
});
},
addItemClick: function (el) {
if (framework.isReadOnly() || framework.isLocked()) { return; }
if (kanban.inEditMode) {
$(el).focus();
verbose("An edit is already active");
//return;
}
kanban.inEditMode = true;
// create a form to enter element
var boardId = $(el.parentNode.parentNode).attr("data-id");
var $item = $('<div>', {'class': 'kanban-item'});
var $input = getInput().val(name).appendTo($item);
kanban.addForm(boardId, $item[0]);
$input.focus();
var save = function () {
$item.remove();
kanban.inEditMode = false;
if (!$input.val()) { return; }
kanban.addElement(boardId, {
"title": $input.val(),
});
};
$input.blur(save);
$input.keydown(function (e) {
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
save();
return;
}
if (e.which === 27) {
e.preventDefault();
e.stopPropagation();
$item.remove();
kanban.inEditMode = false;
return;
}
});
},
addItemButton: true,
boards: boards
});
var addBoardDefault = document.getElementById('kanban-addboard');
$(addBoardDefault).attr('title', Messages.kanban_addBoard);
addBoardDefault.addEventListener('click', function () {
if (framework.isReadOnly()) { return; }
var counter = 1;
// Get the new board id
var boardExists = function (b) { return b.id === "board" + counter; };
while (kanban.options.boards.some(boardExists)) { counter++; }
kanban.addBoards([{
"id": "board" + counter,
"title": Messages.kanban_newBoard,
"color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color
"item": [{
"title": Messages._getKey('kanban_item', [1]),
}]
}]);
kanban.onChange();
});
return kanban;
};
var mkHelpMenu = function (framework) {
var $toolbarContainer = $('#cp-app-kanban-container');
var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']);
$toolbarContainer.prepend(helpMenu.menu);
framework._.toolbar.$drawer.append(helpMenu.button);
};
// Start of the main loop
var andThen2 = function (framework) {
var kanban;
var $container = $('#cp-app-kanban-content');
mkHelpMenu(framework);
if (framework.isReadOnly()) {
$container.addClass('cp-app-readonly');
} else {
framework.setFileImporter({}, function (content /*, file */) {
var parsed;
try { parsed = JSON.parse(content); }
catch (e) { return void console.error(e); }
return { content: parsed };
});
}
framework.setFileExporter('json', function () {
return new Blob([JSON.stringify(kanban.getBoardsJSON())], {
type: 'application/json',
});
});
framework.onEditableChange(function (unlocked) {
if (framework.isReadOnly()) { return; }
if (!kanban) { return; }
if (unlocked) {
addRemoveItemButton(framework, kanban);
kanban.options.readOnly = false;
return void $container.removeClass('cp-app-readonly');
}
kanban.options.readOnly = true;
$container.addClass('cp-app-readonly');
});
framework.onContentUpdate(function (newContent) {
// Init if needed
if (!kanban) {
kanban = initKanban(framework, (newContent || {}).content);
addRemoveItemButton(framework, kanban);
return;
}
// Need to update the content
verbose("Content should be updated to " + newContent);
var currentContent = kanban.getBoardsJSON();
var remoteContent = newContent.content;
if (Sortify(currentContent) !== Sortify(remoteContent)) {
// reinit kanban (TODO: optimize to diff only)
verbose("Content is different.. Applying content");
kanban.setBoards(remoteContent);
kanban.inEditMode = false;
addRemoveItemButton(framework, kanban);
}
});
framework.setContentGetter(function () {
if (!kanban) {
return {
content: []
};
}
var content = kanban.getBoardsJSON();
verbose("Content current value is " + content);
return {
content: content
};
});
framework.onReady(function () {
$("#cp-app-kanban-content").focus();
});
framework.onDefaultContentNeeded(function () {
kanban = initKanban(framework);
});
framework.start();
};
var main = function () {
// var framework;
nThen(function (waitFor) {
// Framework initialization
Framework.create({
toolbarContainer: '#cme_toolbox',
contentContainer: '#cp-app-kanban-editor',
}, waitFor(function (framework) {
andThen2(framework);
}));
});
};
main();
});

100
www/kanban/jkanban.css Normal file
View File

@@ -0,0 +1,100 @@
.kanban-container * {
box-sizing: border-box;
}
.kanban-board {
position: relative;
float: left;
background: #E2E4E6;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
margin: 10px;
vertical-align: top;
display: flex;
flex-flow: column;
}
.kanban-board.disabled-board {
opacity: .3;
}
.kanban-board.is-moving.gu-mirror {
transform: rotate(3deg);
}
.kanban-board.is-moving.gu-mirror .kanban-drag {
overflow: hidden;
padding-right: 50px;
}
.kanban-board header {
font-size: 16px;
padding: 10px;
}
.kanban-board header .kanban-title-board {
font-weight: 700;
margin: 0;
padding: 0;
display: inline;
}
.kanban-board header .kanban-title-button {
float: right;
line-height: 1;
padding: .25rem .5rem;
}
.kanban-board .kanban-drag {
min-height: 200px;
padding: 20px;
flex: 1;
}
.kanban-item {
background: #fff;
padding: 15px;
margin-bottom: 20px;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
.kanban-item:hover {
cursor: move;
}
.kanban-item:last-child {
margin: 0;
}
.kanban-item.is-moving.gu-mirror {
transform: rotate(3deg);
height: auto !important;
}
/* Dragula CSS */
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.gu-transit {
opacity: 0.2 !important;
transform: rotate(0deg) !important;
}
.form-group {
text-align: right;
margin-button: 5px;
}

1567
www/kanban/jkanban.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ define([
'/common/outer/local-store.js',
'/common/test.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore, Test) {
$(function () {
var $main = $('#mainBlock');

22
www/mediatag/index.html Normal file
View File

@@ -0,0 +1,22 @@
<html>
<head>
<title>Test media-tag</title>
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
media-tag * {
max-width: 60vw;
max-height: 50vh;
}
iframe {
width: 60vw;
height: 50vh;
}
</style>
</head>
<body>
Media-tag:
<media-tag src="/blob/84/845adf0efaa47db1754956859a9ffdbc76619d75e38ea3f9" data-crypto-key="cryptpad:kZiCb6zYuTbQw/9/JGjE9WGS+0/BOv6d/qyJagscCgc="></media-tag><br>
<media-tag src="/blob/84/845adf0efaa47db1754956859a9ffdbc76619d75e38ea3f9" data-crypto-key="cryptpad:kZiCb6zYuTbQw/9/JGjE9WGS+0/BOv6d/qyJagscCgc="></media-tag><br>
<media-tag src="/blob/0e/0e9dd35a459eb1d90df073bd2f19108e50b96019a3478288" data-crypto-key="cryptpad:HD3KsYdiz3/em/1bXEoQSibRNEQRNnTq80SdSpQtp+g="></media-tag><br>
<media-tag src="/blob/fe/fe9f3562c42ee32703ced67cf92bfd77fee4e88eb27842db" data-crypto-key="cryptpad:NMo7rKVpl/MF/pw8A4XXJcYuI5JaADX+vQyhOXJgnLo="></media-tag>

58
www/mediatag/main.js Normal file
View File

@@ -0,0 +1,58 @@
require([
'jquery',
'/mediatag/media-tag.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function ($, MediaTag) {
console.log(MediaTag);
console.log($('media-tag'));
if (typeof MediaTag === "function") {
MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html';
var config = {
allowed: ['download'],
download: {
text: 'Download'
}
};
MediaTag($('media-tag'), config)
.on('progress', function (data) {
console.log(data.progress);
})
.on('complete', function (data) {
console.log(data);
})
.on('error', function (data) {
console.error(data);
});
MediaTag($('media-tag')[1])
.on('progress', function (data) {
console.log(data.progress);
})
.on('complete', function (data) {
console.log(data);
})
.on('error', function (data) {
console.error(data);
});
MediaTag($('media-tag')[2])
.on('progress', function (data) {
console.log(data.progress);
})
.on('complete', function (data) {
console.log(data);
})
.on('error', function (data) {
console.error(data);
});
MediaTag($('media-tag')[3])
.on('progress', function (data) {
console.log(data.progress);
})
.on('complete', function (data) {
console.log(data);
})
.on('error', function (data) {
console.error(data);
});
}
});

430
www/mediatag/media-tag.js Normal file
View File

@@ -0,0 +1,430 @@
(function(name, definition) {
if (typeof module !== 'undefined') { module.exports = definition(); }
else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); }
else { this[name] = definition(); }
}('MediaTag', function() {
var cache;
var cypherChunkLength = 131088;
// Save a blob on the file system
var saveFile = function (blob, url, fileName) {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
// We want to be able to download the file with a name, so we need an "a" tag with
// a download attribute
var a = document.createElement("a");
a.href = url;
a.download = fileName;
// It's not in the DOM, so we can't use a.click();
var event = new MouseEvent("click");
a.dispatchEvent(event);
}
};
var fixHTML = function (str) {
if (!str) { return ''; }
return str.replace(/[<>&"']/g, function (x) {
return ({ "<": "&lt;", ">": "&gt", "&": "&amp;", '"': "&#34;", "'": "&#39;" })[x];
});
};
// Default config, can be overriden per media-tag call
var config = {
allowed: [
'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', // FIXME?
'download'
],
pdf: {},
download: {
text: "Download"
},
Plugins: {
image: function (metadata, url, content, cfg, cb) {
var img = document.createElement('img');
img.setAttribute('src', url);
img.blob = content;
cb(void 0, img);
},
video: function (metadata, url, content, cfg, cb) {
var video = document.createElement('video');
video.setAttribute('src', url);
video.setAttribute('controls', true);
cb(void 0, video);
},
audio: function (metadata, url, content, cfg, cb) {
var audio = document.createElement('audio');
audio.setAttribute('src', url);
audio.setAttribute('controls', true);
cb(void 0, audio);
},
pdf: function (metadata, url, content, cfg, cb) {
var iframe = document.createElement('iframe');
if (cfg.pdf.viewer) { // PDFJS
var viewerUrl = cfg.pdf.viewer + '?file=' + url;
iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
return void cb (void 0, iframe);
}
iframe.src = url + '#' + window.encodeURIComponent(metadata.name);
return void cb (void 0, iframe);
},
download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button');
btn.innerHTML = cfg.download.text + '<br>' +
metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '';
btn.addEventListener('click', function () {
saveFile(content, url, metadata.name);
});
cb(void 0, btn);
}
}
};
// Download a blob from href
var download = function (src, cb) {
var xhr = new XMLHttpRequest();
xhr.open('GET', src, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
// Error?
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
var arrayBuffer = xhr.response;
if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); }
};
xhr.send(null);
};
// Decryption tools
var Decrypt = {
// Create a nonce
createNonce: function () {
var n = new Uint8Array(24);
for (var i = 0; i < 24; i++) { n[i] = 0; }
return n;
},
// Increment a nonce
increment: function (N) {
var l = N.length;
while (l-- > 1) {
/* .jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
// you don't need to worry about this running out.
// you'd need a REAAAALLY big file
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
N[l] = 0;
}
},
decodePrefix: function (A) {
return (A[0] << 8) | A[1];
},
joinChunks: function (chunks) {
return new Blob(chunks);
},
// Convert a Uint8Array into Array.
slice: function (u8) {
return Array.prototype.slice.call(u8);
},
// Gets the key from the key string.
getKeyFromStr: function (str) {
return window.nacl.util.decodeBase64(str);
}
};
// Decrypts a Uint8Array with the given key.
var decrypt = function (u8, strKey, done, progressCb) {
var Nacl = window.nacl;
var progress = function (offset) {
progressCb((offset / u8.length) * 100);
};
var key = Decrypt.getKeyFromStr(strKey);
var nonce = Decrypt.createNonce();
var i = 0;
var prefix = u8.subarray(0, 2);
var metadataLength = Decrypt.decodePrefix(prefix);
var res = { metadata: undefined };
// Get metadata
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
Decrypt.increment(nonce);
try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); }
catch (e) { return void done('E_METADATA_DECRYPTION'); }
if (!res.metadata) { return void done('NO_METADATA'); }
var takeChunk = function (cb) {
setTimeout(function () {
var start = i * cypherChunkLength + 2 + metadataLength;
var end = start + cypherChunkLength;
i++;
// Get the chunk
var box = new Uint8Array(u8.subarray(start, end));
// Decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
Decrypt.increment(nonce);
if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
progress(Math.min(end, u8.length));
cb(void 0, plaintext);
});
};
var chunks = [];
// decrypt file contents
var again = function () {
takeChunk(function (e, plaintext) {
if (e) { return setTimeout(function () { done(e); }); }
if (plaintext) {
if (i * cypherChunkLength < u8.length) { // not done
chunks.push(plaintext);
return again();
}
chunks.push(plaintext);
res.content = Decrypt.joinChunks(chunks);
return void done(void 0, res);
}
done('UNEXPECTED_ENDING');
});
};
again();
};
// Get type
var getType = function (mediaObject, metadata, cfg) {
var mime = metadata.type;
var s = metadata.type.split('/');
var type = s[0];
var extension = s[1];
mediaObject.name = metadata.name;
if (mime && cfg.allowed.indexOf(mime) !== -1) {
mediaObject.type = type;
mediaObject.extension = extension;
mediaObject.mime = mime;
return type;
} else if (cfg.allowed.indexOf('download') !== -1) {
mediaObject.type = type;
mediaObject.extension = extension;
mediaObject.mime = mime;
return 'download';
} else {
return;
}
};
// Copy attributes
var copyAttributes = function (origin, dest) {
Object.keys(origin.attributes).forEach(function (i) {
if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
var name = origin.attributes[i].name.slice(10);
var value = origin.attributes[i].value;
dest.setAttribute(name, value);
});
};
// Process
var process = function (mediaObject, decrypted, cfg, cb) {
var metadata = decrypted.metadata;
var blob = decrypted.content;
var mediaType = getType(mediaObject, metadata, cfg);
if (mediaType === 'application') {
mediaType = mediaObject.extension;
}
if (!mediaType || !cfg.Plugins[mediaType]) {
return void cb('NO_PLUGIN_FOUND');
}
// Get blob URL
var url = decrypted.url;
if (!url && window.URL) {
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
type: metadata.type
}));
}
cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) {
if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); }
copyAttributes(mediaObject.tag, el);
mediaObject.tag.innerHTML = '';
mediaObject.tag.appendChild(el);
cb();
});
};
var addMissingConfig = function (base, target) {
Object.keys(target).forEach(function (k) {
if (!target[k]) { return; }
// Target is an object, fix it recursively
if (typeof target[k] === "object" && !Array.isArray(target[k])) {
// Sub-object
if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; }
else if (base[k]) { addMissingConfig(base[k], target[k]); }
else {
base[k] = {};
addMissingConfig(base[k], target[k]);
}
}
// Target is array or immutable, copy the value if it's missing
if (!base[k]) {
base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k]))
: target[k];
}
});
};
// Initialize a media-tag
var init = function (el, cfg) {
cfg = cfg || {};
addMissingConfig(cfg, config);
// Add support for old mediatag library
if (!cfg.pdf.viewer && init.PdfPlugin && init.PdfPlugin.viewer) {
cfg.pdf.viewer = init.PdfPlugin.viewer;
}
// Handle jQuery elements
if (typeof(el) === "object" && el.jQuery) { el = el[0]; }
// Abort smoothly if the element is not a media-tag
if (el.nodeName !== "MEDIA-TAG") {
console.error("Not a media-tag!");
return {
on: function () { return this; }
};
}
var handlers = cfg.handlers || {
'progress': [],
'complete': [],
'error': []
};
var mediaObject = el._mediaObject = {
handlers: handlers,
tag: el
};
var emit = function (ev, data) {
// Check if the event name is valid
if (Object.keys(handlers).indexOf(ev) === -1) {
return void console.error("Invalid mediatag event");
}
// Call the handlers
handlers[ev].forEach(function (h) {
// Make sure a bad handler won't break the media-tag script
try {
h(data);
} catch (err) {
console.error(err);
}
});
};
mediaObject.on = function (ev, handler) {
// Check if the event name is valid
if (Object.keys(handlers).indexOf(ev) === -1) {
console.error("Invalid mediatag event");
return mediaObject;
}
// Check if the handler is valid
if (typeof (handler) !== "function") {
console.error("Handler is not a function!");
return mediaObject;
}
// Add the handler
handlers[ev].push(handler);
return mediaObject;
};
var src = el.getAttribute('src');
var strKey = el.getAttribute('data-crypto-key');
if (/^cryptpad:/.test(strKey)) {
strKey = strKey.slice(9);
}
var uid = [src, strKey].join('');
// End media-tag rendering: display the tag and emit the event
var end = function (decrypted) {
process(mediaObject, decrypted, cfg, function (err) {
if (err) { return void emit('error', err); }
emit('complete', decrypted);
});
};
// If we have the blob in our cache, don't download & decrypt it again, just display
if (cache[uid]) {
end(cache[uid]);
return mediaObject;
}
// Download the encrypted blob
download(src, function (err, u8Encrypted) {
if (err) {
return void emit('error', err);
}
// Decrypt the blob
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
if (errDecryption) {
return void emit('error', errDecryption);
}
// Cache and display the decrypted blob
cache[uid] = u8Decrypted;
end(u8Decrypted);
}, function (progress) {
emit('progress', {
progress: progress
});
});
});
return mediaObject;
};
// Add the cache as a property of MediaTag
cache = init.__Cryptpad_Cache = {};
init.PdfPlugin = {};
return init;
}));

View File

@@ -1,42 +0,0 @@
@import (once) "../customize/src/less2/include/browser.less";
@import (once) "../customize/src/less2/include/toolbar.less";
@import (once) "../customize/src/less2/include/markdown.less";
@import (once) '../customize/src/less2/include/fileupload.less';
@import (once) '../customize/src/less2/include/alertify.less';
@import (once) '../customize/src/less2/include/avatar.less';
.toolbar_main(
@bg-color: @colortheme_oocell-bg,
@warn-color: @colortheme_oocell-warn,
@color: @colortheme_oocell-color
);
.fileupload_main();
.alertify_main();
// body
&.cp-app-oocell {
display: flex;
flex-flow: column;
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-oo-container {
flex: 1;
height: 100%;
background-color: lightgrey;
display: flex;
}
#ooframe {
flex: 1;
border:none;
margin:0;
padding:0;
}
}

View File

@@ -1,42 +0,0 @@
@import (once) "../customize/src/less2/include/browser.less";
@import (once) "../customize/src/less2/include/toolbar.less";
@import (once) "../customize/src/less2/include/markdown.less";
@import (once) '../customize/src/less2/include/fileupload.less';
@import (once) '../customize/src/less2/include/alertify.less';
@import (once) '../customize/src/less2/include/avatar.less';
.toolbar_main(
@bg-color: @colortheme_oodoc-bg,
@warn-color: @colortheme_oodoc-warn,
@color: @colortheme_oodoc-color
);
.fileupload_main();
.alertify_main();
// body
&.cp-app-oodoc {
display: flex;
flex-flow: column;
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-oo-container {
flex: 1;
height: 100%;
background-color: lightgrey;
display: flex;
}
#ooframe {
flex: 1;
border:none;
margin:0;
padding:0;
}
}

View File

@@ -1,42 +0,0 @@
@import (once) "../customize/src/less2/include/browser.less";
@import (once) "../customize/src/less2/include/toolbar.less";
@import (once) "../customize/src/less2/include/markdown.less";
@import (once) '../customize/src/less2/include/fileupload.less';
@import (once) '../customize/src/less2/include/alertify.less';
@import (once) '../customize/src/less2/include/avatar.less';
.toolbar_main(
@bg-color: @colortheme_ooslide-bg,
@warn-color: @colortheme_ooslide-warn,
@color: @colortheme_ooslide-color
);
.fileupload_main();
.alertify_main();
// body
&.cp-app-ooslide {
display: flex;
flex-flow: column;
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-oo-container {
flex: 1;
height: 100%;
background-color: lightgrey;
display: flex;
}
#ooframe {
flex: 1;
border:none;
margin:0;
padding:0;
}
}

View File

@@ -1,15 +1,12 @@
@import (once) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/framework.less";
.framework_main(
@bg-color: @colortheme_pad-bg,
@warn-color: @colortheme_pad-warn,
@color: @colortheme_pad-color
);
.alertify_main();
body.cp-app-pad {
.framework_main(
@bg-color: @colortheme_pad-bg,
@warn-color: @colortheme_pad-warn,
@color: @colortheme_pad-color
);
// body
&.cp-app-pad {
.tokenfield_main();
#cke_1_top {
overflow: visible;
padding: 0px;
@@ -46,17 +43,22 @@
}
}
}
.cke_wysiwyg_frame {
min-width: 60%;
}
@media print {
#cke_1_top {
display:none !important;
.cke_dialog {
display: block;
overflow-x: auto;
max-height: 100vh;
}
&.cp-app-pad .cp-toolbar-userlist-drawer {
display:none;
.cke_wysiwyg_frame {
min-width: 60%;
}
@media print {
#cke_1_top {
display:none !important;
}
&.cp-app-pad .cp-toolbar-userlist-drawer {
display:none;
}
}
}

40
www/pad/disable-base64.js Normal file
View File

@@ -0,0 +1,40 @@
( function() {
CKEDITOR.plugins.add('blockbase64', {
init: function (editor) {
var replaceImgText = function (html) {
var ret = html.replace( /<img[^>]*src="data:image\/(bmp|dds|gif|jpg|jpeg|png|psd|pspimage|tga|thm|tif|tiff|yuv|ai|eps|ps|svg);base64,.*?"[^>]*>/gi,
function () {
console.error("Direct image paste is not allowed.");
return '';
});
return ret;
};
/*var chkImg = function () {
// don't execute code if the editor is readOnly
if (editor.readOnly) {
return;
}
setTimeout( function() {
editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
},100);
};
editor.on('contentDom', function () {
// For Firefox
editor.document.on('drop', chkImg);
// For IE
editor.document.getBody().on('drop', chkImg);
});*/
editor.on('paste', function(e) {
var html = e.data.dataValue;
if (!html) { return; }
e.data.dataValue = replaceImgText(html);
});
}
});
})();

View File

@@ -3,6 +3,43 @@
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
}
#cke_1_top {
overflow: visible;
padding: 0px;
display: flex;
}
#cke_1_toolbox {
display: inline-block;
width: 100%;
background-color: #c1e7ff;
}
#cke_1_toolbox .cke_toolbar {
height: 28px;
padding: 2px 0;
}
#cke_1_top .cryptpad-toolbar {
padding: 0;
display: block;
}
.cke_wysiwyg_frame {
min-width: 60%;
}
@media print {
#cke_1_top {
display:none;
}
body.app-pad .userlist-drawer {
display:none;
}
}
</style>
</head>
<body class="cp-app-pad">
<textarea style="display:none" id="editor1" name="editor1"></textarea>

View File

@@ -30,14 +30,17 @@ define([
'/api/config',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-interface.js',
'/common/hyperscript.js',
'/bower_components/chainpad/chainpad.dist.js',
'/customize/application_config.js',
'/common/test.js',
'/bower_components/diff-dom/diffDOM.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/pad/app-pad.less'
], function (
$,
Hyperjson,
@@ -51,8 +54,12 @@ define([
ApiConfig,
Hash,
Util,
UI,
h,
ChainPad,
AppConfig)
AppConfig,
Test
)
{
var DiffDom = window.diffDOM;
@@ -82,12 +89,54 @@ define([
Cursor: Cursor,
};
// MEDIATAG: Filter elements to serialize
// * Remove the drag&drop and resizers from the hyperjson
var isWidget = function (el) {
return typeof (el.getAttribute) === "function" &&
(el.getAttribute('data-cke-hidden-sel') ||
(el.getAttribute('class') &&
(/cke_widget_drag/.test(el.getAttribute('class')) ||
/cke_image_resizer/.test(el.getAttribute('class')))
)
);
};
var isNotMagicLine = function (el) {
return !(el && typeof(el.getAttribute) === 'function' &&
el.getAttribute('class') &&
el.getAttribute('class').split(' ').indexOf('non-realtime') !== -1);
};
var shouldSerialize = function (el) {
return isNotMagicLine(el) && !isWidget(el);
};
// MEDIATAG: Filter attributes in the serialized elements
var widgetFilter = function (hj) {
// Send a widget ID == 0 to avoid a fight between browsers and
// prevent the container from having the "selected" class (blue border)
if (hj[1].class) {
var split = hj[1].class.split(' ');
if (split.indexOf('cke_widget_wrapper') !== -1 &&
split.indexOf('cke_widget_block') !== -1) {
hj[1].class = "cke_widget_wrapper cke_widget_block";
hj[1]['data-cke-widget-id'] = "0";
}
if (split.indexOf('cke_widget_wrapper') !== -1 &&
split.indexOf('cke_widget_inline') !== -1) {
hj[1].class = "cke_widget_wrapper cke_widget_inline";
delete hj[1]['data-cke-widget-id'];
//hj[1]['data-cke-widget-id'] = "0";
}
// Remove the title attribute of the drag&drop icons (translation conflicts)
if (split.indexOf('cke_widget_drag_handler') !== -1 ||
split.indexOf('cke_image_resizer') !== -1) {
hj[1].title = undefined;
}
}
return hj;
};
var hjsonFilters = function (hj) {
/* catch `type="_moz"` before it goes over the wire */
var brFilter = function (hj) {
@@ -100,6 +149,7 @@ define([
};
brFilter(hj);
mediatagContentFilter(hj);
widgetFilter(hj);
return hj;
};
@@ -117,7 +167,14 @@ define([
];
var getHTML = function (inner) {
return ('<!DOCTYPE html>\n' + '<html>\n' + inner.innerHTML);
return ('<!DOCTYPE html>\n' + '<html>\n' +
' <head><meta charset="utf-8"></head>\n <body>' +
inner.innerHTML.replace(/<img[^>]*class="cke_anchor"[^>]*data-cke-realelement="([^"]*)"[^>]*>/g,
function(match,realElt){
//console.log("returning realElt \"" + unescape(realElt)+ "\".");
return decodeURIComponent(realElt); }) +
' </body>\n</html>'
);
};
var CKEDITOR_CHECK_INTERVAL = 100;
@@ -168,6 +225,36 @@ define([
return true;
}
}
// MEDIATAG
// Never modify widget ids
if (info.node && info.node.tagName === 'SPAN' && info.diff.name === 'data-cke-widget-id') {
return true;
}
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('class') &&
/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
if (info.diff.action === 'modifyAttribute' && info.diff.name === 'class') {
return true;
}
//console.log(info);
}
// CkEditor drag&drop icon container
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('class') &&
info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler_container') !== -1) {
return true;
}
// CkEditor drag&drop title (language fight)
if (info.node && info.node.getAttribute &&
info.node.getAttribute('class') &&
(info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler') !== -1 ||
info.node.getAttribute('class').split(' ').indexOf('cke_image_resizer') !== -1 ) ) {
return true;
}
/*
Also reject any elements which would insert any one of
our forbidden tag types: script, iframe, object,
@@ -199,20 +286,26 @@ define([
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('contentEditable') === "false") {
// it seems to be a magicline plugin element...
// but it can also be a widget (MEDIATAG), in which case the removal was
// probably intentional
if (info.diff.action === 'removeElement') {
// and you're about to remove it...
// this probably isn't what you want
if (!info.node.getAttribute('class') ||
!/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
// This element is not a widget!
// this probably isn't what you want
/*
I have never seen this in the console, but the
magic line is still getting removed on remote
edits. This suggests that it's getting removed
by something other than diffDom.
*/
console.log("preventing removal of the magic line!");
/*
I have never seen this in the console, but the
magic line is still getting removed on remote
edits. This suggests that it's getting removed
by something other than diffDom.
*/
console.log("preventing removal of the magic line!");
// return true to prevent diff application
return true;
// return true to prevent diff application
return true;
}
}
}
@@ -322,7 +415,7 @@ define([
var src = tag.getAttribute('src');
if (mediaTagMap[src]) {
mediaTagMap[src].forEach(function (n) {
tag.appendChild(n);
tag.appendChild(n.cloneNode());
});
}
});
@@ -370,8 +463,11 @@ define([
framework.setMediaTagEmbedder(function ($mt) {
$mt.attr('contenteditable', 'false');
$mt.attr('tabindex', '1');
editor.insertElement(new window.CKEDITOR.dom.element($mt[0]));
//$mt.attr('tabindex', '1');
//MEDIATAG
var element = new window.CKEDITOR.dom.element($mt[0]);
editor.insertElement(element);
editor.widgets.initOn( element, 'mediatag' );
});
framework.setTitleRecommender(function () {
@@ -403,7 +499,18 @@ define([
var patch = (DD).diff(inner, userDocStateDom);
(DD).apply(inner, patch);
// MEDIATAG: Migrate old mediatags to the widget system
$(inner).find('media-tag:not(.cke_widget_element)').each(function (i, el) {
var element = new window.CKEDITOR.dom.element(el);
editor.widgets.initOn( element, 'mediatag' );
});
displayMediaTags(framework, inner, mediaTagMap);
// MEDIATAG: Initialize mediatag widgets inserted in the document by other users
editor.widgets.checkWidgets();
if (framework.isReadOnly()) {
var $links = $(inner).find('a');
// off so that we don't end up with multiple identical handlers
@@ -425,7 +532,7 @@ define([
framework.setContentGetter(function () {
displayMediaTags(framework, inner, mediaTagMap);
inner.normalize();
return Hyperjson.fromDOM(inner, isNotMagicLine, hjsonFilters);
return Hyperjson.fromDOM(inner, shouldSerialize, hjsonFilters);
});
$bar.find('#cke_1_toolbar_collapser').hide();
@@ -460,10 +567,18 @@ define([
body: $('body'),
onUploaded: function (ev, data) {
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>';
editor.insertElement(window.CKEDITOR.dom.element.createFromHtml(mt));
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
// MEDIATAG
var element = window.CKEDITOR.dom.element.createFromHtml(mt);
if (ev && ev.insertElement) {
ev.insertElement(element);
} else {
editor.insertElement(element);
}
editor.widgets.initOn( element, 'mediatag' );
}
};
window.APP.FM = framework._.sfCommon.createFileManager(fmConfig);
@@ -474,6 +589,38 @@ define([
$iframe.find('html').addClass('cke_body_width');
}
});
framework._.sfCommon.isPadStored(function (err, val) {
if (!val) { return; }
var b64images = $(inner).find('img[src^="data:image"]:not(.cke_reset)');
if (b64images.length && framework._.sfCommon.isLoggedIn()) {
var no = h('button.cp-corner-cancel', Messages.cancel);
var yes = h('button.cp-corner-primary', Messages.ok);
var actions = h('div', [yes, no]);
var modal = UI.cornerPopup(Messages.pad_base64, actions, '', {big: true});
$(no).click(function () {
modal.delete();
});
$(yes).click(function () {
modal.delete();
b64images.each(function (i, el) {
var src = $(el).attr('src');
var blob = Util.dataURIToBlob(src);
var ext = '.' + (blob.type.split('/')[1] || 'png');
var name = (framework._.title.getTitle() || 'Pad')+'_image';
blob.name = name + ext;
var ev = {
insertElement: function (newEl) {
var element = new window.CKEDITOR.dom.element(el);
newEl.replace(element);
setTimeout(framework.localChange);
}
};
window.APP.FM.handleFile(blob, ev);
});
});
}
});
/*setTimeout(function () {
$('iframe.cke_wysiwyg_frame').focus();
editor.focus();
@@ -504,10 +651,12 @@ define([
var $clone = $(inner).clone();
nThen(function (waitFor) {
$(inner).find('media-tag').each(function (i, el) {
if (!$(el).data('blob')) { return; }
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
if (!$(el).data('blob') || !el.blob) { return; }
Util.blobToImage(el.blob || $(el).data('blob'), waitFor(function (imgSrc) {
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
.attr('src', imgSrc);
$clone.find('media-tag').parent()
.find('.cke_widget_drag_handler_container').remove();
}));
});
}).nThen(function () {
@@ -628,18 +777,27 @@ define([
'max-width: 50em; padding: 20px 30px; margin: 0 auto; min-height: 100%;'+
'box-sizing: border-box; overflow: auto;'+
'}' +
'.cke_body_width body > *:first-child { margin-top: 0; }';
'.cke_body_width body > *:first-child { margin-top: 0; }' +
Ckeditor.addCss(newCss);
Ckeditor._mediatagTranslations = {
title: Messages.pad_mediatagTitle,
width: Messages.pad_mediatagWidth,
height: Messages.pad_mediatagHeight,
ratio: Messages.pad_mediatagRatio,
border: Messages.pad_mediatagBorder,
preview: Messages.pad_mediatagPreview,
'import': Messages.pad_mediatagImport,
options: Messages.pad_mediatagOptions
};
Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js');
Ckeditor.plugins.addExternal('blockbase64','/pad/', 'disable-base64.js');
module.ckeditor = editor = Ckeditor.replace('editor1', {
customConfig: '/customize/ckeditor-config.js',
});
editor.on('instanceReady', waitFor());
}).nThen(function () {
editor.plugins.mediatag.translations = {
title: Messages.pad_mediatagTitle,
width: Messages.pad_mediatagWidth,
height: Messages.pad_mediatagHeight
editor.plugins.mediatag.import = function ($mt) {
framework._.sfCommon.importMediaTag($mt);
};
Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor});
}).nThen(function () {
@@ -654,6 +812,79 @@ define([
}).nThen(waitFor());
}).nThen(function (/*waitFor*/) {
function launchAnchorTest(test) {
// -------- anchor test: make sure the exported anchor contains <a name="..."> -------
console.log('---- anchor test: make sure the exported anchor contains <a name="..."> -----.');
function tryAndTestExport() {
console.log("Starting tryAndTestExport.");
editor.on( 'dialogShow', function( evt ) {
console.log("Anchor dialog detected.");
var dialog = evt.data;
$(dialog.parts.contents.$).find("input").val('xx-' + Math.round(Math.random()*1000));
dialog.click(window.CKEDITOR.dialog.okButton(editor).id);
} );
var existingText = editor.getData();
editor.insertText("A bit of text");
console.log("Launching anchor command.");
editor.execCommand(editor.ui.get('Anchor').command);
console.log("Anchor command launched.");
var waitH = window.setInterval(function() {
console.log("Waited 2s for the dialog to appear");
var anchors = window.CKEDITOR.plugins["link"].getEditorAnchors(editor);
if(!anchors || anchors.length===0) {
test.fail("No anchors found. Please adjust document");
} else {
console.log(anchors.length + " anchors found.");
var exported = getHTML(window.inner);
console.log("Obtained exported: " + exported);
var allFound = true;
for(var i=0; i<anchors.length; i++) {
var anchor = anchors[i];
console.log("Anchor " + anchor.name);
var expected = "<a id=\"" + anchor.id + "\" name=\"" + anchor.name + "\" ";
var found = exported.indexOf(expected)>=0;
console.log("Found " + expected + " " + found + ".");
allFound = allFound && found;
}
console.log("Cleaning up.");
if(allFound) {
// clean-up
editor.execCommand('undo');
editor.execCommand('undo');
var nint = window.setInterval(function(){
console.log("Waiting for undo to yield same result.");
if(existingText === editor.getData()) {
window.clearInterval(nint);
test.pass();
}
}, 500);
} else
{
test.fail("Not all expected a elements found for document at " + window.top.location + ".");
}
}
window.clearInterval(waitH);
},2000);
}
var intervalHandle = window.setInterval(function() {
if(editor.status==="ready") {
window.clearInterval(intervalHandle);
console.log("Editor is ready.");
tryAndTestExport();
} else {
console.log("Waiting for editor to be ready.");
}
}, 100);
}
Test(function(test) {
launchAnchorTest(test);
});
andThen2(editor, Ckeditor, framework);
});
};

View File

@@ -1,5 +1,5 @@
CKEDITOR.dialog.add('mediatag', function (editor) {
var Messages = editor.plugins.mediatag.translations;
var Messages = CKEDITOR._mediatagTranslations;
return {
title: Messages.title,
minWidth: 400,
@@ -13,17 +13,53 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
type: 'text',
id: 'width',
label: Messages.width,
validate: function () {
if (isNaN(this.getValue())) { return false; }
}
},
{
type: 'text',
id: 'height',
label: Messages.height,
}
validate: function () {
if (isNaN(this.getValue())) { return false; }
}
},
{
type: 'checkbox',
id: 'lock',
'default': 'checked',
label: Messages.ratio,
},
{
type: 'text',
id: 'border',
label: Messages.border,
validate: function () {
if (isNaN(this.getValue())) { return false; }
}
},
{
type: 'html',
id: 'preview',
html: '<label>'+Messages.preview+'</label>'+
'<div id="ck-mediatag-preview"'+
'style="margin:auto;resize:both;max-width:300px;max-height:300px;overflow:auto"'+
'></div>'
},
]
},
],
onShow: function () {
var el = editor.plugins.mediatag.clicked;
var thiss = this;
var sel = editor.getSelection();
element = sel.getSelectedElement();
if (!element) { return; }
var el = element.findOne('media-tag');
if (!el) { return; }
var rect = el.getClientRect();
var dialog = this.parts.contents.$;
var inputs = dialog.querySelectorAll('input');
@@ -31,14 +67,81 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
var hInput = inputs[1];
wInput.value = Math.round(rect.width);
hInput.value = Math.round(rect.height);
var keepRatio = inputs[2];
var ratio = wInput.value/hInput.value;
var borderInput = inputs[3];
var bValue = el.getStyle('border-width').replace('px', '') || 0;
borderInput.value = bValue;
var $preview = $(dialog).find('#ck-mediatag-preview');
var $clone = $(el.$).clone();
$clone.css({
display: 'flex',
'border-style': 'solid',
'border-color': 'black'
});
$preview.html('').append($clone);
var center = function () {
var top = Math.max(($(document).height()/2) - (thiss.getSize().height/2), 0);
thiss.move(thiss.getPosition().x, top);
};
var update = function () {
var w = $(wInput).val() || Math.round(rect.width);
var h = $(hInput).val() || Math.round(rect.height);
var b = $(borderInput).val() || bValue;
$clone.css({
width: w+'px',
height: h+'px',
'border-width': b+'px'
});
center();
};
$(wInput).on('keyup', function () {
if (!$(keepRatio).is(':checked')) { return; }
var w = $(wInput).val();
if (isNaN(w)) { return; }
var newVal = w/ratio;
$(hInput).val(Math.round(newVal));
update();
});
$(hInput).on('keyup', function () {
if (!$(keepRatio).is(':checked')) { return; }
var h = $(hInput).val();
if (isNaN(h)) { return; }
var newVal = h*ratio;
$(wInput).val(Math.round(newVal));
update();
});
$(keepRatio).on('change', function () {
ratio = $(wInput).val()/$(hInput).val();
});
$(borderInput).on('keyup', function () {
update();
});
setTimeout(center);
},
onOk: function() {
var dialog = this;
var el = editor.plugins.mediatag.clicked;
var sel = editor.getSelection();
element = sel.getSelectedElement();
if (!element) { return; }
var el = element.findOne('media-tag');
if (!el) { return; }
var dialog = this.parts.contents.$;
var inputs = dialog.querySelectorAll('input');
var wInput = inputs[0];
var hInput = inputs[1];
var bInput = inputs[3];
window.setTimeout(function () {
if (wInput.value === "") {
@@ -53,6 +156,11 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
} else {
el.setSize('height', parseInt(hInput.value));
}
if (bInput.value === "") {
el.removeStyle('border-width');
} else {
el.setStyle('border-width', parseInt(bInput.value)+'px');
}
editor.fire( 'change' );
});
}

View File

@@ -1,16 +1,7 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview The Image plugin.
*/
( function() {
CKEDITOR.plugins.add( 'mediatag', {
requires: 'dialog',
requires: 'dialog,widget',
//icons: 'image',
//hidpi: true,
onLoad: function () {
@@ -18,6 +9,9 @@
CKEDITOR.addCss(
'media-tag{' +
'display:inline-block;' +
'border-style: solid;' +
'border-color: black;' +
'border-width: 0;' +
'}' +
'media-tag.selected{' +
'border: 1px solid black;' +
@@ -34,148 +28,73 @@
},
init: function( editor ) {
var pluginName = 'mediatag';
var Messages = CKEDITOR._mediatagTranslations;
var targetWidget;
// Register the dialog.
CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' );
var allowed = 'media-tag[!data-crypto-key,!src,contenteditable,width,height]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
required = 'media-tag[data-crypto-key,src]';
editor.widgets.add( 'mediatag', {
// Register the command.
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, {
allowedContent: allowed,
requiredContent: required,
contentTransformations: [
[ 'media-tag{width}: sizeToStyle', 'media-tag[width]: sizeToAttribute' ],
[ 'media-tag{float}: alignmentToStyle', 'media-tag[align]: alignmentToAttribute' ]
]
} ) );
getLabel: function () { return " "; },
dialog: pluginName,
inline: true,
upcast: function( element ) {
return element.name === 'media-tag';
}
var isMediaTag = function (el) {
if (el.is('media-tag')) { return el; }
var mt = el.getParents().slice().filter(function (p) {
return p.is('media-tag');
});
if (mt.length !== 1) { return; }
return mt[0];
};
editor.on('doubleclick', function (evt) {
var element = evt.data.element;
var mt = isMediaTag(element);
if (mt && !element.data('cke-realelement')) {
editor.plugins.mediatag.clicked = mt;
evt.data.dialog = 'mediatag';
});
editor.addCommand('importMediatag', {
exec: function (editor) {
var w = targetWidget;
targetWidget = undefined;
var $mt = $(w.$).find('media-tag');
editor.plugins.mediatag.import($mt);
}
});
// If the "contextmenu" plugin is loaded, register the listeners.
if (editor.addMenuItems) {
editor.addMenuGroup('mediatag');
editor.addMenuItem('importMediatag', {
label: Messages.import,
icon: 'save',
command: 'importMediatag',
group: 'mediatag'
});
editor.addMenuItem('mediatag', {
label: Messages.options,
icon: 'image',
command: 'mediatag',
group: 'mediatag'
});
}
if (editor.contextMenu) {
editor.contextMenu.addListener(function (element) {
if (getSelectedMediatag(editor, element)) {
return { mediatag: CKEDITOR.TRISTATE_OFF };
if (element.is('.cke_widget_mediatag')
|| element.getAttribute('data-cke-display-name') === 'media-tag') {
targetWidget = element;
return {
mediatag: CKEDITOR.TRISTATE_OFF,
importMediatag: CKEDITOR.TRISTATE_OFF,
};
}
});
}
},
afterInit: function( editor ) {
// Customize the behavior of the alignment commands. (http://dev.ckeditor.com/ticket/7430)
setupAlignCommand('left');
setupAlignCommand('right');
setupAlignCommand('center');
setupAlignCommand('block');
function setupAlignCommand (value) {
var command = editor.getCommand('justify' + value);
if (command) {
if (value === 'left' || value === 'right') {
command.on('exec', function (evt) {
var img = getSelectedMediatag(editor), align;
if (img) {
align = getMediatagAlignment(img);
if (align === value) {
img.removeStyle('float');
// Remove "align" attribute when necessary.
if (value === getMediatagAlignment(img))
img.removeAttribute( 'align' );
} else {
img.setStyle( 'float', value );
}
evt.cancel();
}
} );
}
command.on('refresh', function (evt) {
var img = getSelectedMediatag(editor), align;
if (img) {
align = getMediatagAlignment(img);
this.setState(
(align === value) ? CKEDITOR.TRISTATE_ON : ( value === 'right' || value === 'left' ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
evt.cancel();
}
} );
}
}
}
} );
function getSelectedMediatag (editor, element) {
if (!element) {
var sel = editor.getSelection();
element = sel.getSelectedElement();
CKEDITOR.on('dialogDefinition', function (ev) {
var dialog = ev.data.definition;
if (ev.data.name === 'image') {
dialog.removeContents('Link');
dialog.removeContents('advanced');
//var info = dialog.getContents('info');
//info.remove('cmbAlign');
}
});
if (element && element.is('media-tag') && !element.data('cke-realelement')
&& !element.isReadOnly()) {
return element;
}
}
function getMediatagAlignment (element) {
var align = element.getStyle('float');
if (align === 'inherit' || align === 'none') {
align = 0;
}
if (!align) {
align = element.getAttribute('align');
}
return align;
}
} )();
/**
* Determines whether dimension inputs should be automatically filled when the image URL changes in the Image plugin dialog window.
*
* config.image_prefillDimensions = false;
*
* @since 4.5
* @cfg {Boolean} [image_prefillDimensions=true]
* @member CKEDITOR.config
*/
/**
* Whether to remove links when emptying the link URL field in the Image dialog window.
*
* config.image_removeLinkByEmptyURL = false;
*
* @cfg {Boolean} [image_removeLinkByEmptyURL=true]
* @member CKEDITOR.config
*/
CKEDITOR.config.mediatag_removeLinkByEmptyURL = true;
/**
* Padding text to set off the image in the preview area.
*
* config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 );
*
* @cfg {String} [image_previewText='Lorem ipsum dolor...' (placeholder text)]
* @member CKEDITOR.config
*/

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ define([
'jquery',
'/common/toolbar3.js',
'/common/common-util.js',
'/common/cryptget.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-realtime.js',
@@ -26,13 +25,12 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/poll/app-poll.less',
], function (
$,
Toolbar,
Util,
Cryptget,
nThen,
SFCommon,
CommonRealtime,
@@ -647,7 +645,7 @@ define([
var updatePublishedDescription = function () {
var v = APP.editor.getValue();
DiffMd.apply(DiffMd.render(v || ''), APP.$descriptionPublished);
DiffMd.apply(DiffMd.render(v || ''), APP.$descriptionPublished, common);
};
var updateDescription = function (old, n) {
var o = APP.editor.getValue();

View File

@@ -1,23 +1,15 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
.toolbar_main(
@bg-color: @colortheme_profile-bg,
@warn-color: @colortheme_profile-warn,
@color: @colortheme_profile-color
);
.fileupload_main();
.alertify_main();
.sidebar-layout_main();
// body
&.cp-app-profile {
.framework_min_main(
@bg-color: @colortheme_profile-bg,
@warn-color: @colortheme_profile-warn,
@color: @colortheme_profile-color
);
.sidebar-layout_main();
display: flex;
flex-flow: column;
#cp-app-profile-header {

View File

@@ -19,8 +19,8 @@ define([
'css!/bower_components/codemirror/addon/dialog/dialog.css',
'css!/bower_components/codemirror/addon/fold/foldgutter.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/profile/app-profile.less',
'/bower_components/croppie/croppie.min.js',
'css!/bower_components/croppie/croppie.css',
], function (
@@ -43,16 +43,6 @@ define([
_onRefresh: []
};
// Decryption event for avatar mediatag (TODO not needed anymore?)
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
UI.alert(error.message);
});
$(window).click(function () {
$('.cp-dropdown-content').hide();
});

View File

@@ -40,6 +40,7 @@ define([
var Hash = Utils.Hash;
// 1st case: visiting someone else's profile with hash in the URL
if (window.location.hash) {
// No password for profiles
return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1)));
}
var editHash;
@@ -50,11 +51,17 @@ define([
}));
}).nThen(function () {
if (editHash) {
// No password for profile
return void cb(null, Hash.getSecrets('profile', editHash));
}
// 3rd case: profile creation (create a new random hash, store it later if needed)
if (!Utils.LocalStore.isLoggedIn()) { return void cb(); }
var hash = Hash.createRandomHash();
if (!Utils.LocalStore.isLoggedIn()) {
// Unregistered users can't create a profile
window.location.href = '/drive/';
return void cb();
}
// No password for profile
var hash = Hash.createRandomHash('profile');
var secret = Hash.getSecrets('profile', hash);
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
@@ -65,8 +72,8 @@ define([
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
}
var profile = {};
profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
profile.edit = Utils.Hash.getEditHashFromKeys(secret);
profile.view = Utils.Hash.getViewHashFromKeys(secret);
Cryptpad.setNewProfile(profile);
});
cb(null, secret);
@@ -75,7 +82,7 @@ define([
var addRpc = function (sframeChan, Cryptpad, Utils) {
// Adding a new avatar from the profile: pin it and store it in the object
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data);
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); }
Cryptpad.setAvatar(data, cb);
@@ -83,7 +90,7 @@ define([
});
// Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data);
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
Cryptpad.unpinPads([chanId], function () {
Cryptpad.setAvatar(undefined, cb);
});
@@ -93,6 +100,7 @@ define([
getSecrets: getSecrets,
noHash: true, // Don't add the hash in the URL if it doesn't already exist
addRpc: addRpc,
owned: true
});
});
});

View File

@@ -11,7 +11,7 @@ define([
'/common/common-feedback.js',
'/common/outer/local-store.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Login, Cryptpad, Test, Cred, UI, Util, Realtime, Constants, Feedback, LocalStore) {
var Messages = Cryptpad.Messages;
@@ -57,11 +57,6 @@ define([
var test;
$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();
@@ -118,15 +113,10 @@ define([
}, 500);
$register.on('keypress', function (e) {
e.preventDefault();
e.stopPropagation();
console.error(e.which);
switch (e.which) {
case 13: return clickRegister();
case 13: return clickRegister();
default:
//console.log(e.which);
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
return clickRegister();
}
});

View File

@@ -1,26 +1,21 @@
@import (once) "../../customize/src/less2/include/colortheme-all.less";
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
@import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/creation.less";
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (reference) "../../customize/src/less2/include/limit-bar.less";
@import (reference) "../../customize/src/less2/include/creation.less";
@import (reference) '../../customize/src/less2/include/framework.less';
.toolbar_main(
@bg-color: @colortheme_settings-bg,
@warn-color: @colortheme_settings-warn,
@color: @colortheme_settings-color
);
.alertify_main();
.sidebar-layout_main();
.limit-bar_main();
.creation_main();
// body
&.cp-app-settings {
.framework_min_main(
@bg-color: @colortheme_settings-bg,
@warn-color: @colortheme_settings-warn,
@color: @colortheme_settings-color
);
.sidebar-layout_main();
.limit-bar_main();
.creation_main();
display: flex;
flex-flow: column;
font: @colortheme_app-font;
#cp-sidebarlayout-container {
#cp-sidebarlayout-rightside {
input[type="checkbox"] {
@@ -57,6 +52,25 @@
width: @sidebar_button-width;
}
}
.cp-settings-autostore-radio {
display: flex;
align-items: center;
flex-wrap: wrap;
label {
margin-right: 40px;
}
}
.cp-settings-change-password {
[type="password"], [type="text"] {
width: @sidebar_button-width;
flex: unset;
}
button {
margin-top: 5px;
}
}
.cp-settings-drive-backup {
button {
span.fa {

Some files were not shown because too many files have changed in this diff Show More