Merge branch 'staging' into oo2
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 () {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
7
www/common/jquery-ui/jquery-ui.min.css
vendored
Normal file
7
www/common/jquery-ui/jquery-ui.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
www/common/jquery-ui/jquery-ui.min.js
vendored
Normal file
6
www/common/jquery-ui/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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
@@ -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(); }
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
51
www/common/onlyoffice/app-oo.less
Normal file
51
www/common/onlyoffice/app-oo.less
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
147
www/common/outer/login-block.js
Normal file
147
www/common/outer/login-block.js
Normal 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;
|
||||
});
|
||||
103
www/common/outer/noworker.js
Normal file
103
www/common/outer/noworker.js
Normal 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
|
||||
};
|
||||
});
|
||||
178
www/common/outer/serviceworker.js
Normal file
178
www/common/outer/serviceworker.js
Normal 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!');
|
||||
});
|
||||
|
||||
|
||||
180
www/common/outer/sharedworker.js
Normal file
180
www/common/outer/sharedworker.js
Normal 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);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
4
www/common/outer/testworker.js
Normal file
4
www/common/outer/testworker.js
Normal file
@@ -0,0 +1,4 @@
|
||||
if (!self.crypto && !self.msCrypto) {
|
||||
throw new Error("E_NOCRYPTO");
|
||||
}
|
||||
self.postMessage("OK");
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
103
www/common/outer/webworker.js
Normal file
103
www/common/outer/webworker.js
Normal 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);
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
151
www/common/outer/worker-channel.js
Normal file
151
www/common/outer/worker-channel.js
Normal 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 };
|
||||
});
|
||||
@@ -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
1024
www/common/proxy-manager.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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; }
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 = '';
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
1
www/common/tippy.min.js
vendored
1
www/common/tippy.min.js
vendored
File diff suppressed because one or more lines are too long
1
www/common/tippy/tippy.css
Normal file
1
www/common/tippy/tippy.css
Normal file
File diff suppressed because one or more lines are too long
1
www/common/tippy/tippy.min.js
vendored
Normal file
1
www/common/tippy/tippy.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user