Merge branch 'staging' into oo2
This commit is contained in:
@@ -9,9 +9,14 @@ define([
|
||||
'/common/common-thumbnail.js',
|
||||
'/common/wire.js',
|
||||
'/common/flat-dom.js',
|
||||
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
|
||||
'/common/media-tag.js',
|
||||
'/common/outer/login-block.js',
|
||||
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag, Block) {
|
||||
window.Hyperjson = Hyperjson;
|
||||
window.Sortify = Sortify;
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var assertions = 0;
|
||||
var failed = false;
|
||||
@@ -132,6 +137,40 @@ define([
|
||||
strungJSON(orig);
|
||||
});
|
||||
|
||||
HTML_list.forEach(function (sel) {
|
||||
var el = $(sel)[0];
|
||||
|
||||
var pred = function (el) {
|
||||
if (el.nodeName === 'DIV') {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var filter = function (x) {
|
||||
console.log(x);
|
||||
if (x[1]['class']) {
|
||||
x[1]['class'] = x[1]['class'].replace(/cke/g, '');
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
assert(function (cb) {
|
||||
// FlatDOM output
|
||||
var map = Flat.fromDOM(el, pred, filter);
|
||||
|
||||
// Hyperjson output
|
||||
var hj = Hyperjson.fromDOM(el, pred, filter);
|
||||
|
||||
var x = Flat.toDOM(map);
|
||||
var y = Hyperjson.toDOM(hj);
|
||||
|
||||
console.error(x.outerHTML);
|
||||
console.error(y.outerHTML);
|
||||
|
||||
cb(x.outerHTML === y.outerHTML);
|
||||
}, "Test equality of FlatDOM and HyperJSON");
|
||||
});
|
||||
|
||||
// check that old hashes parse correctly
|
||||
assert(function (cb) {
|
||||
//if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug
|
||||
@@ -223,6 +262,33 @@ define([
|
||||
hd.type === 'invite');
|
||||
}, "test support for invite urls");
|
||||
|
||||
// test support for V2
|
||||
assert(function (cb) {
|
||||
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
|
||||
var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
|
||||
return cb(parsed.hashData.version === 2 &&
|
||||
parsed.hashData.mode === "edit" &&
|
||||
parsed.hashData.type === "pad" &&
|
||||
parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
|
||||
secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
|
||||
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
|
||||
secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
|
||||
!parsed.hashData.present);
|
||||
}, "test support for version 2 hash failed to parse");
|
||||
assert(function (cb) {
|
||||
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
|
||||
var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
|
||||
return cb(parsed.hashData.version === 2 &&
|
||||
parsed.hashData.mode === "edit" &&
|
||||
parsed.hashData.type === "pad" &&
|
||||
parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
|
||||
secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
|
||||
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
|
||||
secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
|
||||
parsed.hashData.embed &&
|
||||
parsed.hashData.password);
|
||||
}, "test support for password in version 2 hash failed to parse");
|
||||
|
||||
assert(function (cb) {
|
||||
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
|
||||
var secret = Hash.parsePadUrl(url);
|
||||
@@ -234,6 +300,35 @@ define([
|
||||
!secret.hashData.present);
|
||||
}, "test support for ugly tracking query paramaters in url");
|
||||
|
||||
assert(function (cb) {
|
||||
var keys = Block.genkeys(Nacl.randomBytes(64));
|
||||
var hash = Block.getBlockHash(keys);
|
||||
var parsed = Block.parseBlockHash(hash);
|
||||
|
||||
cb(parsed &&
|
||||
parsed.keys.symmetric.length === keys.symmetric.length);
|
||||
}, 'parse a block hash');
|
||||
|
||||
assert(function (cb) {
|
||||
try {
|
||||
MediaTag(void 0).on('progress').on('decryption');
|
||||
return void cb(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return void cb(false);
|
||||
}
|
||||
}, 'check that MediaTag does the right thing when passed no value');
|
||||
|
||||
assert(function (cb) {
|
||||
try {
|
||||
MediaTag(document.createElement('div')).on('progress').on('decryption');
|
||||
return void cb(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return void cb(false);
|
||||
}
|
||||
}, 'check that MediaTag does the right thing when passed no value');
|
||||
|
||||
assert(function (cb) {
|
||||
// TODO
|
||||
return cb(true);
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) "../../customize/src/less2/include/framework.less";
|
||||
@import (reference) "../../customize/src/less2/include/browser.less";
|
||||
@import (reference) "../../customize/src/less2/include/markdown.less";
|
||||
@import (reference) "../../customize/src/less2/include/framework.less";
|
||||
|
||||
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_code-bg,
|
||||
@warn-color: @colortheme_code-warn,
|
||||
@color: @colortheme_code-color
|
||||
);
|
||||
|
||||
// body
|
||||
&.cp-app-code {
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_code-bg,
|
||||
@warn-color: @colortheme_code-warn,
|
||||
@color: @colortheme_code-color
|
||||
);
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
|
||||
@@ -37,6 +37,8 @@ define([
|
||||
'cm/addon/fold/comment-fold',
|
||||
'cm/addon/display/placeholder',
|
||||
|
||||
'less!/code/app-code.less'
|
||||
|
||||
], function (
|
||||
$,
|
||||
DiffMd,
|
||||
@@ -108,7 +110,7 @@ define([
|
||||
return;
|
||||
}
|
||||
$previewContainer.removeClass('cp-app-code-preview-isempty');
|
||||
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
|
||||
DiffMd.apply(DiffMd.render(editor.getValue()), $preview, framework._.sfCommon);
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
var drawPreview = Util.throttle(function () {
|
||||
@@ -331,13 +333,11 @@ define([
|
||||
dropArea: $('.CodeMirror'),
|
||||
body: $('body'),
|
||||
onUploaded: function (ev, data) {
|
||||
//var cursor = editor.getCursor();
|
||||
//var cleanName = data.name.replace(/[\[\]]/g, '');
|
||||
//var text = '';
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../../customize/src/less2/include/avatar.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_friends-bg,
|
||||
@warn-color: @colortheme_friends-warn,
|
||||
@color: @colortheme_friends-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
@import (reference) '../../customize/src/less2/include/avatar.less';
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
// body
|
||||
&.cp-app-contacts {
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_friends-bg,
|
||||
@warn-color: @colortheme_friends-warn,
|
||||
@color: @colortheme_friends-color
|
||||
);
|
||||
|
||||
@keyframes example {
|
||||
0% {
|
||||
background: rgba(0,0,0,0.1);
|
||||
|
||||
@@ -11,8 +11,8 @@ define([
|
||||
'/common/common-interface.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/contacts/app-contacts.less',
|
||||
], function (
|
||||
$,
|
||||
Crypto,
|
||||
|
||||
@@ -247,7 +247,7 @@ define([
|
||||
|
||||
// insert a newline if they're holding either
|
||||
var val = this.value;
|
||||
var start = this.selectionState;
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
|
||||
if (![start,end].some(function (x) {
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../../customize/src/less2/include/tools.less';
|
||||
@import (once) '../../customize/src/less2/include/tokenfield.less';
|
||||
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tokenfield_main();
|
||||
@import (reference) '../../customize/src/less2/include/tokenfield.less';
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
// body
|
||||
&.cp-app-debug {
|
||||
.tokenfield_main();
|
||||
.framework_min_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
|
||||
@@ -15,8 +15,8 @@ define([
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/debug/app-debug.less',
|
||||
], function (
|
||||
$,
|
||||
Crypto,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
.cp-app-drive-context { display: none; }
|
||||
.cp-contextmenu { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-drive">
|
||||
|
||||
1103
www/drive/inner.js
1103
www/drive/inner.js
File diff suppressed because it is too large
Load Diff
@@ -37,9 +37,25 @@ define([
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var getSecrets = function (Cryptpad, Utils, cb) {
|
||||
var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() ||
|
||||
Utils.LocalStore.getFSHash();
|
||||
cb(null, Utils.Hash.getSecrets('drive', hash));
|
||||
var hash = window.location.hash.slice(1);
|
||||
var secret = Utils.Hash.getSecrets('drive', hash);
|
||||
if (hash) {
|
||||
// Add a shared folder!
|
||||
// XXX password?
|
||||
Cryptpad.addSharedFolder(secret, function (id) {
|
||||
window.CryptPad_newSharedFolder = id;
|
||||
// Update the hash in the address bar
|
||||
var ohc = window.onhashchange;
|
||||
window.onhashchange = function () {};
|
||||
window.location.hash = "";
|
||||
window.onhashchange = ohc;
|
||||
ohc({reset:true});
|
||||
cb(null, secret);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// No password for drive
|
||||
cb(null, secret);
|
||||
};
|
||||
var addRpc = function (sframeChan, Cryptpad, Utils) {
|
||||
sframeChan.on('EV_BURN_ANON_DRIVE', function () {
|
||||
@@ -51,7 +67,16 @@ define([
|
||||
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
|
||||
Cryptpad.userObjectCommand(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_DRIVE_RESTORE', function (data, cb) {
|
||||
Cryptpad.restoreDrive(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
|
||||
if (data && data.sharedFolder) {
|
||||
Cryptpad.getSharedFolder(data.sharedFolder, function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Cryptpad.getUserObject(function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
@@ -81,8 +106,10 @@ define([
|
||||
SFCommonO.start({
|
||||
getSecrets: getSecrets,
|
||||
noHash: true,
|
||||
noRealtime: true,
|
||||
driveEvents: true,
|
||||
addRpc: addRpc
|
||||
addRpc: addRpc,
|
||||
isDrive: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ define([
|
||||
|
||||
module.test = function (assert) {
|
||||
var config = {
|
||||
pinPads: Cryptpad.pinPads,
|
||||
outer: true,
|
||||
workgroup: false,
|
||||
testMode: true,
|
||||
loggedIn: false
|
||||
@@ -237,7 +237,8 @@ define([
|
||||
&& typeof files.template[0] === "number"
|
||||
&& typeof files.filesData[files.template[0]] === "object"
|
||||
&& !files.filesData[files.template[0]].filename
|
||||
&& files.filesData[files.template[0]].href === href3
|
||||
&& !files.filesData[files.template[0]].href
|
||||
&& files.filesData[files.template[0]].roHref === href3
|
||||
&& typeof fileId2 === "number"
|
||||
&& typeof files.filesData[fileId2] === "object"
|
||||
&& files.filesData[fileId2].filename === "Trash"
|
||||
@@ -392,11 +393,6 @@ define([
|
||||
console.log("DRIVE operations: rename");
|
||||
return cb();
|
||||
}
|
||||
fo.replace(href1, href2);
|
||||
if (fo.getFileData(id1).href !== href2) {
|
||||
console.log("DRIVE operations: replace");
|
||||
return cb();
|
||||
}
|
||||
|
||||
cb(true);
|
||||
}, "DRIVE operations");
|
||||
|
||||
@@ -1,160 +1,152 @@
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../../customize/src/less2/include/tokenfield.less';
|
||||
@import (reference) '../../customize/src/less2/include/tokenfield.less';
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_file-bg,
|
||||
@warn-color: @colortheme_file-warn,
|
||||
@color: @colortheme_file-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tokenfield_main();
|
||||
&.cp-app-file {
|
||||
|
||||
@button-border: 2px;
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_file-bg,
|
||||
@warn-color: @colortheme_file-warn,
|
||||
@color: @colortheme_file-color
|
||||
);
|
||||
.tokenfield_main();
|
||||
|
||||
/*html, body {
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}*/
|
||||
@button-border: 2px;
|
||||
|
||||
// body
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
#cp-app-file-content {
|
||||
flex: 1;
|
||||
// body
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-flow: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#cp-app-file-content.ready {
|
||||
//background: url('/customize/bg3.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#cp-app-file-upfile, #cp-app-file-dlfile {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: @button-border solid black;
|
||||
}
|
||||
|
||||
.cp-app-file-input {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
media-tag {
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: ~"calc(100vh - 96px)";
|
||||
}
|
||||
}
|
||||
|
||||
#cp-app-file-upload-form, #cp-app-file-download-form {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
position: relative;
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 80vw;
|
||||
label {
|
||||
line-height: ~"calc(50vh - 20px)";
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 50vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
#cp-app-file-download-form {
|
||||
label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
span {
|
||||
width: 50vh;
|
||||
max-width: 80vw;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-file-hovering {
|
||||
background-color: rgba(255, 0, 115, 0.5) !important;
|
||||
}
|
||||
|
||||
.cp-app-file-block {
|
||||
display: block;
|
||||
}
|
||||
.cp-app-file-hidden {
|
||||
display: none;
|
||||
}
|
||||
.cp-app-file-input + label {
|
||||
//border: 2px solid black;
|
||||
//background-color: rgba(50, 50, 50, .10);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cp-app-file-input:focus + label,
|
||||
.cp-app-file-input + label:hover {
|
||||
//background-color: rgba(50, 50, 50, 0.30);
|
||||
}
|
||||
|
||||
#cp-app-file-dlprogress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
|
||||
|
||||
transition: width 200ms;
|
||||
width: 0%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background-color: rgba(255, 0, 115, 0.75);
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cp-app-file-download-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-flow: column;
|
||||
media-tag {
|
||||
#cp-app-file-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-width: 100vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&> * {
|
||||
max-height: 100%;
|
||||
align-items: center;
|
||||
flex-flow: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#cp-app-file-content.ready {
|
||||
//background: url('/customize/bg3.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#cp-app-file-upfile, #cp-app-file-dlfile {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: @button-border solid black;
|
||||
}
|
||||
|
||||
.cp-app-file-input {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
media-tag {
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: ~"calc(100vh - 96px)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#cp-app-file-upload-form, #cp-app-file-download-form {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
position: relative;
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 80vw;
|
||||
label {
|
||||
line-height: ~"calc(50vh - 20px)";
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 50vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
#cp-app-file-download-form {
|
||||
label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
span {
|
||||
width: 50vh;
|
||||
max-width: 80vw;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-file-hovering {
|
||||
background-color: rgba(255, 0, 115, 0.5) !important;
|
||||
}
|
||||
|
||||
.cp-app-file-block {
|
||||
display: block;
|
||||
}
|
||||
.cp-app-file-hidden {
|
||||
display: none;
|
||||
}
|
||||
.cp-app-file-input + label {
|
||||
//border: 2px solid black;
|
||||
//background-color: rgba(50, 50, 50, .10);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cp-app-file-input:focus + label,
|
||||
.cp-app-file-input + label:hover {
|
||||
//background-color: rgba(50, 50, 50, 0.30);
|
||||
}
|
||||
|
||||
#cp-app-file-dlprogress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
|
||||
|
||||
transition: width 200ms;
|
||||
width: 0%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background-color: rgba(255, 0, 115, 0.75);
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cp-app-file-download-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-flow: column;
|
||||
media-tag {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-width: 100vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&> * {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ define([
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
], function () {
|
||||
var Nacl = window.nacl;
|
||||
var PARANOIA = true;
|
||||
//var PARANOIA = true;
|
||||
|
||||
var plainChunkLength = 128 * 1024;
|
||||
var cypherChunkLength = 131088;
|
||||
@@ -36,24 +36,11 @@ define([
|
||||
var increment = function (N) {
|
||||
var l = N.length;
|
||||
while (l-- > 1) {
|
||||
if (PARANOIA) {
|
||||
if (typeof(N[l]) !== 'number') {
|
||||
throw new Error('E_UNSAFE_TYPE');
|
||||
}
|
||||
if (N[l] > 255) {
|
||||
throw new Error('E_OUT_OF_BOUNDS');
|
||||
}
|
||||
}
|
||||
/* jshint probably suspects this is unsafe because we lack types
|
||||
/* our linter suspects this is unsafe because we lack types
|
||||
but as long as this is only used on nonces, it should be safe */
|
||||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
|
||||
N[l] = 0;
|
||||
|
||||
// you don't need to worry about this running out.
|
||||
// you'd need a REAAAALLY big file
|
||||
if (l === 0) {
|
||||
throw new Error('E_NONCE_TOO_LARGE');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,6 +65,7 @@ define([
|
||||
xhr.setRequestHeader('Range', 'bytes=0-1');
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onerror= function () { return CB('XHR_ERROR'); };
|
||||
xhr.onload = function () {
|
||||
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
|
||||
var res = new Uint8Array(xhr.response);
|
||||
@@ -160,19 +148,21 @@ define([
|
||||
}
|
||||
|
||||
var takeChunk = function (cb) {
|
||||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||
var end = start + cypherChunkLength;
|
||||
i++;
|
||||
var box = new Uint8Array(u8.subarray(start, end));
|
||||
setTimeout(function () {
|
||||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||
var end = start + cypherChunkLength;
|
||||
i++;
|
||||
var box = new Uint8Array(u8.subarray(start, end));
|
||||
|
||||
// decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
increment(nonce);
|
||||
// decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
increment(nonce);
|
||||
|
||||
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
|
||||
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
|
||||
|
||||
_progress(end);
|
||||
cb(void 0, plaintext);
|
||||
_progress(end);
|
||||
cb(void 0, plaintext);
|
||||
});
|
||||
};
|
||||
|
||||
var chunks = [];
|
||||
@@ -219,7 +209,7 @@ define([
|
||||
|
||||
var state = 0;
|
||||
var next = function (cb) {
|
||||
if (state === 2) { return void cb(); }
|
||||
if (state === 2) { return void setTimeout(cb); }
|
||||
|
||||
var start;
|
||||
var end;
|
||||
@@ -238,7 +228,9 @@ define([
|
||||
.concat(slice(box)));
|
||||
state++;
|
||||
|
||||
return void cb(void 0, prefixed);
|
||||
return void setTimeout(function () {
|
||||
cb(void 0, prefixed);
|
||||
});
|
||||
}
|
||||
|
||||
// encrypt the rest of the file...
|
||||
@@ -253,7 +245,9 @@ define([
|
||||
// regular data is done
|
||||
if (i * plainChunkLength >= u8.length) { state = 2; }
|
||||
|
||||
return void cb(void 0, box);
|
||||
setTimeout(function () {
|
||||
cb(void 0, box);
|
||||
});
|
||||
};
|
||||
|
||||
return next;
|
||||
|
||||
@@ -16,8 +16,8 @@ define([
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/file/app-file.less',
|
||||
|
||||
], function (
|
||||
$,
|
||||
@@ -37,6 +37,9 @@ define([
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var APP = window.APP = {};
|
||||
MediaTag.setDefaultConfig('download', {
|
||||
text: Messages.download_mt_button
|
||||
});
|
||||
|
||||
var andThen = function (common) {
|
||||
var $appContainer = $('#cp-app-file-content');
|
||||
@@ -54,16 +57,14 @@ define([
|
||||
|
||||
var uploadMode = false;
|
||||
var secret;
|
||||
var hexFileName;
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
|
||||
if (!priv.filehash) {
|
||||
uploadMode = true;
|
||||
} else {
|
||||
secret = Hash.getSecrets('file', priv.filehash);
|
||||
secret = Hash.getSecrets('file', priv.filehash, priv.password);
|
||||
if (!secret.keys) { throw new Error("You need a hash"); }
|
||||
hexFileName = Util.base64ToHex(secret.channel);
|
||||
}
|
||||
|
||||
var Title = common.createTitle({});
|
||||
@@ -86,28 +87,41 @@ define([
|
||||
toolbar.$rightside.html('');
|
||||
|
||||
if (!uploadMode) {
|
||||
var hexFileName = secret.channel;
|
||||
var src = Hash.getBlobPathFromHex(hexFileName);
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var key = Nacl.util.decodeBase64(cryptKey);
|
||||
var key = secret.keys && secret.keys.cryptKey;
|
||||
var cryptKey = Nacl.util.encodeBase64(key);
|
||||
|
||||
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
|
||||
if (e) {
|
||||
if (e === 'XHR_ERROR') {
|
||||
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable);
|
||||
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () {
|
||||
common.gotoURL('/file/');
|
||||
});
|
||||
}
|
||||
return void console.error(e);
|
||||
}
|
||||
|
||||
// Add pad attributes when the file is saved in the drive
|
||||
Title.onTitleChange(function () {
|
||||
var owners = metadata.owners;
|
||||
if (owners) {
|
||||
common.setPadAttribute('owners', owners);
|
||||
}
|
||||
common.setPadAttribute('fileType', metadata.type);
|
||||
});
|
||||
|
||||
// Save to the drive or update the acces time
|
||||
var title = document.title = metadata.name;
|
||||
Title.updateTitle(title || Title.defaultTitle);
|
||||
|
||||
toolbar.addElement(['pageTitle'], {pageTitle: title});
|
||||
toolbar.$rightside.append(common.createButton('forget', true));
|
||||
toolbar.$rightside.append(common.createButton('properties', true));
|
||||
if (common.isLoggedIn()) {
|
||||
toolbar.$rightside.append(common.createButton('hashtag', true));
|
||||
}
|
||||
|
||||
|
||||
common.setPadAttribute('fileType', metadata.type);
|
||||
|
||||
var displayFile = function (ev, sizeMb, CB) {
|
||||
var called_back;
|
||||
var cb = function (e) {
|
||||
@@ -117,38 +131,21 @@ define([
|
||||
};
|
||||
|
||||
var $mt = $dlview.find('media-tag');
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var hexFileName = Util.base64ToHex(secret.channel);
|
||||
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
|
||||
$mt.attr('src', src);
|
||||
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||
|
||||
var rightsideDisplayed = false;
|
||||
$(window.document).on('decryption', function (e) {
|
||||
/* FIXME
|
||||
we're listening for decryption events and assuming that only
|
||||
the main media-tag exists. In practice there is also your avatar
|
||||
and there could be other things in the future, so we should
|
||||
figure out a generic way target media-tag decryption events.
|
||||
*/
|
||||
var decrypted = e.originalEvent;
|
||||
if (decrypted.callback) {
|
||||
decrypted.callback();
|
||||
}
|
||||
|
||||
MediaTag($mt[0]).on('complete', function (decrypted) {
|
||||
$dlview.show();
|
||||
$dlform.hide();
|
||||
var $dlButton = $dlview.find('media-tag button');
|
||||
if (ev) { $dlButton.click(); }
|
||||
$dlButton.addClass('btn btn-success');
|
||||
var text = Messages.download_mt_button + '<br>';
|
||||
text += '<b>' + Util.fixHTML(title) + '</b><br>';
|
||||
text += '<em>' + Messages._getKey('formattedMB', [sizeMb]) + '</em>';
|
||||
$dlButton.html(text);
|
||||
|
||||
if (!rightsideDisplayed) {
|
||||
toolbar.$rightside
|
||||
.append(common.createButton('export', true, {}, function () {
|
||||
saveAs(decrypted.blob, decrypted.metadata.name);
|
||||
saveAs(decrypted.content, decrypted.metadata.name);
|
||||
}));
|
||||
rightsideDisplayed = true;
|
||||
}
|
||||
@@ -171,42 +168,12 @@ define([
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
//UI.alert(error.message);
|
||||
cb(error.message);
|
||||
})
|
||||
.on('decryptionProgress', function (e) {
|
||||
var progress = e.originalEvent;
|
||||
var p = progress.percent +'%';
|
||||
}).on('progress', function (data) {
|
||||
var p = data.progress +'%';
|
||||
$progress.width(p);
|
||||
}).on('error', function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
/**
|
||||
* Allowed mime types that have to be set for a rendering after a decryption.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
var allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
'application/dash+xml',
|
||||
'download'
|
||||
];
|
||||
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
};
|
||||
|
||||
var todoBigFile = function (sizeMb) {
|
||||
@@ -233,8 +200,7 @@ define([
|
||||
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
|
||||
$dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick);
|
||||
};
|
||||
var href = priv.origin + priv.pathname + priv.filehash;
|
||||
common.getFileSize(href, function (e, data) {
|
||||
common.getFileSize(hexFileName, function (e, data) {
|
||||
if (e) {
|
||||
return void UI.errorLoadingScreen(e);
|
||||
}
|
||||
@@ -263,7 +229,7 @@ define([
|
||||
dropArea: $form,
|
||||
hoverArea: $label,
|
||||
body: $body,
|
||||
keepTable: true // Don't fadeOut the tbale with the uploaded files
|
||||
keepTable: true // Don't fadeOut the table with the uploaded files
|
||||
};
|
||||
|
||||
var FM = common.createFileManager(fmConfig);
|
||||
|
||||
@@ -1,76 +1,85 @@
|
||||
@import (once) '../../customize/src/less2/include/colortheme-all.less';
|
||||
@import (once) '../../customize/src/less2/include/modal.less';
|
||||
@import (once) '../../customize/src/less2/include/icon-colors.less';
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (reference) '../../customize/src/less2/include/colortheme-all.less';
|
||||
@import (reference) '../../customize/src/less2/include/modal.less';
|
||||
@import (reference) '../../customize/src/less2/include/icon-colors.less';
|
||||
@import (reference) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (reference) '../../customize/src/less2/include/alertify.less';
|
||||
@import (reference) '../../customize/src/less2/include/tippy.less';
|
||||
@import (reference) '../../customize/src/less2/include/checkmark.less';
|
||||
@import (reference) '../../customize/src/less2/include/password-input.less';
|
||||
|
||||
.iconColors_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
&.cp-app-filepicker {
|
||||
.iconColors_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tippy_main();
|
||||
.checkmark_main(20px);
|
||||
.password_main();
|
||||
.modal_main();
|
||||
|
||||
#cp-filepicker-dialog {
|
||||
display: none;
|
||||
.cp-modal {
|
||||
.cp-filepicker-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cp-filepicker-content-element {
|
||||
@darker: darken(@colortheme_modal-fg, 30%);
|
||||
|
||||
width: 125px;
|
||||
//min-width: 200px;
|
||||
//height: 1em;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
|
||||
display: inline-flex;
|
||||
flex-flow: column;
|
||||
|
||||
box-sizing: content-box;
|
||||
|
||||
text-align: left;
|
||||
line-height: 1em;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: #111;
|
||||
color: @darker;
|
||||
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
color: @colortheme_modal-fg;
|
||||
#cp-filepicker-dialog {
|
||||
display: none;
|
||||
.cp-modal {
|
||||
.cp-filepicker-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
.cp-filepicker-content-element {
|
||||
@darker: darken(@colortheme_modal-fg, 30%);
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
background: #fff;
|
||||
}
|
||||
width: 125px;
|
||||
//min-width: 200px;
|
||||
//height: 1em;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
|
||||
.cp-filepicker-content-element-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-top: 5px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fa {
|
||||
display: inline-flex;
|
||||
flex-flow: column;
|
||||
|
||||
box-sizing: content-box;
|
||||
|
||||
text-align: left;
|
||||
line-height: 1em;
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 70px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
|
||||
background-color: @colortheme_modal-bg;
|
||||
box-shadow: 2px 2px 5px #000;
|
||||
color: @darker;
|
||||
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
color: @colortheme_modal-fg;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cp-filepicker-content-element-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-top: 5px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fa {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 70px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,8 +11,8 @@ define([
|
||||
'/customize/messages.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/filepicker/app-filepicker.less',
|
||||
], function (
|
||||
$,
|
||||
Crypto,
|
||||
@@ -40,13 +40,14 @@ define([
|
||||
var parsed = Hash.parsePadUrl(data.url);
|
||||
hideFileDialog();
|
||||
if (parsed.type === 'file') {
|
||||
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var secret = Hash.getSecrets('file', parsed.hash, data.password);
|
||||
var src = Hash.getBlobPathFromHex(secret.channel);
|
||||
var key = Hash.encodeBase64(secret.keys.cryptKey);
|
||||
sframeChan.event("EV_FILE_PICKED", {
|
||||
type: parsed.type,
|
||||
src: src,
|
||||
name: data.name,
|
||||
key: parsed.hashData.key
|
||||
key: key
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -68,8 +69,8 @@ define([
|
||||
APP.FM = common.createFileManager(fmConfig);
|
||||
|
||||
// Create file picker
|
||||
var onSelect = function (url, name) {
|
||||
onFilePicked({url: url, name: name});
|
||||
var onSelect = function (url, name, password) {
|
||||
onFilePicked({url: url, name: name, password: password});
|
||||
};
|
||||
var data = {
|
||||
FM: APP.FM
|
||||
@@ -134,11 +135,13 @@ define([
|
||||
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
|
||||
.appendTo($span);
|
||||
$span.click(function () {
|
||||
if (typeof onSelect === "function") { onSelect(data.href, name); }
|
||||
if (typeof onSelect === "function") {
|
||||
onSelect(data.href, name, data.password);
|
||||
}
|
||||
});
|
||||
|
||||
// Add thumbnail if it exists
|
||||
common.displayThumbnail(data.href, $span);
|
||||
common.displayThumbnail(data.href, data.channel, data.password, $span);
|
||||
});
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
174
www/kanban/app-kanban.less
Normal file
174
www/kanban/app-kanban.less
Normal file
@@ -0,0 +1,174 @@
|
||||
@import (reference) "../../customize/src/less2/include/browser.less";
|
||||
@import (reference) "../../customize/src/less2/include/framework.less";
|
||||
@import (reference) "../../customize/src/less2/include/tools.less";
|
||||
|
||||
// body
|
||||
&.cp-app-kanban {
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_kanban-bg,
|
||||
@warn-color: @colortheme_kanban-warn,
|
||||
@color: @colortheme_kanban-color
|
||||
);
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
|
||||
#cp-app-kanban-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
#cp-app-kanban-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#cp-app-kanban-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.kanban-container-outer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: -webkit-min-content;
|
||||
min-height: min-content;
|
||||
.kanban-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 5px 10px 10px;
|
||||
}
|
||||
|
||||
.kanban-board {
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 13px 10px;
|
||||
.kanban-title-board {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#kanban-edit {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#kanban-edit {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
@button-size: 50px;
|
||||
#kanban-addboard {
|
||||
margin: 30px;
|
||||
border: 1px solid;
|
||||
width: @button-size;
|
||||
height: @button-size;
|
||||
line-height: @button-size;
|
||||
text-align: center;
|
||||
background: @colortheme_kanban-bg;
|
||||
align-self: flex-start;
|
||||
font-size: 30px;
|
||||
cursor: pointer;
|
||||
.tools_unselectable();
|
||||
}
|
||||
|
||||
.kanban-remove-item {
|
||||
padding: 0 0.5em;
|
||||
visibility: hidden;
|
||||
}
|
||||
.kanban-item:hover {
|
||||
.kanban-remove-item {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-additem {
|
||||
float: right;
|
||||
background: #EEE;
|
||||
padding: 5px .5rem 4px;
|
||||
line-height: 1;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-header-yellow {
|
||||
background: #FC3;
|
||||
}
|
||||
|
||||
.kanban-header-orange {
|
||||
background: #F91;
|
||||
}
|
||||
|
||||
.kanban-header-blue {
|
||||
background: #0AC;
|
||||
}
|
||||
|
||||
.kanban-header-red {
|
||||
background: #E43;
|
||||
}
|
||||
|
||||
.kanban-header-green {
|
||||
background: #8C4;
|
||||
}
|
||||
|
||||
.kanban-header-purple {
|
||||
background: #c851ff;
|
||||
}
|
||||
|
||||
.kanban-header-cyan {
|
||||
background: #00ffff;
|
||||
}
|
||||
|
||||
.kanban-header-lightgreen {
|
||||
background: #c3ff5b;
|
||||
}
|
||||
|
||||
.kanban-header-lightblue {
|
||||
background: #adeeff;
|
||||
}
|
||||
|
||||
@media (max-width: @browser_media-medium-screen) {
|
||||
#cp-app-kanban-container {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.cp-app-readonly {
|
||||
.kanban-item, .kanban-title-board {
|
||||
cursor: default !important;
|
||||
.tools_unselectable();
|
||||
}
|
||||
.kanban-title-button, #kanban-addboard, .kanban-remove-item, .kanban-additem {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
www/kanban/index.html
Normal file
37
www/kanban/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#sbox-iframe {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<iframe id="sbox-iframe">
|
||||
</iframe>
|
||||
</body>
|
||||
</html>
|
||||
23
www/kanban/inner.html
Normal file
23
www/kanban/inner.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp-app-noscroll">
|
||||
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<script async data-bootload="/kanban/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="cp-app-kanban">
|
||||
<div id="cme_toolbox" class="cp-toolbar-container"></div>
|
||||
<div id="cp-app-kanban-editor">
|
||||
<div id="cp-app-kanban-container">
|
||||
<div id="cp-app-kanban-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
418
www/kanban/inner.js
Normal file
418
www/kanban/inner.js
Normal file
@@ -0,0 +1,418 @@
|
||||
define([
|
||||
'jquery',
|
||||
'json.sortify',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/sframe-app-framework.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/modes.js',
|
||||
'/customize/messages.js',
|
||||
'/kanban/jkanban.js',
|
||||
'css!/kanban/jkanban.css',
|
||||
|
||||
'less!/kanban/app-kanban.less'
|
||||
], function (
|
||||
$,
|
||||
Sortify,
|
||||
nThen,
|
||||
SFCommon,
|
||||
Framework,
|
||||
Util,
|
||||
Hash,
|
||||
UI,
|
||||
Modes,
|
||||
Messages)
|
||||
{
|
||||
|
||||
var verbose = function (x) { console.log(x); };
|
||||
verbose = function () {}; // comment out to enable verbose logging
|
||||
|
||||
var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue'];
|
||||
|
||||
var addRemoveItemButton = function (framework, kanban) {
|
||||
if (!kanban) { return; }
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
var $container = $(kanban.element);
|
||||
$container.find('.kanban-remove-item').remove();
|
||||
$container.find('.kanban-board .kanban-item').each(function (i, el) {
|
||||
var pos = kanban.findElementPosition(el);
|
||||
var board = kanban.options.boards.find(function (b) {
|
||||
return b.id === $(el.parentNode.parentNode).attr('data-id');
|
||||
});
|
||||
$('<button>', {
|
||||
'class': 'kanban-remove-item btn btn-default fa fa-times',
|
||||
title: Messages.kanban_removeItem
|
||||
}).click(function (e) {
|
||||
e.stopPropagation();
|
||||
UI.confirm(Messages.kanban_removeItemConfirm, function (yes) {
|
||||
if (!yes) { return; }
|
||||
board.item.splice(pos, 1);
|
||||
$(el).remove();
|
||||
kanban.onChange();
|
||||
});
|
||||
}).appendTo($(el));
|
||||
});
|
||||
};
|
||||
|
||||
// Kanban code
|
||||
var initKanban = function (framework, boards) {
|
||||
var defaultBoards = [{
|
||||
"id": "todo",
|
||||
"title": Messages.kanban_todo,
|
||||
"color": "blue",
|
||||
"item": [{
|
||||
"title": Messages._getKey('kanban_item', [1])
|
||||
}, {
|
||||
"title": Messages._getKey('kanban_item', [2])
|
||||
}]
|
||||
}, {
|
||||
"id": "working",
|
||||
"title": Messages.kanban_working,
|
||||
"color": "orange",
|
||||
"item": [{
|
||||
"title": Messages._getKey('kanban_item', [3])
|
||||
}, {
|
||||
"title": Messages._getKey('kanban_item', [4])
|
||||
}]
|
||||
}, {
|
||||
"id": "done",
|
||||
"title": Messages.kanban_done,
|
||||
"color": "green",
|
||||
"item": [{
|
||||
"title": Messages._getKey('kanban_item', [5])
|
||||
}, {
|
||||
"title": Messages._getKey('kanban_item', [6])
|
||||
}]
|
||||
}];
|
||||
|
||||
if (!boards) {
|
||||
verbose("Initializing with default boards content");
|
||||
boards = defaultBoards;
|
||||
} else {
|
||||
verbose("Initializing with boards content " + boards);
|
||||
}
|
||||
|
||||
// Remove any existing elements
|
||||
$(".kanban-container-outer").remove();
|
||||
|
||||
var getInput = function () {
|
||||
return $('<input>', {
|
||||
'type': 'text',
|
||||
'id': 'kanban-edit',
|
||||
'size': '30'
|
||||
}).click(function (e) { e.stopPropagation(); });
|
||||
};
|
||||
|
||||
var kanban = new window.jKanban({
|
||||
element: '#cp-app-kanban-content',
|
||||
gutter: '15px',
|
||||
widthBoard: '300px',
|
||||
buttonContent: '❌',
|
||||
colors: COLORS,
|
||||
readOnly: framework.isReadOnly(),
|
||||
onChange: function () {
|
||||
verbose("Board object has changed");
|
||||
framework.localChange();
|
||||
if (kanban) {
|
||||
addRemoveItemButton(framework, kanban);
|
||||
}
|
||||
},
|
||||
click: function (el) {
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
if (kanban.inEditMode) {
|
||||
$(el).focus();
|
||||
verbose("An edit is already active");
|
||||
//return;
|
||||
}
|
||||
kanban.inEditMode = true;
|
||||
$(el).find('button').remove();
|
||||
var name = $(el).text();
|
||||
$(el).html('');
|
||||
var $input = getInput().val(name).appendTo(el).focus();
|
||||
$input[0].select();
|
||||
var save = function () {
|
||||
// Store the value
|
||||
var name = $input.val();
|
||||
// Remove the input
|
||||
$(el).text(name);
|
||||
// Save the value for the correct board
|
||||
var board = $(el.parentNode.parentNode).attr("data-id");
|
||||
var pos = kanban.findElementPosition(el);
|
||||
kanban.getBoardJSON(board).item[pos].title = name;
|
||||
kanban.onChange();
|
||||
// Unlock edit mode
|
||||
kanban.inEditMode = false;
|
||||
};
|
||||
$input.blur(save);
|
||||
$input.keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
save();
|
||||
return;
|
||||
}
|
||||
if (e.which === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(el).text(name);
|
||||
kanban.inEditMode = false;
|
||||
addRemoveItemButton(framework, kanban);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
boardTitleClick: function (el, e) {
|
||||
e.stopPropagation();
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
if (kanban.inEditMode) {
|
||||
$(el).focus();
|
||||
verbose("An edit is already active");
|
||||
//return;
|
||||
}
|
||||
kanban.inEditMode = true;
|
||||
var name = $(el).text();
|
||||
$(el).html('');
|
||||
var $input = getInput().val(name).appendTo(el).focus();
|
||||
$input[0].select();
|
||||
var save = function () {
|
||||
// Store the value
|
||||
var name = $input.val();
|
||||
// Remove the input
|
||||
$(el).text(name);
|
||||
// Save the value for the correct board
|
||||
var board = $(el.parentNode.parentNode).attr("data-id");
|
||||
kanban.getBoardJSON(board).title = name;
|
||||
kanban.onChange();
|
||||
// Unlock edit mode
|
||||
kanban.inEditMode = false;
|
||||
};
|
||||
$input.blur(save);
|
||||
$input.keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
save();
|
||||
return;
|
||||
}
|
||||
if (e.which === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(el).text(name);
|
||||
kanban.inEditMode = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
colorClick: function (el) {
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
verbose("in color click");
|
||||
var board = $(el.parentNode).attr("data-id");
|
||||
var boardJSON = kanban.getBoardJSON(board);
|
||||
var currentColor = boardJSON.color;
|
||||
verbose("Current color " + currentColor);
|
||||
var index = kanban.options.colors.findIndex(function (element) {
|
||||
return (element === currentColor);
|
||||
}) + 1;
|
||||
verbose("Next index " + index);
|
||||
if (index >= kanban.options.colors.length) { index = 0; }
|
||||
var nextColor = kanban.options.colors[index];
|
||||
verbose("Next color " + nextColor);
|
||||
boardJSON.color = nextColor;
|
||||
$(el).removeClass("kanban-header-" + currentColor);
|
||||
$(el).addClass("kanban-header-" + nextColor);
|
||||
kanban.onChange();
|
||||
},
|
||||
buttonClick: function (el, boardId, e) {
|
||||
e.stopPropagation();
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
UI.confirm(Messages.kanban_deleteBoard, function (yes) {
|
||||
if (!yes) { return; }
|
||||
verbose("Delete board");
|
||||
//var boardName = $(el.parentNode.parentNode).attr("data-id");
|
||||
for (var index in kanban.options.boards) {
|
||||
if (kanban.options.boards[index].id === boardId) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
kanban.options.boards.splice(index, 1);
|
||||
kanban.removeBoard(boardId);
|
||||
kanban.onChange();
|
||||
});
|
||||
},
|
||||
addItemClick: function (el) {
|
||||
if (framework.isReadOnly() || framework.isLocked()) { return; }
|
||||
if (kanban.inEditMode) {
|
||||
$(el).focus();
|
||||
verbose("An edit is already active");
|
||||
//return;
|
||||
}
|
||||
kanban.inEditMode = true;
|
||||
// create a form to enter element
|
||||
var boardId = $(el.parentNode.parentNode).attr("data-id");
|
||||
var $item = $('<div>', {'class': 'kanban-item'});
|
||||
var $input = getInput().val(name).appendTo($item);
|
||||
kanban.addForm(boardId, $item[0]);
|
||||
$input.focus();
|
||||
var save = function () {
|
||||
$item.remove();
|
||||
kanban.inEditMode = false;
|
||||
if (!$input.val()) { return; }
|
||||
kanban.addElement(boardId, {
|
||||
"title": $input.val(),
|
||||
});
|
||||
};
|
||||
$input.blur(save);
|
||||
$input.keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
save();
|
||||
return;
|
||||
}
|
||||
if (e.which === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$item.remove();
|
||||
kanban.inEditMode = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
addItemButton: true,
|
||||
boards: boards
|
||||
});
|
||||
|
||||
var addBoardDefault = document.getElementById('kanban-addboard');
|
||||
$(addBoardDefault).attr('title', Messages.kanban_addBoard);
|
||||
addBoardDefault.addEventListener('click', function () {
|
||||
if (framework.isReadOnly()) { return; }
|
||||
var counter = 1;
|
||||
|
||||
// Get the new board id
|
||||
var boardExists = function (b) { return b.id === "board" + counter; };
|
||||
while (kanban.options.boards.some(boardExists)) { counter++; }
|
||||
|
||||
kanban.addBoards([{
|
||||
"id": "board" + counter,
|
||||
"title": Messages.kanban_newBoard,
|
||||
"color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color
|
||||
"item": [{
|
||||
"title": Messages._getKey('kanban_item', [1]),
|
||||
}]
|
||||
}]);
|
||||
kanban.onChange();
|
||||
});
|
||||
|
||||
return kanban;
|
||||
};
|
||||
|
||||
var mkHelpMenu = function (framework) {
|
||||
var $toolbarContainer = $('#cp-app-kanban-container');
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']);
|
||||
$toolbarContainer.prepend(helpMenu.menu);
|
||||
|
||||
framework._.toolbar.$drawer.append(helpMenu.button);
|
||||
};
|
||||
|
||||
// Start of the main loop
|
||||
var andThen2 = function (framework) {
|
||||
|
||||
var kanban;
|
||||
var $container = $('#cp-app-kanban-content');
|
||||
|
||||
mkHelpMenu(framework);
|
||||
|
||||
if (framework.isReadOnly()) {
|
||||
$container.addClass('cp-app-readonly');
|
||||
} else {
|
||||
framework.setFileImporter({}, function (content /*, file */) {
|
||||
var parsed;
|
||||
try { parsed = JSON.parse(content); }
|
||||
catch (e) { return void console.error(e); }
|
||||
return { content: parsed };
|
||||
});
|
||||
}
|
||||
|
||||
framework.setFileExporter('json', function () {
|
||||
return new Blob([JSON.stringify(kanban.getBoardsJSON())], {
|
||||
type: 'application/json',
|
||||
});
|
||||
});
|
||||
|
||||
framework.onEditableChange(function (unlocked) {
|
||||
if (framework.isReadOnly()) { return; }
|
||||
if (!kanban) { return; }
|
||||
if (unlocked) {
|
||||
addRemoveItemButton(framework, kanban);
|
||||
kanban.options.readOnly = false;
|
||||
return void $container.removeClass('cp-app-readonly');
|
||||
}
|
||||
kanban.options.readOnly = true;
|
||||
$container.addClass('cp-app-readonly');
|
||||
});
|
||||
|
||||
framework.onContentUpdate(function (newContent) {
|
||||
// Init if needed
|
||||
if (!kanban) {
|
||||
kanban = initKanban(framework, (newContent || {}).content);
|
||||
addRemoveItemButton(framework, kanban);
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to update the content
|
||||
verbose("Content should be updated to " + newContent);
|
||||
var currentContent = kanban.getBoardsJSON();
|
||||
var remoteContent = newContent.content;
|
||||
|
||||
if (Sortify(currentContent) !== Sortify(remoteContent)) {
|
||||
// reinit kanban (TODO: optimize to diff only)
|
||||
verbose("Content is different.. Applying content");
|
||||
kanban.setBoards(remoteContent);
|
||||
kanban.inEditMode = false;
|
||||
addRemoveItemButton(framework, kanban);
|
||||
}
|
||||
});
|
||||
|
||||
framework.setContentGetter(function () {
|
||||
if (!kanban) {
|
||||
return {
|
||||
content: []
|
||||
};
|
||||
}
|
||||
var content = kanban.getBoardsJSON();
|
||||
verbose("Content current value is " + content);
|
||||
return {
|
||||
content: content
|
||||
};
|
||||
});
|
||||
|
||||
framework.onReady(function () {
|
||||
$("#cp-app-kanban-content").focus();
|
||||
});
|
||||
|
||||
framework.onDefaultContentNeeded(function () {
|
||||
kanban = initKanban(framework);
|
||||
});
|
||||
|
||||
framework.start();
|
||||
};
|
||||
|
||||
var main = function () {
|
||||
// var framework;
|
||||
nThen(function (waitFor) {
|
||||
|
||||
// Framework initialization
|
||||
Framework.create({
|
||||
toolbarContainer: '#cme_toolbox',
|
||||
contentContainer: '#cp-app-kanban-editor',
|
||||
}, waitFor(function (framework) {
|
||||
andThen2(framework);
|
||||
}));
|
||||
});
|
||||
};
|
||||
main();
|
||||
});
|
||||
100
www/kanban/jkanban.css
Normal file
100
www/kanban/jkanban.css
Normal file
@@ -0,0 +1,100 @@
|
||||
.kanban-container * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.kanban-board {
|
||||
position: relative;
|
||||
float: left;
|
||||
background: #E2E4E6;
|
||||
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
margin: 10px;
|
||||
vertical-align: top;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.kanban-board.disabled-board {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.kanban-board.is-moving.gu-mirror {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
|
||||
.kanban-board.is-moving.gu-mirror .kanban-drag {
|
||||
overflow: hidden;
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
.kanban-board header {
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.kanban-board header .kanban-title-board {
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.kanban-board header .kanban-title-button {
|
||||
float: right;
|
||||
line-height: 1;
|
||||
padding: .25rem .5rem;
|
||||
}
|
||||
|
||||
.kanban-board .kanban-drag {
|
||||
min-height: 200px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kanban-item {
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
.kanban-item:hover {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.kanban-item:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.kanban-item.is-moving.gu-mirror {
|
||||
transform: rotate(3deg);
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Dragula CSS */
|
||||
|
||||
.gu-mirror {
|
||||
position: fixed !important;
|
||||
margin: 0 !important;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
.gu-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.gu-unselectable {
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.gu-transit {
|
||||
opacity: 0.2 !important;
|
||||
transform: rotate(0deg) !important;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
text-align: right;
|
||||
margin-button: 5px;
|
||||
}
|
||||
1567
www/kanban/jkanban.js
Normal file
1567
www/kanban/jkanban.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ define([
|
||||
'/common/outer/local-store.js',
|
||||
'/common/test.js',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore, Test) {
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
|
||||
22
www/mediatag/index.html
Normal file
22
www/mediatag/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Test media-tag</title>
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
media-tag * {
|
||||
max-width: 60vw;
|
||||
max-height: 50vh;
|
||||
}
|
||||
iframe {
|
||||
width: 60vw;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
Media-tag:
|
||||
<media-tag src="/blob/84/845adf0efaa47db1754956859a9ffdbc76619d75e38ea3f9" data-crypto-key="cryptpad:kZiCb6zYuTbQw/9/JGjE9WGS+0/BOv6d/qyJagscCgc="></media-tag><br>
|
||||
<media-tag src="/blob/84/845adf0efaa47db1754956859a9ffdbc76619d75e38ea3f9" data-crypto-key="cryptpad:kZiCb6zYuTbQw/9/JGjE9WGS+0/BOv6d/qyJagscCgc="></media-tag><br>
|
||||
<media-tag src="/blob/0e/0e9dd35a459eb1d90df073bd2f19108e50b96019a3478288" data-crypto-key="cryptpad:HD3KsYdiz3/em/1bXEoQSibRNEQRNnTq80SdSpQtp+g="></media-tag><br>
|
||||
<media-tag src="/blob/fe/fe9f3562c42ee32703ced67cf92bfd77fee4e88eb27842db" data-crypto-key="cryptpad:NMo7rKVpl/MF/pw8A4XXJcYuI5JaADX+vQyhOXJgnLo="></media-tag>
|
||||
58
www/mediatag/main.js
Normal file
58
www/mediatag/main.js
Normal file
@@ -0,0 +1,58 @@
|
||||
require([
|
||||
'jquery',
|
||||
'/mediatag/media-tag.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function ($, MediaTag) {
|
||||
console.log(MediaTag);
|
||||
console.log($('media-tag'));
|
||||
if (typeof MediaTag === "function") {
|
||||
MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html';
|
||||
|
||||
var config = {
|
||||
allowed: ['download'],
|
||||
download: {
|
||||
text: 'Download'
|
||||
}
|
||||
};
|
||||
MediaTag($('media-tag'), config)
|
||||
.on('progress', function (data) {
|
||||
console.log(data.progress);
|
||||
})
|
||||
.on('complete', function (data) {
|
||||
console.log(data);
|
||||
})
|
||||
.on('error', function (data) {
|
||||
console.error(data);
|
||||
});
|
||||
MediaTag($('media-tag')[1])
|
||||
.on('progress', function (data) {
|
||||
console.log(data.progress);
|
||||
})
|
||||
.on('complete', function (data) {
|
||||
console.log(data);
|
||||
})
|
||||
.on('error', function (data) {
|
||||
console.error(data);
|
||||
});
|
||||
MediaTag($('media-tag')[2])
|
||||
.on('progress', function (data) {
|
||||
console.log(data.progress);
|
||||
})
|
||||
.on('complete', function (data) {
|
||||
console.log(data);
|
||||
})
|
||||
.on('error', function (data) {
|
||||
console.error(data);
|
||||
});
|
||||
MediaTag($('media-tag')[3])
|
||||
.on('progress', function (data) {
|
||||
console.log(data.progress);
|
||||
})
|
||||
.on('complete', function (data) {
|
||||
console.log(data);
|
||||
})
|
||||
.on('error', function (data) {
|
||||
console.error(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
430
www/mediatag/media-tag.js
Normal file
430
www/mediatag/media-tag.js
Normal file
@@ -0,0 +1,430 @@
|
||||
(function(name, definition) {
|
||||
if (typeof module !== 'undefined') { module.exports = definition(); }
|
||||
else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); }
|
||||
else { this[name] = definition(); }
|
||||
}('MediaTag', function() {
|
||||
var cache;
|
||||
var cypherChunkLength = 131088;
|
||||
|
||||
// Save a blob on the file system
|
||||
var saveFile = function (blob, url, fileName) {
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveOrOpenBlob(blob, fileName);
|
||||
} else {
|
||||
// We want to be able to download the file with a name, so we need an "a" tag with
|
||||
// a download attribute
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
// It's not in the DOM, so we can't use a.click();
|
||||
var event = new MouseEvent("click");
|
||||
a.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
var fixHTML = function (str) {
|
||||
if (!str) { return ''; }
|
||||
return str.replace(/[<>&"']/g, function (x) {
|
||||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Default config, can be overriden per media-tag call
|
||||
var config = {
|
||||
allowed: [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
//'application/dash+xml', // FIXME?
|
||||
'download'
|
||||
],
|
||||
pdf: {},
|
||||
download: {
|
||||
text: "Download"
|
||||
},
|
||||
Plugins: {
|
||||
image: function (metadata, url, content, cfg, cb) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('src', url);
|
||||
img.blob = content;
|
||||
cb(void 0, img);
|
||||
},
|
||||
video: function (metadata, url, content, cfg, cb) {
|
||||
var video = document.createElement('video');
|
||||
video.setAttribute('src', url);
|
||||
video.setAttribute('controls', true);
|
||||
cb(void 0, video);
|
||||
},
|
||||
audio: function (metadata, url, content, cfg, cb) {
|
||||
var audio = document.createElement('audio');
|
||||
audio.setAttribute('src', url);
|
||||
audio.setAttribute('controls', true);
|
||||
cb(void 0, audio);
|
||||
},
|
||||
pdf: function (metadata, url, content, cfg, cb) {
|
||||
var iframe = document.createElement('iframe');
|
||||
if (cfg.pdf.viewer) { // PDFJS
|
||||
var viewerUrl = cfg.pdf.viewer + '?file=' + url;
|
||||
iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
|
||||
return void cb (void 0, iframe);
|
||||
}
|
||||
iframe.src = url + '#' + window.encodeURIComponent(metadata.name);
|
||||
return void cb (void 0, iframe);
|
||||
},
|
||||
download: function (metadata, url, content, cfg, cb) {
|
||||
var btn = document.createElement('button');
|
||||
btn.innerHTML = cfg.download.text + '<br>' +
|
||||
metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '';
|
||||
btn.addEventListener('click', function () {
|
||||
saveFile(content, url, metadata.name);
|
||||
});
|
||||
cb(void 0, btn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Download a blob from href
|
||||
var download = function (src, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', src, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onload = function () {
|
||||
// Error?
|
||||
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
|
||||
|
||||
var arrayBuffer = xhr.response;
|
||||
if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); }
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
// Decryption tools
|
||||
var Decrypt = {
|
||||
// Create a nonce
|
||||
createNonce: function () {
|
||||
var n = new Uint8Array(24);
|
||||
for (var i = 0; i < 24; i++) { n[i] = 0; }
|
||||
return n;
|
||||
},
|
||||
|
||||
// Increment a nonce
|
||||
increment: function (N) {
|
||||
var l = N.length;
|
||||
while (l-- > 1) {
|
||||
/* .jshint probably suspects this is unsafe because we lack types
|
||||
but as long as this is only used on nonces, it should be safe */
|
||||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||
|
||||
// you don't need to worry about this running out.
|
||||
// you'd need a REAAAALLY big file
|
||||
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
|
||||
|
||||
N[l] = 0;
|
||||
}
|
||||
},
|
||||
|
||||
decodePrefix: function (A) {
|
||||
return (A[0] << 8) | A[1];
|
||||
},
|
||||
joinChunks: function (chunks) {
|
||||
return new Blob(chunks);
|
||||
},
|
||||
|
||||
// Convert a Uint8Array into Array.
|
||||
slice: function (u8) {
|
||||
return Array.prototype.slice.call(u8);
|
||||
},
|
||||
|
||||
// Gets the key from the key string.
|
||||
getKeyFromStr: function (str) {
|
||||
return window.nacl.util.decodeBase64(str);
|
||||
}
|
||||
};
|
||||
|
||||
// Decrypts a Uint8Array with the given key.
|
||||
var decrypt = function (u8, strKey, done, progressCb) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var progress = function (offset) {
|
||||
progressCb((offset / u8.length) * 100);
|
||||
};
|
||||
|
||||
var key = Decrypt.getKeyFromStr(strKey);
|
||||
var nonce = Decrypt.createNonce();
|
||||
var i = 0;
|
||||
var prefix = u8.subarray(0, 2);
|
||||
var metadataLength = Decrypt.decodePrefix(prefix);
|
||||
|
||||
var res = { metadata: undefined };
|
||||
|
||||
// Get metadata
|
||||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||||
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
|
||||
|
||||
Decrypt.increment(nonce);
|
||||
|
||||
try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); }
|
||||
catch (e) { return void done('E_METADATA_DECRYPTION'); }
|
||||
|
||||
if (!res.metadata) { return void done('NO_METADATA'); }
|
||||
|
||||
var takeChunk = function (cb) {
|
||||
setTimeout(function () {
|
||||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||
var end = start + cypherChunkLength;
|
||||
i++;
|
||||
|
||||
// Get the chunk
|
||||
var box = new Uint8Array(u8.subarray(start, end));
|
||||
|
||||
// Decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
Decrypt.increment(nonce);
|
||||
|
||||
if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
|
||||
|
||||
progress(Math.min(end, u8.length));
|
||||
|
||||
cb(void 0, plaintext);
|
||||
});
|
||||
};
|
||||
|
||||
var chunks = [];
|
||||
|
||||
// decrypt file contents
|
||||
var again = function () {
|
||||
takeChunk(function (e, plaintext) {
|
||||
if (e) { return setTimeout(function () { done(e); }); }
|
||||
|
||||
if (plaintext) {
|
||||
if (i * cypherChunkLength < u8.length) { // not done
|
||||
chunks.push(plaintext);
|
||||
return again();
|
||||
}
|
||||
|
||||
chunks.push(plaintext);
|
||||
res.content = Decrypt.joinChunks(chunks);
|
||||
return void done(void 0, res);
|
||||
}
|
||||
done('UNEXPECTED_ENDING');
|
||||
});
|
||||
};
|
||||
again();
|
||||
};
|
||||
|
||||
// Get type
|
||||
var getType = function (mediaObject, metadata, cfg) {
|
||||
var mime = metadata.type;
|
||||
var s = metadata.type.split('/');
|
||||
var type = s[0];
|
||||
var extension = s[1];
|
||||
|
||||
mediaObject.name = metadata.name;
|
||||
if (mime && cfg.allowed.indexOf(mime) !== -1) {
|
||||
mediaObject.type = type;
|
||||
mediaObject.extension = extension;
|
||||
mediaObject.mime = mime;
|
||||
return type;
|
||||
} else if (cfg.allowed.indexOf('download') !== -1) {
|
||||
mediaObject.type = type;
|
||||
mediaObject.extension = extension;
|
||||
mediaObject.mime = mime;
|
||||
return 'download';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Copy attributes
|
||||
var copyAttributes = function (origin, dest) {
|
||||
Object.keys(origin.attributes).forEach(function (i) {
|
||||
if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
|
||||
var name = origin.attributes[i].name.slice(10);
|
||||
var value = origin.attributes[i].value;
|
||||
dest.setAttribute(name, value);
|
||||
});
|
||||
};
|
||||
|
||||
// Process
|
||||
var process = function (mediaObject, decrypted, cfg, cb) {
|
||||
var metadata = decrypted.metadata;
|
||||
var blob = decrypted.content;
|
||||
|
||||
var mediaType = getType(mediaObject, metadata, cfg);
|
||||
|
||||
if (mediaType === 'application') {
|
||||
mediaType = mediaObject.extension;
|
||||
}
|
||||
|
||||
if (!mediaType || !cfg.Plugins[mediaType]) {
|
||||
return void cb('NO_PLUGIN_FOUND');
|
||||
}
|
||||
|
||||
// Get blob URL
|
||||
var url = decrypted.url;
|
||||
if (!url && window.URL) {
|
||||
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
|
||||
type: metadata.type
|
||||
}));
|
||||
}
|
||||
|
||||
cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) {
|
||||
if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); }
|
||||
copyAttributes(mediaObject.tag, el);
|
||||
mediaObject.tag.innerHTML = '';
|
||||
mediaObject.tag.appendChild(el);
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
var addMissingConfig = function (base, target) {
|
||||
Object.keys(target).forEach(function (k) {
|
||||
if (!target[k]) { return; }
|
||||
// Target is an object, fix it recursively
|
||||
if (typeof target[k] === "object" && !Array.isArray(target[k])) {
|
||||
// Sub-object
|
||||
if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; }
|
||||
else if (base[k]) { addMissingConfig(base[k], target[k]); }
|
||||
else {
|
||||
base[k] = {};
|
||||
addMissingConfig(base[k], target[k]);
|
||||
}
|
||||
}
|
||||
// Target is array or immutable, copy the value if it's missing
|
||||
if (!base[k]) {
|
||||
base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k]))
|
||||
: target[k];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize a media-tag
|
||||
var init = function (el, cfg) {
|
||||
cfg = cfg || {};
|
||||
|
||||
addMissingConfig(cfg, config);
|
||||
|
||||
// Add support for old mediatag library
|
||||
if (!cfg.pdf.viewer && init.PdfPlugin && init.PdfPlugin.viewer) {
|
||||
cfg.pdf.viewer = init.PdfPlugin.viewer;
|
||||
}
|
||||
|
||||
// Handle jQuery elements
|
||||
if (typeof(el) === "object" && el.jQuery) { el = el[0]; }
|
||||
|
||||
// Abort smoothly if the element is not a media-tag
|
||||
if (el.nodeName !== "MEDIA-TAG") {
|
||||
console.error("Not a media-tag!");
|
||||
return {
|
||||
on: function () { return this; }
|
||||
};
|
||||
}
|
||||
|
||||
var handlers = cfg.handlers || {
|
||||
'progress': [],
|
||||
'complete': [],
|
||||
'error': []
|
||||
};
|
||||
|
||||
var mediaObject = el._mediaObject = {
|
||||
handlers: handlers,
|
||||
tag: el
|
||||
};
|
||||
|
||||
var emit = function (ev, data) {
|
||||
// Check if the event name is valid
|
||||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||||
return void console.error("Invalid mediatag event");
|
||||
}
|
||||
|
||||
// Call the handlers
|
||||
handlers[ev].forEach(function (h) {
|
||||
// Make sure a bad handler won't break the media-tag script
|
||||
try {
|
||||
h(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mediaObject.on = function (ev, handler) {
|
||||
// Check if the event name is valid
|
||||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||||
console.error("Invalid mediatag event");
|
||||
return mediaObject;
|
||||
}
|
||||
// Check if the handler is valid
|
||||
if (typeof (handler) !== "function") {
|
||||
console.error("Handler is not a function!");
|
||||
return mediaObject;
|
||||
}
|
||||
// Add the handler
|
||||
handlers[ev].push(handler);
|
||||
return mediaObject;
|
||||
};
|
||||
|
||||
var src = el.getAttribute('src');
|
||||
var strKey = el.getAttribute('data-crypto-key');
|
||||
if (/^cryptpad:/.test(strKey)) {
|
||||
strKey = strKey.slice(9);
|
||||
}
|
||||
var uid = [src, strKey].join('');
|
||||
|
||||
// End media-tag rendering: display the tag and emit the event
|
||||
var end = function (decrypted) {
|
||||
process(mediaObject, decrypted, cfg, function (err) {
|
||||
if (err) { return void emit('error', err); }
|
||||
emit('complete', decrypted);
|
||||
});
|
||||
};
|
||||
|
||||
// If we have the blob in our cache, don't download & decrypt it again, just display
|
||||
if (cache[uid]) {
|
||||
end(cache[uid]);
|
||||
return mediaObject;
|
||||
}
|
||||
|
||||
// Download the encrypted blob
|
||||
download(src, function (err, u8Encrypted) {
|
||||
if (err) {
|
||||
return void emit('error', err);
|
||||
}
|
||||
// Decrypt the blob
|
||||
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
|
||||
if (errDecryption) {
|
||||
return void emit('error', errDecryption);
|
||||
}
|
||||
// Cache and display the decrypted blob
|
||||
cache[uid] = u8Decrypted;
|
||||
end(u8Decrypted);
|
||||
}, function (progress) {
|
||||
emit('progress', {
|
||||
progress: progress
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return mediaObject;
|
||||
};
|
||||
|
||||
// Add the cache as a property of MediaTag
|
||||
cache = init.__Cryptpad_Cache = {};
|
||||
|
||||
init.PdfPlugin = {};
|
||||
|
||||
return init;
|
||||
}));
|
||||
@@ -1,42 +0,0 @@
|
||||
@import (once) "../customize/src/less2/include/browser.less";
|
||||
@import (once) "../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../customize/src/less2/include/avatar.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_oocell-bg,
|
||||
@warn-color: @colortheme_oocell-warn,
|
||||
@color: @colortheme_oocell-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-oocell {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
#cp-toolbar {
|
||||
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
|
||||
}
|
||||
|
||||
.cp-cryptpad-toolbar {
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#cp-app-oo-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
}
|
||||
#ooframe {
|
||||
flex: 1;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
@import (once) "../customize/src/less2/include/browser.less";
|
||||
@import (once) "../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../customize/src/less2/include/avatar.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_oodoc-bg,
|
||||
@warn-color: @colortheme_oodoc-warn,
|
||||
@color: @colortheme_oodoc-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-oodoc {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
#cp-toolbar {
|
||||
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
|
||||
}
|
||||
|
||||
.cp-cryptpad-toolbar {
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#cp-app-oo-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
}
|
||||
#ooframe {
|
||||
flex: 1;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
@import (once) "../customize/src/less2/include/browser.less";
|
||||
@import (once) "../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../customize/src/less2/include/avatar.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_ooslide-bg,
|
||||
@warn-color: @colortheme_ooslide-warn,
|
||||
@color: @colortheme_ooslide-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-ooslide {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
#cp-toolbar {
|
||||
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
|
||||
}
|
||||
|
||||
.cp-cryptpad-toolbar {
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#cp-app-oo-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
}
|
||||
#ooframe {
|
||||
flex: 1;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
@import (once) "../../customize/src/less2/include/framework.less";
|
||||
@import (reference) "../../customize/src/less2/include/framework.less";
|
||||
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_pad-bg,
|
||||
@warn-color: @colortheme_pad-warn,
|
||||
@color: @colortheme_pad-color
|
||||
);
|
||||
.alertify_main();
|
||||
body.cp-app-pad {
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_pad-bg,
|
||||
@warn-color: @colortheme_pad-warn,
|
||||
@color: @colortheme_pad-color
|
||||
);
|
||||
|
||||
// body
|
||||
&.cp-app-pad {
|
||||
.tokenfield_main();
|
||||
#cke_1_top {
|
||||
overflow: visible;
|
||||
padding: 0px;
|
||||
@@ -46,17 +43,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cke_wysiwyg_frame {
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#cke_1_top {
|
||||
display:none !important;
|
||||
.cke_dialog {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
&.cp-app-pad .cp-toolbar-userlist-drawer {
|
||||
display:none;
|
||||
|
||||
.cke_wysiwyg_frame {
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#cke_1_top {
|
||||
display:none !important;
|
||||
}
|
||||
&.cp-app-pad .cp-toolbar-userlist-drawer {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
www/pad/disable-base64.js
Normal file
40
www/pad/disable-base64.js
Normal file
@@ -0,0 +1,40 @@
|
||||
( function() {
|
||||
CKEDITOR.plugins.add('blockbase64', {
|
||||
init: function (editor) {
|
||||
|
||||
var replaceImgText = function (html) {
|
||||
var ret = html.replace( /<img[^>]*src="data:image\/(bmp|dds|gif|jpg|jpeg|png|psd|pspimage|tga|thm|tif|tiff|yuv|ai|eps|ps|svg);base64,.*?"[^>]*>/gi,
|
||||
function () {
|
||||
console.error("Direct image paste is not allowed.");
|
||||
return '';
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/*var chkImg = function () {
|
||||
// don't execute code if the editor is readOnly
|
||||
if (editor.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout( function() {
|
||||
editor.document.$.body.innerHTML = replaceImgText(editor.document.$.body.innerHTML);
|
||||
},100);
|
||||
};
|
||||
|
||||
editor.on('contentDom', function () {
|
||||
// For Firefox
|
||||
editor.document.on('drop', chkImg);
|
||||
// For IE
|
||||
editor.document.getBody().on('drop', chkImg);
|
||||
});*/
|
||||
|
||||
editor.on('paste', function(e) {
|
||||
var html = e.data.dataValue;
|
||||
if (!html) { return; }
|
||||
e.data.dataValue = replaceImgText(html);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -3,6 +3,43 @@
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
}
|
||||
#cke_1_top {
|
||||
overflow: visible;
|
||||
padding: 0px;
|
||||
display: flex;
|
||||
}
|
||||
#cke_1_toolbox {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: #c1e7ff;
|
||||
}
|
||||
#cke_1_toolbox .cke_toolbar {
|
||||
height: 28px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
#cke_1_top .cryptpad-toolbar {
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
.cke_wysiwyg_frame {
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
|
||||
@media print {
|
||||
#cke_1_top {
|
||||
display:none;
|
||||
}
|
||||
body.app-pad .userlist-drawer {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-pad">
|
||||
<textarea style="display:none" id="editor1" name="editor1"></textarea>
|
||||
|
||||
291
www/pad/inner.js
291
www/pad/inner.js
@@ -30,14 +30,17 @@ define([
|
||||
'/api/config',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/hyperscript.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/test.js',
|
||||
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/pad/app-pad.less'
|
||||
], function (
|
||||
$,
|
||||
Hyperjson,
|
||||
@@ -51,8 +54,12 @@ define([
|
||||
ApiConfig,
|
||||
Hash,
|
||||
Util,
|
||||
UI,
|
||||
h,
|
||||
ChainPad,
|
||||
AppConfig)
|
||||
AppConfig,
|
||||
Test
|
||||
)
|
||||
{
|
||||
var DiffDom = window.diffDOM;
|
||||
|
||||
@@ -82,12 +89,54 @@ define([
|
||||
Cursor: Cursor,
|
||||
};
|
||||
|
||||
// MEDIATAG: Filter elements to serialize
|
||||
// * Remove the drag&drop and resizers from the hyperjson
|
||||
var isWidget = function (el) {
|
||||
return typeof (el.getAttribute) === "function" &&
|
||||
(el.getAttribute('data-cke-hidden-sel') ||
|
||||
(el.getAttribute('class') &&
|
||||
(/cke_widget_drag/.test(el.getAttribute('class')) ||
|
||||
/cke_image_resizer/.test(el.getAttribute('class')))
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
var isNotMagicLine = function (el) {
|
||||
return !(el && typeof(el.getAttribute) === 'function' &&
|
||||
el.getAttribute('class') &&
|
||||
el.getAttribute('class').split(' ').indexOf('non-realtime') !== -1);
|
||||
};
|
||||
|
||||
var shouldSerialize = function (el) {
|
||||
return isNotMagicLine(el) && !isWidget(el);
|
||||
};
|
||||
|
||||
// MEDIATAG: Filter attributes in the serialized elements
|
||||
var widgetFilter = function (hj) {
|
||||
// Send a widget ID == 0 to avoid a fight between browsers and
|
||||
// prevent the container from having the "selected" class (blue border)
|
||||
if (hj[1].class) {
|
||||
var split = hj[1].class.split(' ');
|
||||
if (split.indexOf('cke_widget_wrapper') !== -1 &&
|
||||
split.indexOf('cke_widget_block') !== -1) {
|
||||
hj[1].class = "cke_widget_wrapper cke_widget_block";
|
||||
hj[1]['data-cke-widget-id'] = "0";
|
||||
}
|
||||
if (split.indexOf('cke_widget_wrapper') !== -1 &&
|
||||
split.indexOf('cke_widget_inline') !== -1) {
|
||||
hj[1].class = "cke_widget_wrapper cke_widget_inline";
|
||||
delete hj[1]['data-cke-widget-id'];
|
||||
//hj[1]['data-cke-widget-id'] = "0";
|
||||
}
|
||||
// Remove the title attribute of the drag&drop icons (translation conflicts)
|
||||
if (split.indexOf('cke_widget_drag_handler') !== -1 ||
|
||||
split.indexOf('cke_image_resizer') !== -1) {
|
||||
hj[1].title = undefined;
|
||||
}
|
||||
}
|
||||
return hj;
|
||||
};
|
||||
|
||||
var hjsonFilters = function (hj) {
|
||||
/* catch `type="_moz"` before it goes over the wire */
|
||||
var brFilter = function (hj) {
|
||||
@@ -100,6 +149,7 @@ define([
|
||||
};
|
||||
brFilter(hj);
|
||||
mediatagContentFilter(hj);
|
||||
widgetFilter(hj);
|
||||
return hj;
|
||||
};
|
||||
|
||||
@@ -117,7 +167,14 @@ define([
|
||||
];
|
||||
|
||||
var getHTML = function (inner) {
|
||||
return ('<!DOCTYPE html>\n' + '<html>\n' + inner.innerHTML);
|
||||
return ('<!DOCTYPE html>\n' + '<html>\n' +
|
||||
' <head><meta charset="utf-8"></head>\n <body>' +
|
||||
inner.innerHTML.replace(/<img[^>]*class="cke_anchor"[^>]*data-cke-realelement="([^"]*)"[^>]*>/g,
|
||||
function(match,realElt){
|
||||
//console.log("returning realElt \"" + unescape(realElt)+ "\".");
|
||||
return decodeURIComponent(realElt); }) +
|
||||
' </body>\n</html>'
|
||||
);
|
||||
};
|
||||
|
||||
var CKEDITOR_CHECK_INTERVAL = 100;
|
||||
@@ -168,6 +225,36 @@ define([
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MEDIATAG
|
||||
// Never modify widget ids
|
||||
if (info.node && info.node.tagName === 'SPAN' && info.diff.name === 'data-cke-widget-id') {
|
||||
return true;
|
||||
}
|
||||
if (info.node && info.node.tagName === 'SPAN' &&
|
||||
info.node.getAttribute('class') &&
|
||||
/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
|
||||
if (info.diff.action === 'modifyAttribute' && info.diff.name === 'class') {
|
||||
return true;
|
||||
}
|
||||
//console.log(info);
|
||||
}
|
||||
// CkEditor drag&drop icon container
|
||||
if (info.node && info.node.tagName === 'SPAN' &&
|
||||
info.node.getAttribute('class') &&
|
||||
info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler_container') !== -1) {
|
||||
return true;
|
||||
}
|
||||
// CkEditor drag&drop title (language fight)
|
||||
if (info.node && info.node.getAttribute &&
|
||||
info.node.getAttribute('class') &&
|
||||
(info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler') !== -1 ||
|
||||
info.node.getAttribute('class').split(' ').indexOf('cke_image_resizer') !== -1 ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Also reject any elements which would insert any one of
|
||||
our forbidden tag types: script, iframe, object,
|
||||
@@ -199,20 +286,26 @@ define([
|
||||
if (info.node && info.node.tagName === 'SPAN' &&
|
||||
info.node.getAttribute('contentEditable') === "false") {
|
||||
// it seems to be a magicline plugin element...
|
||||
// but it can also be a widget (MEDIATAG), in which case the removal was
|
||||
// probably intentional
|
||||
|
||||
if (info.diff.action === 'removeElement') {
|
||||
// and you're about to remove it...
|
||||
// this probably isn't what you want
|
||||
if (!info.node.getAttribute('class') ||
|
||||
!/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
|
||||
// This element is not a widget!
|
||||
// this probably isn't what you want
|
||||
/*
|
||||
I have never seen this in the console, but the
|
||||
magic line is still getting removed on remote
|
||||
edits. This suggests that it's getting removed
|
||||
by something other than diffDom.
|
||||
*/
|
||||
console.log("preventing removal of the magic line!");
|
||||
|
||||
/*
|
||||
I have never seen this in the console, but the
|
||||
magic line is still getting removed on remote
|
||||
edits. This suggests that it's getting removed
|
||||
by something other than diffDom.
|
||||
*/
|
||||
console.log("preventing removal of the magic line!");
|
||||
|
||||
// return true to prevent diff application
|
||||
return true;
|
||||
// return true to prevent diff application
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +415,7 @@ define([
|
||||
var src = tag.getAttribute('src');
|
||||
if (mediaTagMap[src]) {
|
||||
mediaTagMap[src].forEach(function (n) {
|
||||
tag.appendChild(n);
|
||||
tag.appendChild(n.cloneNode());
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -370,8 +463,11 @@ define([
|
||||
|
||||
framework.setMediaTagEmbedder(function ($mt) {
|
||||
$mt.attr('contenteditable', 'false');
|
||||
$mt.attr('tabindex', '1');
|
||||
editor.insertElement(new window.CKEDITOR.dom.element($mt[0]));
|
||||
//$mt.attr('tabindex', '1');
|
||||
//MEDIATAG
|
||||
var element = new window.CKEDITOR.dom.element($mt[0]);
|
||||
editor.insertElement(element);
|
||||
editor.widgets.initOn( element, 'mediatag' );
|
||||
});
|
||||
|
||||
framework.setTitleRecommender(function () {
|
||||
@@ -403,7 +499,18 @@ define([
|
||||
|
||||
var patch = (DD).diff(inner, userDocStateDom);
|
||||
(DD).apply(inner, patch);
|
||||
|
||||
// MEDIATAG: Migrate old mediatags to the widget system
|
||||
$(inner).find('media-tag:not(.cke_widget_element)').each(function (i, el) {
|
||||
var element = new window.CKEDITOR.dom.element(el);
|
||||
editor.widgets.initOn( element, 'mediatag' );
|
||||
});
|
||||
|
||||
displayMediaTags(framework, inner, mediaTagMap);
|
||||
|
||||
// MEDIATAG: Initialize mediatag widgets inserted in the document by other users
|
||||
editor.widgets.checkWidgets();
|
||||
|
||||
if (framework.isReadOnly()) {
|
||||
var $links = $(inner).find('a');
|
||||
// off so that we don't end up with multiple identical handlers
|
||||
@@ -425,7 +532,7 @@ define([
|
||||
framework.setContentGetter(function () {
|
||||
displayMediaTags(framework, inner, mediaTagMap);
|
||||
inner.normalize();
|
||||
return Hyperjson.fromDOM(inner, isNotMagicLine, hjsonFilters);
|
||||
return Hyperjson.fromDOM(inner, shouldSerialize, hjsonFilters);
|
||||
});
|
||||
|
||||
$bar.find('#cke_1_toolbar_collapser').hide();
|
||||
@@ -460,10 +567,18 @@ define([
|
||||
body: $('body'),
|
||||
onUploaded: function (ev, data) {
|
||||
var parsed = Hash.parsePadUrl(data.url);
|
||||
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>';
|
||||
editor.insertElement(window.CKEDITOR.dom.element.createFromHtml(mt));
|
||||
var secret = Hash.getSecrets('file', parsed.hash, data.password);
|
||||
var src = Hash.getBlobPathFromHex(secret.channel);
|
||||
var key = Hash.encodeBase64(secret.keys.cryptKey);
|
||||
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
|
||||
// MEDIATAG
|
||||
var element = window.CKEDITOR.dom.element.createFromHtml(mt);
|
||||
if (ev && ev.insertElement) {
|
||||
ev.insertElement(element);
|
||||
} else {
|
||||
editor.insertElement(element);
|
||||
}
|
||||
editor.widgets.initOn( element, 'mediatag' );
|
||||
}
|
||||
};
|
||||
window.APP.FM = framework._.sfCommon.createFileManager(fmConfig);
|
||||
@@ -474,6 +589,38 @@ define([
|
||||
$iframe.find('html').addClass('cke_body_width');
|
||||
}
|
||||
});
|
||||
|
||||
framework._.sfCommon.isPadStored(function (err, val) {
|
||||
if (!val) { return; }
|
||||
var b64images = $(inner).find('img[src^="data:image"]:not(.cke_reset)');
|
||||
if (b64images.length && framework._.sfCommon.isLoggedIn()) {
|
||||
var no = h('button.cp-corner-cancel', Messages.cancel);
|
||||
var yes = h('button.cp-corner-primary', Messages.ok);
|
||||
var actions = h('div', [yes, no]);
|
||||
var modal = UI.cornerPopup(Messages.pad_base64, actions, '', {big: true});
|
||||
$(no).click(function () {
|
||||
modal.delete();
|
||||
});
|
||||
$(yes).click(function () {
|
||||
modal.delete();
|
||||
b64images.each(function (i, el) {
|
||||
var src = $(el).attr('src');
|
||||
var blob = Util.dataURIToBlob(src);
|
||||
var ext = '.' + (blob.type.split('/')[1] || 'png');
|
||||
var name = (framework._.title.getTitle() || 'Pad')+'_image';
|
||||
blob.name = name + ext;
|
||||
var ev = {
|
||||
insertElement: function (newEl) {
|
||||
var element = new window.CKEDITOR.dom.element(el);
|
||||
newEl.replace(element);
|
||||
setTimeout(framework.localChange);
|
||||
}
|
||||
};
|
||||
window.APP.FM.handleFile(blob, ev);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
/*setTimeout(function () {
|
||||
$('iframe.cke_wysiwyg_frame').focus();
|
||||
editor.focus();
|
||||
@@ -504,10 +651,12 @@ define([
|
||||
var $clone = $(inner).clone();
|
||||
nThen(function (waitFor) {
|
||||
$(inner).find('media-tag').each(function (i, el) {
|
||||
if (!$(el).data('blob')) { return; }
|
||||
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
|
||||
if (!$(el).data('blob') || !el.blob) { return; }
|
||||
Util.blobToImage(el.blob || $(el).data('blob'), waitFor(function (imgSrc) {
|
||||
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
|
||||
.attr('src', imgSrc);
|
||||
$clone.find('media-tag').parent()
|
||||
.find('.cke_widget_drag_handler_container').remove();
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
@@ -628,18 +777,27 @@ define([
|
||||
'max-width: 50em; padding: 20px 30px; margin: 0 auto; min-height: 100%;'+
|
||||
'box-sizing: border-box; overflow: auto;'+
|
||||
'}' +
|
||||
'.cke_body_width body > *:first-child { margin-top: 0; }';
|
||||
'.cke_body_width body > *:first-child { margin-top: 0; }' +
|
||||
Ckeditor.addCss(newCss);
|
||||
Ckeditor._mediatagTranslations = {
|
||||
title: Messages.pad_mediatagTitle,
|
||||
width: Messages.pad_mediatagWidth,
|
||||
height: Messages.pad_mediatagHeight,
|
||||
ratio: Messages.pad_mediatagRatio,
|
||||
border: Messages.pad_mediatagBorder,
|
||||
preview: Messages.pad_mediatagPreview,
|
||||
'import': Messages.pad_mediatagImport,
|
||||
options: Messages.pad_mediatagOptions
|
||||
};
|
||||
Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js');
|
||||
Ckeditor.plugins.addExternal('blockbase64','/pad/', 'disable-base64.js');
|
||||
module.ckeditor = editor = Ckeditor.replace('editor1', {
|
||||
customConfig: '/customize/ckeditor-config.js',
|
||||
});
|
||||
editor.on('instanceReady', waitFor());
|
||||
}).nThen(function () {
|
||||
editor.plugins.mediatag.translations = {
|
||||
title: Messages.pad_mediatagTitle,
|
||||
width: Messages.pad_mediatagWidth,
|
||||
height: Messages.pad_mediatagHeight
|
||||
editor.plugins.mediatag.import = function ($mt) {
|
||||
framework._.sfCommon.importMediaTag($mt);
|
||||
};
|
||||
Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor});
|
||||
}).nThen(function () {
|
||||
@@ -654,6 +812,79 @@ define([
|
||||
}).nThen(waitFor());
|
||||
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
function launchAnchorTest(test) {
|
||||
// -------- anchor test: make sure the exported anchor contains <a name="..."> -------
|
||||
console.log('---- anchor test: make sure the exported anchor contains <a name="..."> -----.');
|
||||
|
||||
function tryAndTestExport() {
|
||||
console.log("Starting tryAndTestExport.");
|
||||
editor.on( 'dialogShow', function( evt ) {
|
||||
console.log("Anchor dialog detected.");
|
||||
var dialog = evt.data;
|
||||
$(dialog.parts.contents.$).find("input").val('xx-' + Math.round(Math.random()*1000));
|
||||
dialog.click(window.CKEDITOR.dialog.okButton(editor).id);
|
||||
} );
|
||||
var existingText = editor.getData();
|
||||
editor.insertText("A bit of text");
|
||||
console.log("Launching anchor command.");
|
||||
editor.execCommand(editor.ui.get('Anchor').command);
|
||||
console.log("Anchor command launched.");
|
||||
|
||||
var waitH = window.setInterval(function() {
|
||||
console.log("Waited 2s for the dialog to appear");
|
||||
var anchors = window.CKEDITOR.plugins["link"].getEditorAnchors(editor);
|
||||
if(!anchors || anchors.length===0) {
|
||||
test.fail("No anchors found. Please adjust document");
|
||||
} else {
|
||||
console.log(anchors.length + " anchors found.");
|
||||
var exported = getHTML(window.inner);
|
||||
console.log("Obtained exported: " + exported);
|
||||
var allFound = true;
|
||||
for(var i=0; i<anchors.length; i++) {
|
||||
var anchor = anchors[i];
|
||||
console.log("Anchor " + anchor.name);
|
||||
var expected = "<a id=\"" + anchor.id + "\" name=\"" + anchor.name + "\" ";
|
||||
var found = exported.indexOf(expected)>=0;
|
||||
console.log("Found " + expected + " " + found + ".");
|
||||
allFound = allFound && found;
|
||||
}
|
||||
|
||||
console.log("Cleaning up.");
|
||||
if(allFound) {
|
||||
// clean-up
|
||||
editor.execCommand('undo');
|
||||
editor.execCommand('undo');
|
||||
var nint = window.setInterval(function(){
|
||||
console.log("Waiting for undo to yield same result.");
|
||||
if(existingText === editor.getData()) {
|
||||
window.clearInterval(nint);
|
||||
test.pass();
|
||||
}
|
||||
}, 500);
|
||||
} else
|
||||
{
|
||||
test.fail("Not all expected a elements found for document at " + window.top.location + ".");
|
||||
}
|
||||
}
|
||||
window.clearInterval(waitH);
|
||||
},2000);
|
||||
|
||||
|
||||
}
|
||||
var intervalHandle = window.setInterval(function() {
|
||||
if(editor.status==="ready") {
|
||||
window.clearInterval(intervalHandle);
|
||||
console.log("Editor is ready.");
|
||||
tryAndTestExport();
|
||||
} else {
|
||||
console.log("Waiting for editor to be ready.");
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
Test(function(test) {
|
||||
|
||||
launchAnchorTest(test);
|
||||
});
|
||||
andThen2(editor, Ckeditor, framework);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
var Messages = editor.plugins.mediatag.translations;
|
||||
var Messages = CKEDITOR._mediatagTranslations;
|
||||
return {
|
||||
title: Messages.title,
|
||||
minWidth: 400,
|
||||
@@ -13,17 +13,53 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
type: 'text',
|
||||
id: 'width',
|
||||
label: Messages.width,
|
||||
validate: function () {
|
||||
if (isNaN(this.getValue())) { return false; }
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'height',
|
||||
label: Messages.height,
|
||||
}
|
||||
validate: function () {
|
||||
if (isNaN(this.getValue())) { return false; }
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
id: 'lock',
|
||||
'default': 'checked',
|
||||
label: Messages.ratio,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'border',
|
||||
label: Messages.border,
|
||||
validate: function () {
|
||||
if (isNaN(this.getValue())) { return false; }
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
id: 'preview',
|
||||
html: '<label>'+Messages.preview+'</label>'+
|
||||
'<div id="ck-mediatag-preview"'+
|
||||
'style="margin:auto;resize:both;max-width:300px;max-height:300px;overflow:auto"'+
|
||||
'></div>'
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
onShow: function () {
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
|
||||
var thiss = this;
|
||||
var sel = editor.getSelection();
|
||||
element = sel.getSelectedElement();
|
||||
if (!element) { return; }
|
||||
|
||||
var el = element.findOne('media-tag');
|
||||
if (!el) { return; }
|
||||
|
||||
var rect = el.getClientRect();
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
@@ -31,14 +67,81 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
var hInput = inputs[1];
|
||||
wInput.value = Math.round(rect.width);
|
||||
hInput.value = Math.round(rect.height);
|
||||
|
||||
var keepRatio = inputs[2];
|
||||
var ratio = wInput.value/hInput.value;
|
||||
|
||||
var borderInput = inputs[3];
|
||||
var bValue = el.getStyle('border-width').replace('px', '') || 0;
|
||||
borderInput.value = bValue;
|
||||
|
||||
var $preview = $(dialog).find('#ck-mediatag-preview');
|
||||
var $clone = $(el.$).clone();
|
||||
$clone.css({
|
||||
display: 'flex',
|
||||
'border-style': 'solid',
|
||||
'border-color': 'black'
|
||||
});
|
||||
$preview.html('').append($clone);
|
||||
|
||||
var center = function () {
|
||||
var top = Math.max(($(document).height()/2) - (thiss.getSize().height/2), 0);
|
||||
thiss.move(thiss.getPosition().x, top);
|
||||
};
|
||||
|
||||
var update = function () {
|
||||
var w = $(wInput).val() || Math.round(rect.width);
|
||||
var h = $(hInput).val() || Math.round(rect.height);
|
||||
var b = $(borderInput).val() || bValue;
|
||||
$clone.css({
|
||||
width: w+'px',
|
||||
height: h+'px',
|
||||
'border-width': b+'px'
|
||||
});
|
||||
center();
|
||||
};
|
||||
|
||||
$(wInput).on('keyup', function () {
|
||||
if (!$(keepRatio).is(':checked')) { return; }
|
||||
var w = $(wInput).val();
|
||||
if (isNaN(w)) { return; }
|
||||
var newVal = w/ratio;
|
||||
$(hInput).val(Math.round(newVal));
|
||||
update();
|
||||
});
|
||||
$(hInput).on('keyup', function () {
|
||||
if (!$(keepRatio).is(':checked')) { return; }
|
||||
var h = $(hInput).val();
|
||||
if (isNaN(h)) { return; }
|
||||
var newVal = h*ratio;
|
||||
$(wInput).val(Math.round(newVal));
|
||||
update();
|
||||
});
|
||||
$(keepRatio).on('change', function () {
|
||||
ratio = $(wInput).val()/$(hInput).val();
|
||||
});
|
||||
|
||||
$(borderInput).on('keyup', function () {
|
||||
update();
|
||||
});
|
||||
|
||||
setTimeout(center);
|
||||
},
|
||||
onOk: function() {
|
||||
var dialog = this;
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
|
||||
var sel = editor.getSelection();
|
||||
element = sel.getSelectedElement();
|
||||
if (!element) { return; }
|
||||
|
||||
var el = element.findOne('media-tag');
|
||||
if (!el) { return; }
|
||||
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
var wInput = inputs[0];
|
||||
var hInput = inputs[1];
|
||||
var bInput = inputs[3];
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (wInput.value === "") {
|
||||
@@ -53,6 +156,11 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
} else {
|
||||
el.setSize('height', parseInt(hInput.value));
|
||||
}
|
||||
if (bInput.value === "") {
|
||||
el.removeStyle('border-width');
|
||||
} else {
|
||||
el.setStyle('border-width', parseInt(bInput.value)+'px');
|
||||
}
|
||||
editor.fire( 'change' );
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview The Image plugin.
|
||||
*/
|
||||
|
||||
( function() {
|
||||
|
||||
CKEDITOR.plugins.add( 'mediatag', {
|
||||
requires: 'dialog',
|
||||
requires: 'dialog,widget',
|
||||
//icons: 'image',
|
||||
//hidpi: true,
|
||||
onLoad: function () {
|
||||
@@ -18,6 +9,9 @@
|
||||
CKEDITOR.addCss(
|
||||
'media-tag{' +
|
||||
'display:inline-block;' +
|
||||
'border-style: solid;' +
|
||||
'border-color: black;' +
|
||||
'border-width: 0;' +
|
||||
'}' +
|
||||
'media-tag.selected{' +
|
||||
'border: 1px solid black;' +
|
||||
@@ -34,148 +28,73 @@
|
||||
},
|
||||
init: function( editor ) {
|
||||
var pluginName = 'mediatag';
|
||||
var Messages = CKEDITOR._mediatagTranslations;
|
||||
var targetWidget;
|
||||
|
||||
// Register the dialog.
|
||||
CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' );
|
||||
|
||||
var allowed = 'media-tag[!data-crypto-key,!src,contenteditable,width,height]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
|
||||
required = 'media-tag[data-crypto-key,src]';
|
||||
editor.widgets.add( 'mediatag', {
|
||||
|
||||
// Register the command.
|
||||
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, {
|
||||
allowedContent: allowed,
|
||||
requiredContent: required,
|
||||
contentTransformations: [
|
||||
[ 'media-tag{width}: sizeToStyle', 'media-tag[width]: sizeToAttribute' ],
|
||||
[ 'media-tag{float}: alignmentToStyle', 'media-tag[align]: alignmentToAttribute' ]
|
||||
]
|
||||
} ) );
|
||||
getLabel: function () { return " "; },
|
||||
dialog: pluginName,
|
||||
inline: true,
|
||||
upcast: function( element ) {
|
||||
return element.name === 'media-tag';
|
||||
}
|
||||
|
||||
var isMediaTag = function (el) {
|
||||
if (el.is('media-tag')) { return el; }
|
||||
var mt = el.getParents().slice().filter(function (p) {
|
||||
return p.is('media-tag');
|
||||
});
|
||||
if (mt.length !== 1) { return; }
|
||||
return mt[0];
|
||||
};
|
||||
editor.on('doubleclick', function (evt) {
|
||||
var element = evt.data.element;
|
||||
var mt = isMediaTag(element);
|
||||
if (mt && !element.data('cke-realelement')) {
|
||||
editor.plugins.mediatag.clicked = mt;
|
||||
evt.data.dialog = 'mediatag';
|
||||
});
|
||||
|
||||
editor.addCommand('importMediatag', {
|
||||
exec: function (editor) {
|
||||
var w = targetWidget;
|
||||
targetWidget = undefined;
|
||||
var $mt = $(w.$).find('media-tag');
|
||||
editor.plugins.mediatag.import($mt);
|
||||
}
|
||||
});
|
||||
|
||||
// If the "contextmenu" plugin is loaded, register the listeners.
|
||||
if (editor.addMenuItems) {
|
||||
editor.addMenuGroup('mediatag');
|
||||
editor.addMenuItem('importMediatag', {
|
||||
label: Messages.import,
|
||||
icon: 'save',
|
||||
command: 'importMediatag',
|
||||
group: 'mediatag'
|
||||
});
|
||||
editor.addMenuItem('mediatag', {
|
||||
label: Messages.options,
|
||||
icon: 'image',
|
||||
command: 'mediatag',
|
||||
group: 'mediatag'
|
||||
});
|
||||
}
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function (element) {
|
||||
if (getSelectedMediatag(editor, element)) {
|
||||
return { mediatag: CKEDITOR.TRISTATE_OFF };
|
||||
if (element.is('.cke_widget_mediatag')
|
||||
|| element.getAttribute('data-cke-display-name') === 'media-tag') {
|
||||
targetWidget = element;
|
||||
return {
|
||||
mediatag: CKEDITOR.TRISTATE_OFF,
|
||||
importMediatag: CKEDITOR.TRISTATE_OFF,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
afterInit: function( editor ) {
|
||||
// Customize the behavior of the alignment commands. (http://dev.ckeditor.com/ticket/7430)
|
||||
setupAlignCommand('left');
|
||||
setupAlignCommand('right');
|
||||
setupAlignCommand('center');
|
||||
setupAlignCommand('block');
|
||||
|
||||
function setupAlignCommand (value) {
|
||||
var command = editor.getCommand('justify' + value);
|
||||
if (command) {
|
||||
if (value === 'left' || value === 'right') {
|
||||
command.on('exec', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
if (align === value) {
|
||||
img.removeStyle('float');
|
||||
|
||||
// Remove "align" attribute when necessary.
|
||||
if (value === getMediatagAlignment(img))
|
||||
img.removeAttribute( 'align' );
|
||||
} else {
|
||||
img.setStyle( 'float', value );
|
||||
}
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
command.on('refresh', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
|
||||
this.setState(
|
||||
(align === value) ? CKEDITOR.TRISTATE_ON : ( value === 'right' || value === 'left' ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
function getSelectedMediatag (editor, element) {
|
||||
if (!element) {
|
||||
var sel = editor.getSelection();
|
||||
element = sel.getSelectedElement();
|
||||
|
||||
CKEDITOR.on('dialogDefinition', function (ev) {
|
||||
var dialog = ev.data.definition;
|
||||
if (ev.data.name === 'image') {
|
||||
dialog.removeContents('Link');
|
||||
dialog.removeContents('advanced');
|
||||
//var info = dialog.getContents('info');
|
||||
//info.remove('cmbAlign');
|
||||
}
|
||||
});
|
||||
|
||||
if (element && element.is('media-tag') && !element.data('cke-realelement')
|
||||
&& !element.isReadOnly()) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
function getMediatagAlignment (element) {
|
||||
var align = element.getStyle('float');
|
||||
|
||||
if (align === 'inherit' || align === 'none') {
|
||||
align = 0;
|
||||
}
|
||||
|
||||
if (!align) {
|
||||
align = element.getAttribute('align');
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
} )();
|
||||
|
||||
/**
|
||||
* Determines whether dimension inputs should be automatically filled when the image URL changes in the Image plugin dialog window.
|
||||
*
|
||||
* config.image_prefillDimensions = false;
|
||||
*
|
||||
* @since 4.5
|
||||
* @cfg {Boolean} [image_prefillDimensions=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether to remove links when emptying the link URL field in the Image dialog window.
|
||||
*
|
||||
* config.image_removeLinkByEmptyURL = false;
|
||||
*
|
||||
* @cfg {Boolean} [image_removeLinkByEmptyURL=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
CKEDITOR.config.mediatag_removeLinkByEmptyURL = true;
|
||||
|
||||
/**
|
||||
* Padding text to set off the image in the preview area.
|
||||
*
|
||||
* config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 );
|
||||
*
|
||||
* @cfg {String} [image_previewText='Lorem ipsum dolor...' (placeholder text)]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ define([
|
||||
'jquery',
|
||||
'/common/toolbar3.js',
|
||||
'/common/common-util.js',
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-realtime.js',
|
||||
@@ -26,13 +25,12 @@ define([
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/poll/app-poll.less',
|
||||
], function (
|
||||
$,
|
||||
Toolbar,
|
||||
Util,
|
||||
Cryptget,
|
||||
nThen,
|
||||
SFCommon,
|
||||
CommonRealtime,
|
||||
@@ -647,7 +645,7 @@ define([
|
||||
|
||||
var updatePublishedDescription = function () {
|
||||
var v = APP.editor.getValue();
|
||||
DiffMd.apply(DiffMd.render(v || ''), APP.$descriptionPublished);
|
||||
DiffMd.apply(DiffMd.render(v || ''), APP.$descriptionPublished, common);
|
||||
};
|
||||
var updateDescription = function (old, n) {
|
||||
var o = APP.editor.getValue();
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
|
||||
@import (once) '../../customize/src/less2/include/avatar.less';
|
||||
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_profile-bg,
|
||||
@warn-color: @colortheme_profile-warn,
|
||||
@color: @colortheme_profile-color
|
||||
);
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.sidebar-layout_main();
|
||||
|
||||
// body
|
||||
&.cp-app-profile {
|
||||
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_profile-bg,
|
||||
@warn-color: @colortheme_profile-warn,
|
||||
@color: @colortheme_profile-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
#cp-app-profile-header {
|
||||
|
||||
@@ -19,8 +19,8 @@ define([
|
||||
'css!/bower_components/codemirror/addon/dialog/dialog.css',
|
||||
'css!/bower_components/codemirror/addon/fold/foldgutter.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/profile/app-profile.less',
|
||||
'/bower_components/croppie/croppie.min.js',
|
||||
'css!/bower_components/croppie/croppie.css',
|
||||
], function (
|
||||
@@ -43,16 +43,6 @@ define([
|
||||
_onRefresh: []
|
||||
};
|
||||
|
||||
// Decryption event for avatar mediatag (TODO not needed anymore?)
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
if (decrypted.callback) { decrypted.callback(); }
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
UI.alert(error.message);
|
||||
});
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cp-dropdown-content').hide();
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ define([
|
||||
var Hash = Utils.Hash;
|
||||
// 1st case: visiting someone else's profile with hash in the URL
|
||||
if (window.location.hash) {
|
||||
// No password for profiles
|
||||
return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1)));
|
||||
}
|
||||
var editHash;
|
||||
@@ -50,11 +51,17 @@ define([
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (editHash) {
|
||||
// No password for profile
|
||||
return void cb(null, Hash.getSecrets('profile', editHash));
|
||||
}
|
||||
// 3rd case: profile creation (create a new random hash, store it later if needed)
|
||||
if (!Utils.LocalStore.isLoggedIn()) { return void cb(); }
|
||||
var hash = Hash.createRandomHash();
|
||||
if (!Utils.LocalStore.isLoggedIn()) {
|
||||
// Unregistered users can't create a profile
|
||||
window.location.href = '/drive/';
|
||||
return void cb();
|
||||
}
|
||||
// No password for profile
|
||||
var hash = Hash.createRandomHash('profile');
|
||||
var secret = Hash.getSecrets('profile', hash);
|
||||
Cryptpad.pinPads([secret.channel], function (e) {
|
||||
if (e) {
|
||||
@@ -65,8 +72,8 @@ define([
|
||||
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
|
||||
}
|
||||
var profile = {};
|
||||
profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
|
||||
profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
|
||||
profile.edit = Utils.Hash.getEditHashFromKeys(secret);
|
||||
profile.view = Utils.Hash.getViewHashFromKeys(secret);
|
||||
Cryptpad.setNewProfile(profile);
|
||||
});
|
||||
cb(null, secret);
|
||||
@@ -75,7 +82,7 @@ define([
|
||||
var addRpc = function (sframeChan, Cryptpad, Utils) {
|
||||
// Adding a new avatar from the profile: pin it and store it in the object
|
||||
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
|
||||
var chanId = Utils.Hash.hrefToHexChannelId(data);
|
||||
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
|
||||
Cryptpad.pinPads([chanId], function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
Cryptpad.setAvatar(data, cb);
|
||||
@@ -83,7 +90,7 @@ define([
|
||||
});
|
||||
// Removing the avatar from the profile: unpin it
|
||||
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
|
||||
var chanId = Utils.Hash.hrefToHexChannelId(data);
|
||||
var chanId = Utils.Hash.hrefToHexChannelId(data, null);
|
||||
Cryptpad.unpinPads([chanId], function () {
|
||||
Cryptpad.setAvatar(undefined, cb);
|
||||
});
|
||||
@@ -93,6 +100,7 @@ define([
|
||||
getSecrets: getSecrets,
|
||||
noHash: true, // Don't add the hash in the URL if it doesn't already exist
|
||||
addRpc: addRpc,
|
||||
owned: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ define([
|
||||
'/common/common-feedback.js',
|
||||
'/common/outer/local-store.js',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
], function ($, Login, Cryptpad, Test, Cred, UI, Util, Realtime, Constants, Feedback, LocalStore) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -57,11 +57,6 @@ define([
|
||||
var test;
|
||||
|
||||
$register.click(function () {
|
||||
if (registering) {
|
||||
console.log("registration is already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
var uname = $uname.val();
|
||||
var passwd = $passwd.val();
|
||||
var confirmPassword = $confirm.val();
|
||||
@@ -118,15 +113,10 @@ define([
|
||||
}, 500);
|
||||
|
||||
$register.on('keypress', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
console.error(e.which);
|
||||
switch (e.which) {
|
||||
case 13: return clickRegister();
|
||||
case 13: return clickRegister();
|
||||
default:
|
||||
//console.log(e.which);
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return clickRegister();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
@import (once) "../../customize/src/less2/include/colortheme-all.less";
|
||||
@import (once) "../../customize/src/less2/include/browser.less";
|
||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (once) "../../customize/src/less2/include/limit-bar.less";
|
||||
@import (once) "../../customize/src/less2/include/creation.less";
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) "../../customize/src/less2/include/limit-bar.less";
|
||||
@import (reference) "../../customize/src/less2/include/creation.less";
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
.toolbar_main(
|
||||
@bg-color: @colortheme_settings-bg,
|
||||
@warn-color: @colortheme_settings-warn,
|
||||
@color: @colortheme_settings-color
|
||||
);
|
||||
.alertify_main();
|
||||
.sidebar-layout_main();
|
||||
.limit-bar_main();
|
||||
.creation_main();
|
||||
|
||||
// body
|
||||
&.cp-app-settings {
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_settings-bg,
|
||||
@warn-color: @colortheme_settings-warn,
|
||||
@color: @colortheme_settings-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
.limit-bar_main();
|
||||
.creation_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font: @colortheme_app-font;
|
||||
#cp-sidebarlayout-container {
|
||||
#cp-sidebarlayout-rightside {
|
||||
input[type="checkbox"] {
|
||||
@@ -57,6 +52,25 @@
|
||||
width: @sidebar_button-width;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-settings-autostore-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
label {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-settings-change-password {
|
||||
[type="password"], [type="text"] {
|
||||
width: @sidebar_button-width;
|
||||
flex: unset;
|
||||
}
|
||||
button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
.cp-settings-drive-backup {
|
||||
button {
|
||||
span.fa {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user