Merge branch 'staging' into debugtime

pull/1/head
yflory 5 years ago
commit ae78f0a5df

@ -34,4 +34,4 @@ customize/
www/debug/chainpad.dist.js
www/pad/mathjax/
www/code/mermaid.js
www/code/mermaid*.js

@ -30,8 +30,8 @@
"secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0",
"chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.8.1",
"chainpad": "^5.1.0",
"chainpad-listmap": "^0.9.0",
"chainpad": "^5.2.0",
"file-saver": "1.3.1",
"alertifyjs": "1.0.11",
"scrypt-async": "1.2.0",

@ -20,22 +20,36 @@ var getFileDescriptorLimit = function (env, server, cb) {
};
var getCacheStats = function (env, server, cb) {
var metaSize = 0;
var channelSize = 0;
var metaCount = 0;
var channelCount = 0;
var meta = env.metadata_cache;
for (var x in meta) {
if (meta.hasOwnProperty(x)) { metaCount++; }
}
var channels = env.channel_cache;
for (var y in channels) {
if (channels.hasOwnProperty(y)) { channelCount++; }
try {
var meta = env.metadata_cache;
for (var x in meta) {
if (meta.hasOwnProperty(x)) {
metaCount++;
metaSize += JSON.stringify(meta[x]).length;
}
}
var channels = env.channel_cache;
for (var y in channels) {
if (channels.hasOwnProperty(y)) {
channelCount++;
channelSize += JSON.stringify(channels[y]).length;
}
}
} catch (err) {
return void cb(err && err.message);
}
cb(void 0, {
metadata: metaCount,
metaSize: metaSize,
channel: channelCount,
channelSize: channelSize,
});
};

@ -36,7 +36,10 @@ Quota.applyCustomLimits = function (Env) {
Quota.updateCachedLimits = function (Env, cb) {
Quota.applyCustomLimits(Env);
if (Env.allowSubscriptions === false || Env.blockDailyCheck === true) { return void cb(); }
if (Env.blockDailyCheck === true ||
(typeof(Env.blockDailyCheck) === 'undefined' && Env.adminEmail === false && Env.allowSubscriptions === false)) {
return void cb();
}
var body = JSON.stringify({
domain: Env.myDomain,

@ -235,6 +235,54 @@ const getIndex = (Env, channelName, cb) => {
});
};
/* checkOffsetMap
Sorry for the weird function --ansuz
This should be almost equivalent to `Object.keys(map).length` except
that is will use less memory by not allocating space for the temporary array.
Beyond that, it returns length * -1 if any of the members of the map
are not in ascending order. The function for removing older members of the map
loops over elements in order and deletes them, so ordering is important!
*/
var checkOffsetMap = function (map) {
var prev = 0;
var cur;
var ooo = 0; // out of order
var count = 0;
for (let k in map) {
count++;
cur = map[k];
if (!ooo && prev > cur) { ooo = true; }
prev = cur;
}
return ooo ? count * -1: count;
};
/* Pass the map and the number of elements it contains */
var trimOffsetByOrder = function (map, n) {
var toRemove = Math.max(n - 50, 0);
var i = 0;
for (let k in map) {
if (i >= toRemove) { return; }
i++;
delete map[k];
}
};
/* Remove from the map any byte offsets which are below
the lowest offset you'd like to preserve
(probably the oldest checkpoint */
var trimMapByOffset = function (map, offset) {
if (!offset) { return; }
for (let k in map) {
if (map[k] < offset) {
delete map[k];
}
}
};
/* storeMessage
* channel id
* the message to store
@ -286,17 +334,28 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
if (typeof (index.line) === "number") { index.line++; }
if (isCp) {
index.cpIndex = sliceCpIndex(index.cpIndex, index.line || 0);
for (let k in index.offsetByHash) {
if (index.offsetByHash[k] < index.cpIndex[0]) {
delete index.offsetByHash[k];
}
}
trimMapByOffset(index.offsetByHash, index.cpIndex[0]);
index.cpIndex.push({
offset: index.size,
line: ((index.line || 0) + 1)
});
}
if (optionalMessageHash) { index.offsetByHash[optionalMessageHash] = index.size; }
if (optionalMessageHash) {
index.offsetByHash[optionalMessageHash] = index.size;
index.offsets++;
}
if (index.offsets >= 100 && !index.cpIndex.length) {
let offsetCount = checkOffsetMap(index.offsetByHash);
if (offsetCount < 0) {
Log.warn('OFFSET_TRIM_OOO', {
channel: id,
map: index.OffsetByHash
});
} else if (offsetCount > 0) {
trimOffsetByOrder(index.offsetByHash, index.offsets);
index.offsets = checkOffsetMap(index.offsetByHash);
}
}
index.size += msgBin.length;
// handle the next element in the queue
@ -324,7 +383,7 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
* it has a side-effect of filling the index cache if it's empty
1. if you provided a lastKnownHash and that message does not exist in the history:
* either the client has made a mistake or the history they knew about no longer exists
* call back with EINVAL
* call back with EUNKNOWN
2. if you did not provide a lastKnownHash
* and there are fewer than two checkpoints:
* return 0 (read from the start of the file)
@ -348,19 +407,9 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
// check if the "hash" the client is requesting exists in the index
const lkh = index.offsetByHash[lastKnownHash];
// we evict old hashes from the index as new checkpoints are discovered.
// if someone connects and asks for a hash that is no longer relevant,
// we tell them it's an invalid request. This is because of the semantics of "GET_HISTORY"
// which is only ever used when connecting or reconnecting in typical uses of history...
// this assumption should hold for uses by chainpad, but perhaps not for other uses cases.
// EXCEPT: other cases don't use checkpoints!
// clients that are told that their request is invalid should just make another request
// without specifying the hash, and just trust the server to give them the relevant data.
// QUESTION: does this mean mailboxes are causing the server to store too much stuff in memory?
if (lastKnownHash && typeof(lkh) !== "number") {
waitFor.abort();
return void cb(new Error('EINVAL'));
}
// fall through to the next block if the offset of the hash in question is not in memory
if (lastKnownHash && typeof(lkh) !== "number") { return; }
// Since last 2 checkpoints
if (!lastKnownHash) {
@ -382,13 +431,13 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
offset = lkh;
}));
}).nThen((w) => {
// if offset is less than zero then presumably the channel has no messages
// returning falls through to the next block and therefore returns -1
// skip past this block if the offset is anything other than -1
// this basically makes these first two nThen blocks behave like if-else
if (offset !== -1) { return; }
// do a lookup from the index
// FIXME maybe we don't need this anymore?
// otherwise we have a non-negative offset and we can start to read from there
// either the message exists in history but is not in the cached index
// or it does not exist at all. In either case 'getHashOffset' is expected
// to return a number: -1 if not present, positive interger otherwise
Env.getHashOffset(channelName, lastKnownHash, w(function (err, _offset) {
if (err) {
w.abort();
@ -423,7 +472,9 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
offset = os;
}));
}).nThen((waitFor) => {
if (offset === -1) { return void cb(new Error("could not find offset")); }
if (offset === -1) {
return void cb(new Error('EUNKNOWN'));
}
const start = (beforeHash) ? 0 : offset;
store.readMessagesBin(channelName, start, (msgObj, readMore, abort) => {
if (beforeHash && msgObj.offset >= offset) { return void abort(); }

@ -118,6 +118,7 @@ const computeIndex = function (data, cb) {
const CB = Util.once(cb);
const offsetByHash = {};
let offsetCount = 0;
let size = 0;
nThen(function (w) {
// iterate over all messages in the channel log
@ -151,6 +152,9 @@ const computeIndex = function (data, cb) {
// so clear the buffer every time you see a new one
messageBuf = [];
}
} else if (messageBuf.length > 100 && cpIndex.length === 0) {
// take the last 50 messages
messageBuf = messageBuf.slice(-50);
}
// if it's not metadata or a checkpoint then it should be a regular message
// store it in the buffer
@ -163,6 +167,7 @@ const computeIndex = function (data, cb) {
}
// once indexing is complete you should have a buffer of messages since the latest checkpoint
// or the 50-100 latest messages if the channel is of a type without checkpoints.
// map the 'hash' of each message to its byte offset in the log, to be used for reconnecting clients
messageBuf.forEach((msgObj) => {
const msg = HK.tryParse(Env, msgObj.buff.toString('utf8'));
@ -171,6 +176,7 @@ const computeIndex = function (data, cb) {
// msgObj.offset is API guaranteed by our storage module
// it should always be a valid positive integer
offsetByHash[HK.getHash(msg[4])] = msgObj.offset;
offsetCount++;
}
// There is a trailing \n at the end of the file
size = msgObj.offset + msgObj.buff.length + 1;
@ -182,6 +188,7 @@ const computeIndex = function (data, cb) {
// Only keep the checkpoints included in the last 100 messages
cpIndex: HK.sliceCpIndex(cpIndex, i),
offsetByHash: offsetByHash,
offsets: offsetCount,
size: size,
//metadata: metadata,
line: i
@ -346,7 +353,8 @@ const getMultipleFileSize = function (data, cb) {
const getHashOffset = function (data, cb) {
const channelName = data.channel;
const lastKnownHash = data.lastKnownHash;
const lastKnownHash = data.hash;
if (typeof(lastKnownHash) !== 'string') { return void cb("INVALID_HASH"); }
var offset = -1;
store.readMessagesBin(channelName, 0, (msgObj, readMore, abort) => {

@ -0,0 +1,235 @@
/* globals process */
var Client = require("../../lib/client/");
var Crypto = require("../../www/bower_components/chainpad-crypto");
var Mailbox = Crypto.Mailbox;
var Nacl = require("tweetnacl/nacl-fast");
var nThen = require("nthen");
var Pinpad = require("../../www/common/pinpad");
var Rpc = require("../../www/common/rpc");
var Hash = require("../../www/common/common-hash");
var CpNetflux = require("../../www/bower_components/chainpad-netflux");
var Util = require("../../lib/common-util");
// you need more than 100 messages in the history, and you need a lastKnownHash between "50" and "length - 50"
var createMailbox = function (config, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var webchannel;
var user = config.user;
user.messages = [];
CpNetflux.start({
network: config.network,
channel: config.channel,
crypto: config.crypto,
owners: [ config.edPublic ],
noChainPad: true,
lastKnownHash: config.lastKnownHash,
onChannelError: function (err) {
cb(err);
},
onConnect: function (wc /*, sendMessage */) {
webchannel = wc;
},
onMessage: function (msg /*, user, vKey, isCp, hash, author */) {
user.messages.push(msg);
},
onReady: function () {
cb(void 0, webchannel);
},
});
};
process.on('unhandledRejection', function (err) {
console.error(err);
});
var state = {};
var makeCurveKeys = function () {
var pair = Nacl.box.keyPair();
return {
curvePrivate: Nacl.util.encodeBase64(pair.secretKey),
curvePublic: Nacl.util.encodeBase64(pair.publicKey),
};
};
var makeEdKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
return {
edPrivate: Nacl.util.encodeBase64(keys.secretKey),
edPublic: Nacl.util.encodeBase64(keys.publicKey),
};
};
var edKeys = makeEdKeys();
var curveKeys = makeCurveKeys();
var mailboxChannel = Hash.createChannelId();
var createUser = function (config, cb) {
// config should contain keys for a team rpc (ed)
// teamEdKeys
// rosterHash
var user;
nThen(function (w) {
Client.create(w(function (err, client) {
if (err) {
w.abort();
return void cb(err);
}
user = client;
user.destroy = Util.mkEvent(true);
user.destroy.reg(function () {
user.network.disconnect();
});
}));
}).nThen(function (w) {
// make all the parameters you'll need
var network = user.network = user.config.network;
user.edKeys = edKeys;
user.curveKeys = curveKeys;
user.mailbox = Mailbox.createEncryptor(user.curveKeys);
user.mailboxChannel = mailboxChannel;
// create an anon rpc for alice
Rpc.createAnonymous(network, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
return void console.error('ANON_RPC_CONNECT_ERR');
}
user.anonRpc = rpc;
user.destroy.reg(function () {
user.anonRpc.destroy();
});
}));
Pinpad.create(network, user.edKeys, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
console.error(err);
return console.log('RPC_CONNECT_ERR');
}
user.rpc = rpc;
user.destroy.reg(function () {
user.rpc.destroy();
});
}));
}).nThen(function (w) {
// create and subscribe to your mailbox
createMailbox({
user: user,
lastKnownHash: config.lastKnownHash,
network: user.network,
channel: user.mailboxChannel,
crypto: user.mailbox,
edPublic: user.edKeys.edPublic,
}, w(function (err /*, wc*/) {
if (err) {
w.abort();
//console.error("Mailbox creation error");
cb(err);
//process.exit(1);
}
//wc.leave();
}));
}).nThen(function () {
user.cleanup = function (cb) {
//console.log("Destroying user");
// TODO remove your mailbox
user.destroy.fire();
cb = cb;
};
cb(void 0, user);
});
};
var alice;
nThen(function (w) {
createUser({
//sharedConfig
}, w(function (err, _alice) {
if (err) {
w.abort();
return void console.log(err);
}
alice = _alice;
alice.name = 'alice';
}));
/*
createUser(sharedConfig, w(function (err, _bob) {
if (err) {
w.abort();
return void console.log(err);
}
bob = _bob;
bob.name = 'bob';
}));*/
}).nThen(function (w) {
var i = 0;
var next = w();
state.hashes = [];
var send = function () {
if (i++ >= 160) { return next(); }
var msg = alice.mailbox.encrypt(JSON.stringify({
pewpew: 'bangbang',
}), alice.curveKeys.curvePublic);
var hash = msg.slice(0, 64);
state.hashes.push(hash);
alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [
alice.mailboxChannel,
msg
//Nacl.util.encodeBase64(Nacl.randomBytes(128))
], w(function (err) {
if (err) { throw new Error(err); }
console.log('message %s written successfully', i);
setTimeout(send, 15);
}));
};
send();
}).nThen(function (w) {
console.log("Connecting with second user");
createUser({
lastKnownHash: state.hashes[55],
}, w(function (err, _alice) {
if (err) {
w.abort();
console.log("lastKnownHash: ", state.hashes[55]);
console.log(err);
process.exit(1);
//return void console.log(err);
}
var user = state.alice2 = _alice;
if (user.messages.length === 105) {
process.exit(0);
}
//console.log(user.messages, user.messages.length);
process.exit(1);
}));
}).nThen(function () {
}).nThen(function () {
alice.cleanup();
//bob.cleanup();
});

@ -0,0 +1,4 @@
.sectionTitle, .titleText {
font-weight: bold;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -15,12 +15,13 @@ define([
'/customize/pages.js',
'/bower_components/nthen/index.js',
'/common/inner/invitation.js',
'/common/visible.js',
'css!/customize/fonts/cptools/style.css',
'/bower_components/croppie/croppie.min.js',
'css!/bower_components/croppie/croppie.css',
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
Messages, AppConfig, Pages, NThen, InviteInner) {
Messages, AppConfig, Pages, NThen, InviteInner, Visible) {
var UIElements = {};
// Configure MediaTags to use our local viewer
@ -99,857 +100,6 @@ define([
});
};
};
/*
var getPropertiesData = function (common, cb) {
var data = {};
NThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
common.getPadAttribute('', waitFor(function (err, val) {
if (err || !val) {
waitFor.abort();
return void cb(err || 'EEMPTY');
}
if (!val.fileType) {
delete val.owners;
delete val.expire;
}
Util.extend(data, val);
if (data.href) { data.href = base + data.href; }
if (data.roHref) { data.roHref = base + data.roHref; }
}));
common.getPadMetadata(null, waitFor(function (obj) {
if (obj && obj.error) { return; }
data.owners = obj.owners;
data.expire = obj.expire;
data.pending_owners = obj.pending_owners;
}));
}).nThen(function () {
cb(void 0, data);
});
};
*/
var getPropertiesData = function (common, opts, cb) {
opts = opts || {};
var data = {};
NThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
common.getPadAttribute('', waitFor(function (err, val) {
if (err || !val) {
waitFor.abort();
return void cb(err || 'EEMPTY');
}
if (!val.fileType) {
delete val.owners;
delete val.expire;
}
Util.extend(data, val);
if (data.href) { data.href = base + data.href; }
if (data.roHref) { data.roHref = base + data.roHref; }
}), opts.href);
// If this is a file, don't try to look for metadata
if (opts.channel && opts.channel.length > 34) { return; }
common.getPadMetadata({
channel: opts.channel // optional, fallback to current pad
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
data.owners = obj.owners;
data.expire = obj.expire;
data.pending_owners = obj.pending_owners;
}));
}).nThen(function () {
cb(void 0, data);
});
};
/*
var createOwnerModal = function (common, data) {
var friends = common.getFriends(true);
var sframeChan = common.getSframeChannel();
var priv = common.getMetadataMgr().getPrivateData();
var user = common.getMetadataMgr().getUserData();
var edPublic = priv.edPublic;
var channel = data.channel;
var owners = data.owners || [];
var pending_owners = data.pending_owners || [];
var teams = priv.teams;
var teamOwner = data.teamId;
var redrawAll = function () {};
var div1 = h('div.cp-usergrid-user.cp-share-column.cp-ownership');
var div2 = h('div.cp-usergrid-user.cp-share-column.cp-ownership');
var $div1 = $(div1);
var $div2 = $(div2);
// Remove owner column
var drawRemove = function (pending) {
var _owners = {};
var o = (pending ? pending_owners : owners) || [];
o.forEach(function (ed) {
var f;
Object.keys(friends).some(function (c) {
if (friends[c].edPublic === ed) {
f = friends[c];
return true;
}
});
Object.keys(teams).some(function (id) {
if (teams[id].edPublic === ed) {
f = teams[id];
f.teamId = id;
}
});
if (ed === edPublic) {
f = f || user;
if (f.name) { f.edPublic = edPublic; }
}
_owners[ed] = f || {
displayName: Messages._getKey('owner_unknownUser', [ed]),
edPublic: ed,
};
});
var msg = pending ? Messages.owner_removePendingText
: Messages.owner_removeText;
var removeCol = UIElements.getUserGrid(msg, {
common: common,
large: true,
data: _owners,
noFilter: true
}, function () {
});
var $div = $(removeCol.div);
// When clicking on the remove button, we check the selected users.
// If you try to remove yourself, we'll display an additional warning message
var btnMsg = pending ? Messages.owner_removePendingButton : Messages.owner_removeButton;
var removeButton = h('button.no-margin', btnMsg);
$(removeButton).click(function () {
// Check selection
var $sel = $div.find('.cp-usergrid-user.cp-selected');
var sel = $sel.toArray();
if (!sel.length) { return; }
var me = false;
var toRemove = sel.map(function (el) {
var ed = $(el).attr('data-ed');
if (!ed) { return; }
if (teamOwner && teams[teamOwner] && teams[teamOwner].edPublic === ed) { me = true; }
if (ed === edPublic && !teamOwner) { me = true; }
return ed;
}).filter(function (x) { return x; });
NThen(function (waitFor) {
var msg = me ? Messages.owner_removeMeConfirm : Messages.owner_removeConfirm;
UI.confirm(msg, waitFor(function (yes) {
if (!yes) {
waitFor.abort();
return;
}
}));
}).nThen(function (waitFor) {
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
value: toRemove,
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
UI.log(Messages.saved);
}));
}).nThen(function (waitFor) {
sel.forEach(function (el) {
var curve = $(el).attr('data-curve');
var friend = curve === user.curvePublic ? user : friends[curve];
if (!friend) { return; }
common.mailbox.sendTo("RM_OWNER", {
channel: channel,
title: data.title,
pending: pending
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, waitFor());
});
}).nThen(function () {
redrawAll();
});
});
$div.append(h('p', removeButton));
return $div;
};
// Add owners column
var drawAdd = function () {
var $div = $(h('div.cp-share-column'));
var _friends = JSON.parse(JSON.stringify(friends));
Object.keys(_friends).forEach(function (curve) {
if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
pending_owners.indexOf(_friends[curve].edPublic) !== -1 ||
!_friends[curve].notifications) {
delete _friends[curve];
}
});
var addCol = UIElements.getUserGrid(Messages.owner_addText, {
common: common,
large: true,
data: _friends
}, function () {
//console.log(arguments);
});
$div.append(addCol.div);
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
Object.keys(teamsData).forEach(function (id) {
var t = teamsData[id];
t.teamId = id;
if (owners.indexOf(t.edPublic) !== -1 || pending_owners.indexOf(t.edPublic) !== -1) {
delete teamsData[id];
}
});
var teamsList = UIElements.getUserGrid(Messages.owner_addTeamText, {
common: common,
large: true,
noFilter: true,
data: teamsData
}, function () {});
$div.append(teamsList.div);
// When clicking on the add button, we get the selected users.
var addButton = h('button.no-margin', Messages.owner_addButton);
$(addButton).click(function () {
// Check selection
var $sel = $div.find('.cp-usergrid-user.cp-selected');
var sel = $sel.toArray();
if (!sel.length) { return; }
var toAdd = sel.map(function (el) {
var curve = $(el).attr('data-curve');
// If the pad is woned by a team, we can transfer ownership to ourselves
if (curve === user.curvePublic && teamOwner) { return priv.edPublic; }
var friend = friends[curve];
if (!friend) { return; }
return friend.edPublic;
}).filter(function (x) { return x; });
var toAddTeams = sel.map(function (el) {
var team = teamsData[$(el).attr('data-teamid')];
if (!team || !team.edPublic) { return; }
return {
edPublic: team.edPublic,
id: $(el).attr('data-teamid')
};
}).filter(function (x) { return x; });
NThen(function (waitFor) {
var msg = Messages.owner_addConfirm;
UI.confirm(msg, waitFor(function (yes) {
if (!yes) {
waitFor.abort();
return;
}
}));
}).nThen(function (waitFor) {
// Add one of our teams as an owner
if (toAddTeams.length) {
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: 'ADD_OWNERS',
value: toAddTeams.map(function (obj) { return obj.edPublic; }),
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll();
var text = err === "INSUFFICIENT_PERMISSIONS" ?
Messages.fm_forbidden : Messages.error;
return void UI.warn(text);
}
var isTemplate = priv.isTemplate || data.isTemplate;
toAddTeams.forEach(function (obj) {
sframeChan.query('Q_STORE_IN_TEAM', {
href: data.href || data.rohref,
password: data.password,
path: isTemplate ? ['template'] : undefined,
title: data.title || '',
teamId: obj.id
}, waitFor(function (err) {
if (err) { return void console.error(err); }
}));
});
}));
}
}).nThen(function (waitFor) {
// Offer ownership to a friend
if (toAdd.length) {
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: 'ADD_PENDING_OWNERS',
value: toAdd,
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
}));
}
}).nThen(function (waitFor) {
sel.forEach(function (el) {
var curve = $(el).attr('data-curve');
var friend = curve === user.curvePublic ? user : friends[curve];
if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", {
channel: channel,
href: data.href,
password: data.password,
title: data.title
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, waitFor());
});
}).nThen(function () {
redrawAll();
UI.log(Messages.saved);
});
});
$div.append(h('p', addButton));
return $div;
};
redrawAll = function (md) {
var todo = function (obj) {
if (obj && obj.error) { return; }
owners = obj.owners || [];
pending_owners = obj.pending_owners || [];
$div1.empty();
$div2.empty();
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
};
if (md) { return void todo(md); }
common.getPadMetadata({
channel: data.channel
}, todo);
};
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
if (!$div1.length) {
return void handler.stop();
}
owners = md.owners || [];
pending_owners = md.pending_owners || [];
redrawAll(md);
});
// Create modal
var link = h('div.cp-share-columns', [
div1,
div2
// drawRemove()[0],
//drawAdd()[0]
]);
var linkButtons = [{
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [27]
}];
return UI.dialog.customModal(link, {buttons: linkButtons});
};
*/
/*
var getRightsProperties = function (common, data, cb) {
var $div = $('<div>');
if (!data) { return void cb(void 0, $div); }
var draw = function () {
var $d = $('<div>');
var priv = common.getMetadataMgr().getPrivateData();
var user = common.getMetadataMgr().getUserData();
var edPublic = priv.edPublic;
var owned = false;
var _owners = {};
if (data.owners && data.owners.length) {
if (data.owners.indexOf(edPublic) !== -1) {
owned = true;
} else {
Object.keys(priv.teams || {}).some(function (id) {
var team = priv.teams[id] || {};
if (team.viewer) { return; }
if (data.owners.indexOf(team.edPublic) === -1) { return; }
owned = Number(id);
return true;
});
}
var strangers = 0;
data.owners.forEach(function (ed) {
// If a friend is an owner, add their name to the list
// otherwise, increment the list of strangers
// Our edPublic? print "Yourself"
if (ed === edPublic) {
_owners[ed] = {
selected: true,
name: user.name,
avatar: user.avatar
};
return;
}
// One of our teams? print the team name
if (Object.keys(priv.teams || {}).some(function (id) {
var team = priv.teams[id] || {};
if (team.edPublic !== ed) { return; }
_owners[ed] = {
name: team.name,
avatar: team.avatar
};
return true;
})) {
return;
}
// One of our friends? print the friend name
if (Object.keys(priv.friends || {}).some(function (c) {
var friend = priv.friends[c] || {};
if (friend.edPublic !== ed || c === 'me') { return; }
_owners[friend.edPublic] = {
name: friend.displayName,
avatar: friend.avatar
};
return true;
})) {
return;
}
// Otherwise it's a stranger
strangers++;
});
if (strangers) {
_owners['stangers'] = {
name: Messages._getKey('properties_unknownUser', [strangers]),
};
}
}
var _ownersGrid = UIElements.getUserGrid(Messages.creation_owners, {
common: common,
noSelect: true,
data: _owners,
large: true
}, function () {});
if (_ownersGrid && Object.keys(_owners).length) {
$d.append(_ownersGrid.div);
} else {
$d.append([
h('label', Messages.creation_owners),
]);
$d.append(UI.dialog.selectable(Messages.creation_noOwner, {
id: 'cp-app-prop-owners',
}));
}
var parsed;
if (data.href || data.roHref) {
parsed = Hash.parsePadUrl(data.href || data.roHref);
}
if (owned && parsed.hashData.type === 'pad') {
var manageOwners = h('button.no-margin', Messages.owner_openModalButton);
$(manageOwners).click(function () {
data.teamId = typeof(owned) !== "boolean" ? owned : undefined;
var modal = createOwnerModal(common, data);
UI.openCustomModal(modal, {
wide: true,
});
});
$d.append(h('p', manageOwners));
}
if (!data.noExpiration) {
var expire = Messages.creation_expireFalse;
if (data.expire && typeof (data.expire) === "number") {
expire = new Date(data.expire).toLocaleString();
}
$('<label>', {'for': 'cp-app-prop-expire'}).text(Messages.creation_expiration)
.appendTo($d);
$d.append(UI.dialog.selectable(expire, {
id: 'cp-app-prop-expire',
}));
}
if (!data.noPassword) {
var hasPassword = data.password;
var $pwLabel = $('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
.hide().appendTo($d);
var password = UI.passwordInput({
id: 'cp-app-prop-password',
readonly: 'readonly'
});
var $password = $(password).hide();
var $pwInput = $password.find('.cp-password-input');
$pwInput.val(data.password).click(function () {
$pwInput[0].select();
});
$d.append(password);
if (hasPassword) {
$pwLabel.show();
$password.css('display', 'flex');
}
// In the properties, we should have the edit href if we know it.
// We should know it because the pad is stored, but it's better to check...
if (!data.noEditPassword && owned && data.href) { // FIXME SHEET fix password change for sheets
var sframeChan = common.getSframeChannel();
var isOO = parsed.type === 'sheet';
var isFile = parsed.hashData.type === 'file';
var isSharedFolder = parsed.type === 'drive';
var changePwTitle = Messages.properties_changePassword;
var changePwConfirm = isFile ? Messages.properties_confirmChangeFile : Messages.properties_confirmChange;
if (!hasPassword) {
changePwTitle = Messages.properties_addPassword;
changePwConfirm = isFile ? Messages.properties_confirmNewFile : Messages.properties_confirmNew;
}
$('<label>', {'for': 'cp-app-prop-change-password'})
.text(changePwTitle).appendTo($d);
var newPassword = UI.passwordInput({
id: 'cp-app-prop-change-password',
style: 'flex: 1;'
});
var passwordOk = h('button', Messages.properties_changePasswordButton);
var changePass = h('span.cp-password-change-container', [
newPassword,
passwordOk
]);
var pLocked = false;
$(passwordOk).click(function () {
var newPass = $(newPassword).find('input').val();
if (data.password === newPass ||
(!data.password && !newPass)) {
return void UI.alert(Messages.properties_passwordSame);
}
if (pLocked) { return; }
pLocked = true;
UI.confirm(changePwConfirm, function (yes) {
if (!yes) { pLocked = false; return; }
$(passwordOk).html('').append(h('span.fa.fa-spinner.fa-spin', {style: 'margin-left: 0'}));
var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' :
(isOO ? 'Q_OO_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE');
// If this is a file password change, register to the upload events:
// * if there is a pending upload, ask if we shoudl interrupt
// * display upload progress
var onPending;
var onProgress;
if (isFile) {
onPending = sframeChan.on('Q_BLOB_PASSWORD_CHANGE_PENDING', function (data, cb) {
onPending.stop();
UI.confirm(Messages.upload_uploadPending, function (yes) {
cb({cancel: yes});
});
});
onProgress = sframeChan.on('EV_BLOB_PASSWORD_CHANGE_PROGRESS', function (data) {
if (typeof (data) !== "number") { return; }
var p = Math.round(data);
$(passwordOk).text(p + '%');
});
}
sframeChan.query(q, {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
href: data.href,
password: newPass
}, function (err, data) {
$(passwordOk).text(Messages.properties_changePasswordButton);
pLocked = false;
if (err || data.error) {
console.error(err || data.error);
return void UI.alert(Messages.properties_passwordError);
}
UI.findOKButton().click();
$pwLabel.show();
$password.css('display', 'flex');
$pwInput.val(newPass);
// If the current document is a file or if we're changing the password from a drive,
// we don't have to reload the page at the end.
// Tell the user the password change was successful and abort
if (isFile || priv.app !== parsed.type) {
if (onProgress && onProgress.stop) { onProgress.stop(); }
$(passwordOk).text(Messages.properties_changePasswordButton);
var alertMsg = data.warning ? Messages.properties_passwordWarningFile
: Messages.properties_passwordSuccessFile;
return void UI.alert(alertMsg, undefined, {force: true});
}
// Pad password changed: update the href
// Use hidden hash if needed (we're an owner of this pad so we know it is stored)
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
var href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
if (useUnsafe === false) {
var newParsed = Hash.parsePadUrl(href);
var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass);
var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {});
href = Hash.hashToHref(newHash, parsed.type);
}
if (data.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () {
common.gotoURL(href);
}, {force: true});
}
return void UI.alert(Messages.properties_passwordSuccess, function () {
if (!isSharedFolder) {
common.gotoURL(href);
}
}, {force: true});
});
});
});
$d.append(changePass);
}
}
return $d;
};
var sframeChan = common.getSframeChannel();
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
if (!$div.length) {
handler.stop();
return;
}
md = JSON.parse(JSON.stringify(md));
data.owners = md.owners;
data.expire = md.expire;
data.pending_owners = md.pending_owners;
$div.empty();
$div.append(draw());
});
$div.append(draw());
cb(void 0, $div);
};
*/
var getPadProperties = function (common, data, opts, cb) {
opts = opts || {};
var $d = $('<div>');
if (!data) { return void cb(void 0, $d); }
var priv = common.getMetadataMgr().getPrivateData();
var edPublic = priv.edPublic;
if (data.href) {
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
$d.append(UI.dialog.selectable(data.href, {
id: 'cp-app-prop-link',
}));
}
if (data.roHref && !opts.noReadOnly) {
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
$d.append(UI.dialog.selectable(data.roHref, {
id: 'cp-app-prop-rolink',
}));
}
if (data.tags && Array.isArray(data.tags)) {
$d.append(h('div.cp-app-prop', [Messages.fm_prop_tagsList, h('br'), h('span.cp-app-prop-content', data.tags.join(', '))]));
}
if (data.ctime) {
$d.append(h('div.cp-app-prop', [Messages.fm_creation, h('br'), h('span.cp-app-prop-content', new Date(data.ctime).toLocaleString())]));
}
if (data.atime) {
$d.append(h('div.cp-app-prop', [Messages.fm_lastAccess, h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
}
var owned = false;
if (common.isLoggedIn()) {
if (Array.isArray(data.owners)) {
if (data.owners.indexOf(edPublic) !== -1) {
owned = true;
} else {
Object.keys(priv.teams || {}).some(function (id) {
var team = priv.teams[id] || {};
if (team.viewer) { return; }
if (data.owners.indexOf(team.edPublic) === -1) { return; }
owned = Number(id);
return true;
});
}
}
// check the size of this file...
var bytes = 0;
var historyBytes;
var chan = [data.channel];
if (data.rtChannel) { chan.push(data.rtChannel); }
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
var channels = chan.filter(function (c) { return c.length === 32; }).map(function (id) {
if (id === data.rtChannel && data.lastVersion && data.lastCpHash) {
return {
channel: id,
lastKnownHash: data.lastCpHash
};
}
return {
channel: id
};
});
var history = common.makeUniversal('history');
var trimChannels = [];
NThen(function (waitFor) {
chan.forEach(function (c) {
common.getFileSize(c, waitFor(function (e, _bytes) {
if (e) {
// there was a problem with the RPC
console.error(e);
}
bytes += _bytes;
}));
});
if (!owned) { return; }
history.execCommand('GET_HISTORY_SIZE', {
pad: true,
channels: channels,
teamId: typeof(owned) === "number" && owned
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
historyBytes = obj.size;
trimChannels = obj.channels;
}));
}).nThen(function () {
if (bytes === 0) { return void cb(void 0, $d); }
var formatted = UIElements.prettySize(bytes);
if (!owned || !historyBytes || historyBytes > bytes || historyBytes < 0) {
$d.append(h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('span.cp-app-prop-content', formatted)
]));
return void cb(void 0, $d);
}
var p = Math.round((historyBytes / bytes) * 100);
var historyPrettySize = UIElements.prettySize(historyBytes);
var contentsPrettySize = UIElements.prettySize(bytes - historyBytes);
var button;
var spinner = UI.makeSpinner();
var size = h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('div.cp-app-prop-size-container', [
h('div.cp-app-prop-size-history', { style: 'width:'+p+'%;' })
]),
h('div.cp-app-prop-size-legend', [
h('div.cp-app-prop-history-size', [
h('span.cp-app-prop-history-size-color'),
h('span.cp-app-prop-content', Messages._getKey('historyTrim_historySize', [historyPrettySize]))
]),
h('div.cp-app-prop-contents-size', [
h('span.cp-app-prop-contents-size-color'),
h('span.cp-app-prop-content', Messages._getKey('historyTrim_contentsSize', [contentsPrettySize]))
]),
]),
button = h('button.btn.btn-danger-alt.no-margin', Messages.trimHistory_button),
spinner.spinner
]);
$d.append(size);
var $button = $(button);
UI.confirmButton(button, {
classes: 'btn-danger'
}, function () {
$button.remove();
spinner.spin();
history.execCommand('TRIM_HISTORY', {
pad: true,
channels: trimChannels,
teamId: typeof(owned) === "number" && owned
}, function (obj) {
spinner.hide();
if (obj && obj.error) {
$(size).append(h('div.alert.alert-danger', Messages.trimHistory_error));
return;
}
$(size).remove();
var formatted = UIElements.prettySize(bytes - historyBytes);
$d.append(h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('span.cp-app-prop-content', formatted)
]));
$d.append(h('div.alert.alert-success', Messages.trimHistory_success));
});
});
cb(void 0, $d);
});
} else {
cb(void 0, $d);
}
};
UIElements.getProperties = function (common, opts, cb) {
var data;
var content;
var button = [{
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [13,27]
}];
NThen(function (waitFor) {
getPropertiesData(common, opts, waitFor(function (e, _data) {
if (e) {
waitFor.abort();
return void cb(e);
}
data = _data;
}));
}).nThen(function (waitFor) {
getPadProperties(common, data, opts, waitFor(function (e, c) {
if (e) {
waitFor.abort();
return void cb(e);
}
content = UI.dialog.customModal(c[0], {
buttons: button
});
}));
}).nThen(function () {
var tabs = UI.dialog.tabs([{
title: Messages.fc_prop,
icon: "fa fa-info-circle",
content: content
}]);
var modal = UI.openCustomModal(tabs);
cb (void 0, modal);
});
};
UIElements.getUserGrid = function (label, config, onSelect) {
var common = config.common;
@ -2452,14 +1602,9 @@ define([
.text(Messages.accessButton))
.click(common.prepareFeedback(type))
.click(function () {
common.isPadStored(function (err, data) {
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {}, function (e) {
if (e) { console.error(e); }
});
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
});
@ -2476,8 +1621,10 @@ define([
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
UIElements.getProperties(common, {}, function (e) {
if (e) { return void console.error(e); }
require(['/common/inner/properties.js'], function (Properties) {
Properties.getPropertiesModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
});
});
@ -3083,9 +2230,21 @@ define([
updateUsage();
}, LIMIT_REFRESH_RATE * 3);
Visible.onChange(function (state) {
if (!state) {
clearInterval(interval);
return;
}
interval = setInterval(function () {
updateUsage();
}, LIMIT_REFRESH_RATE * 3);
updateUsage();
});
updateUsage();
cb(null, $container);
return {
$container: $container,
stop: function () {
clearInterval(interval);
}

@ -879,7 +879,8 @@ define([
postMessage("LEAVE_PAD", data, cb);
};
pad.sendPadMsg = function (data, cb) {
postMessage("SEND_PAD_MSG", data, cb);
// -1 ==> no timeout, we may receive the callback only when we reconnect
postMessage("SEND_PAD_MSG", data, cb, { timeout: -1 });
};
pad.onReadyEvent = Util.mkEvent();
pad.onMessageEvent = Util.mkEvent();
@ -1970,7 +1971,9 @@ define([
common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]);
var _parsed1 = Hash.parsePadUrl(common.fromFileData.href);
var _parsed2 = Hash.parsePadUrl(window.location.href);
if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; }
if (_parsed1.hashData.type === 'pad') {
if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; }
}
delete sessionStorage[Constants.newPadFileData];
}

@ -22,7 +22,7 @@ define([
init: function () {}
};
require(['/code/mermaid.js', 'css!/code/mermaid.css'], function (_Mermaid) {
require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) {
Mermaid = _Mermaid;
});
@ -84,7 +84,7 @@ define([
var defaultCode = renderer.code;
renderer.code = function (code, language) {
if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) {
return '<pre class="mermaid">'+code+'</pre>';
return '<pre class="mermaid">'+Util.fixHTML(code)+'</pre>';
} else {
return defaultCode.apply(renderer, arguments);
}
@ -287,6 +287,23 @@ define([
return patch;
};
var removeMermaidClickables = function ($el) {
// find all links in the tree and do the following for each one
$el.find('a').each(function (index, a) {
var parent = a.parentElement;
if (!parent) { return; }
// iterate over the links' children and transform them into preceding children
// to preserve their visible ordering
slice(a.children).forEach(function (child) {
parent.insertBefore(child, a);
});
// remove the link once it has been emptied
$(a).remove();
});
// finally, find all 'clickable' items and remove the class
$el.find('.clickable').removeClass('clickable');
};
DiffMd.apply = function (newHtml, $content, common) {
var contextMenu = common.importMediaTagMenu();
var id = $content.attr('id');
@ -364,7 +381,7 @@ define([
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
var list_values = [].slice.call(mutation.target.children)
var list_values = slice(mutation.target.children)
.map(function (el) { return el.outerHTML; })
.join('');
mediaMap[mutation.target.getAttribute('src')] = list_values;
@ -400,7 +417,13 @@ define([
// check if you had cached a pre-rendered instance of the supplied source
if (typeof(cached) !== 'object') {
try {
Mermaid.init(undefined, $(el));
var $el = $(el);
Mermaid.init(undefined, $el);
// clickable elements in mermaid don't work well with our sandboxing setup
// the function below strips clickable elements but still leaves behind some artifacts
// tippy tooltips might still be useful, so they're not removed. It would be
// preferable to just support links, but this covers up a rough edge in the meantime
removeMermaidClickables($el);
} catch (e) { console.error(e); }
return;
}

@ -10,6 +10,7 @@ define([
'/common/common-feedback.js',
'/common/inner/access.js',
'/common/inner/properties.js',
'/bower_components/nthen/index.js',
'/common/hyperscript.js',
@ -27,6 +28,7 @@ define([
Constants,
Feedback,
Access,
Properties,
nThen,
h,
ProxyManager,
@ -536,10 +538,10 @@ define([
var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel();
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
// Initialization
Util.extend(APP, driveConfig.APP);
APP.$limit = driveConfig.$limit;
var proxy = driveConfig.proxy;
var folders = driveConfig.folders;
var files = proxy.drive;
@ -585,11 +587,6 @@ define([
// TOOLBAR
/* add a "change username" button */
if (!APP.readOnly) {
APP.$displayName.text(user.name || Messages.anonymous);
}
// DRIVE
var currentPath = APP.currentPath = LS.getLastOpenedFolder();
if (APP.newSharedFolder) {
@ -3873,7 +3870,7 @@ define([
var ro = folders[el] && folders[el].version >= 2;
if (!ro) { opts.noReadOnly = true; }
}
UIElements.getProperties(common, opts, cb);
Properties.getPropertiesModal(common, opts, cb);
};
APP.getAccess = function (el, cb) {
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {

@ -4,59 +4,22 @@ define([
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/inner/common-modal.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, h,
], function ($, Util, Hash, UI, UIElements, Modal, h,
Messages, nThen) {
var Access = {};
var evRedrawAll = Util.mkEvent();
// Override metadata values from data
var override = function (data, obj) {
data.owners = obj.owners;
data.expire = obj.expire;
data.pending_owners = obj.pending_owners;
data.mailbox = obj.mailbox;
data.restricted = obj.restricted;
data.allowed = obj.allowed;
data.rejected = obj.rejected;
};
var loadMetadata = function (common, data, waitFor, redraw) {
common.getPadMetadata({
channel: data.channel
}, waitFor(function (md) {
override(data, md);
if (redraw) { evRedrawAll.fire(redraw); }
}));
};
var isOwned = function (common, data) {
data = data || {};
var priv = common.getMetadataMgr().getPrivateData();
var edPublic = priv.edPublic;
var owned = false;
if (Array.isArray(data.owners) && data.owners.length) {
if (data.owners.indexOf(edPublic) !== -1) {
owned = true;
} else {
Object.keys(priv.teams || {}).some(function (id) {
var team = priv.teams[id] || {};
if (team.viewer) { return; }
if (data.owners.indexOf(team.edPublic) === -1) { return; }
owned = id;
return true;
});
}
}
return owned;
};
var getOwnersTab = function (common, data, opts, _cb) {
var getOwnersTab = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
var parsed = Hash.parsePadUrl(data.href || data.roHref);
var owned = Modal.isOwned(Env, data);
var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
if (disabled) { return void cb(); }
var friends = common.getFriends(true);
var sframeChan = common.getSframeChannel();
@ -340,10 +303,13 @@ define([
});
});
var called = false;
redrawAll = function (reload) {
if (called) { return; }
called = true;
nThen(function (waitFor) {
if (!reload) { return; }
loadMetadata(common, data, waitFor, "owner");
Modal.loadMetadata(common, data, waitFor, "owner");
}).nThen(function () {
owners = data.owners || [];
pending_owners = data.pending_owners || [];
@ -352,11 +318,12 @@ define([
$div1.append(h('p', Messages.owner_text));
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
called = false;
});
};
redrawAll();
evRedrawAll.reg(function (type) {
Env.evRedrawAll.reg(function (type) {
if (type === "owner") { return; }
setTimeout(function () {
redrawAll();
@ -372,8 +339,16 @@ define([
cb(void 0, link);
};
var getAllowTab = function (common, data, opts, _cb) {
var getAllowTab = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
var parsed = Hash.parsePadUrl(data.href || data.roHref);
var owned = Modal.isOwned(Env, data);
var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
var allowDisabled = parsed.type === 'drive';
if (disabled || allowDisabled) { return void cb(); }
opts = opts || {};
var friends = common.getFriends(true);
@ -636,10 +611,13 @@ define([
});
});
var called = false;
redrawAll = function (reload) {
if (called) { return; }
called = true;
nThen(function (waitFor) {
if (!reload) { return; }
loadMetadata(common, data, waitFor, "allow");
Modal.loadMetadata(common, data, waitFor, "allow");
}).nThen(function () {
owners = data.owners || [];
restricted = data.restricted || false;
@ -649,11 +627,12 @@ define([
$div1.append(drawRemove());
$div2.append(drawAdd());
setLock(!restricted);
called = false;
});
};
redrawAll();
evRedrawAll.reg(function (type) {
Env.evRedrawAll.reg(function (type) {
if (type === "allow") { return; }
setTimeout(function () {
redrawAll();
@ -730,8 +709,9 @@ define([
}, function () {});
};
var getAccessTab = function (common, data, opts, _cb) {
var getAccessTab = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
opts = opts || {};
var sframeChan = common.getSframeChannel();
@ -753,7 +733,7 @@ define([
var $d = $('<div>');
var owned = isOwned(common, data);
var owned = Modal.isOwned(Env, data);
if (!opts.noExpiration) {
var expire = Messages.creation_expireFalse;
@ -949,7 +929,7 @@ define([
// Mute access requests
var edPublic = priv.edPublic;
var owned = isOwned(common, data);
var owned = Modal.isOwned(Env, data);
var canMute = data.mailbox && owned === true && (
(typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
data.mailbox[edPublic]);
@ -1007,7 +987,7 @@ define([
};
redraw();
evRedrawAll.reg(function (ownersOrAllow) {
Env.evRedrawAll.reg(function (ownersOrAllow) {
setTimeout(function () {
redraw(ownersOrAllow);
});
@ -1016,136 +996,25 @@ define([
cb(void 0, $div);
};
var getAccessData = function (common, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
opts = opts || {};
var data = {};
nThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
common.getPadAttribute('', waitFor(function (err, val) {
if (err || !val) {
waitFor.abort();
return void cb(err || 'EEMPTY');
}
if (!val.fileType) {
delete val.owners;
delete val.expire;
}
Util.extend(data, val);
if (data.href) { data.href = base + data.href; }
if (data.roHref) { data.roHref = base + data.roHref; }
}), opts.href);
// If this is a file, don't try to look for metadata
if (opts.channel && opts.channel.length > 34) { return; }
common.getPadMetadata({
channel: opts.channel // optional, fallback to current pad
}, waitFor(function (obj) {
if (obj && obj.error) { console.error(obj.error); return; }
loadMetadata(common, data, waitFor);
}));
}).nThen(function () {
cb(void 0, data);
});
};
Access.getAccessModal = function (common, opts, cb) {
var data;
var tab1, tab2, tab3;
var disabled = false;
var allowDisabled = false;
var button = [{
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [13,27]
cb = cb || function () {};
opts = opts || {};
opts.wide = true;
opts.access = true;
var tabs = [{
getTab: getAccessTab,
title: Messages.access_main,
icon: "fa fa-unlock-alt",
}, {
getTab: getAllowTab,
title: Messages.access_allow,
icon: "fa fa-list",
}, {
getTab: getOwnersTab,
title: Messages.creation_owners,
icon: "fa fa-id-badge",
}];
nThen(function (waitFor) {
getAccessData(common, opts, waitFor(function (e, _data) {
if (e) {
waitFor.abort();
return void cb(e);
}
data = _data;
}));
}).nThen(function (waitFor) {
var owned = isOwned(common, data);
if (typeof(owned) !== "boolean") {
data.teamId = Number(owned);
}
var parsed = Hash.parsePadUrl(data.href || data.roHref);
disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
allowDisabled = parsed.type === 'drive';
getAccessTab(common, data, opts, waitFor(function (e, c) {
if (e) {
waitFor.abort();
return void cb(e);
}
tab1 = UI.dialog.customModal(c[0], {
buttons: button
});
}));
if (disabled) { return; }
if (!allowDisabled) {
getAllowTab(common, data, opts, waitFor(function (e, c) {
if (e) {
waitFor.abort();
return void cb(e);
}
tab2 = UI.dialog.customModal(c, {
buttons: button
});
}));
}
getOwnersTab(common, data, opts, waitFor(function (e, c) {
if (e) {
waitFor.abort();
return void cb(e);
}
tab3 = UI.dialog.customModal(c, {
buttons: button
});
}));
}).nThen(function () {
var tabs = UI.dialog.tabs([{
title: Messages.access_main,
icon: "fa fa-unlock-alt",
content: tab1
}, {
title: Messages.access_allow,
disabled: disabled || allowDisabled,
icon: "fa fa-list",
content: tab2
}, {
title: Messages.creation_owners,
disabled: disabled,
icon: "fa fa-id-badge",
content: tab3
}]);
var modal = UI.openCustomModal(tabs, {
wide: true
});
cb (void 0, modal);
var sframeChan = common.getSframeChannel();
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
if (!$(modal).length) {
return void handler.stop();
}
override(data, Util.clone(md));
evRedrawAll.fire();
});
var metadataMgr = common.getMetadataMgr();
var f = function () {
if (!$(modal).length) {
return void metadataMgr.off('change', f);
}
evRedrawAll.fire();
};
metadataMgr.onChange(f);
});
Modal.getModal(common, opts, tabs, cb);
};
return Access;

@ -0,0 +1,167 @@
define([
'jquery',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, Messages, nThen) {
var Modal = {};
Modal.override = function (data, obj) {
data.owners = obj.owners;
data.expire = obj.expire;
data.pending_owners = obj.pending_owners;
data.mailbox = obj.mailbox;
data.restricted = obj.restricted;
data.allowed = obj.allowed;
data.rejected = obj.rejected;
};
Modal.loadMetadata = function (Env, data, waitFor, redraw) {
Env.common.getPadMetadata({
channel: data.channel
}, waitFor(function (md) {
if (md && md.error) { return void console.error(md.error); }
Modal.override(data, md);
if (redraw) { Env.evRedrawAll.fire(redraw); }
}));
};
Modal.getPadData = function (Env, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
opts = opts || {};
var data = {};
nThen(function (waitFor) {
var priv = common.getMetadataMgr().getPrivateData();
var base = priv.origin;
common.getPadAttribute('', waitFor(function (err, val) {
if (err || !val) {
if (opts.access) {
data.password = priv.password;
// Access modal and the pad is not stored: we're not an owner
// so we don't need the correct href, just the type
var h = Hash.createRandomHash(priv.app, priv.password);
data.href = base + priv.pathname + '#' + h;
} else {
waitFor.abort();
return void cb(err || 'EEMPTY');
}
return;
}
if (!val.fileType) {
delete val.owners;
delete val.expire;
}
Util.extend(data, val);
if (data.href) { data.href = base + data.href; }
if (data.roHref) { data.roHref = base + data.roHref; }
}), opts.href);
// If this is a file, don't try to look for metadata
if (opts.channel && opts.channel.length > 34) { return; }
if (opts.channel) { data.channel = opts.channel; }
Modal.loadMetadata(Env, data, waitFor);
}).nThen(function () {
cb(void 0, data);
});
};
Modal.isOwned = function (Env, data) {
var common = Env.common;
data = data || {};
var priv = common.getMetadataMgr().getPrivateData();
var edPublic = priv.edPublic;
var owned = false;
if (Array.isArray(data.owners) && data.owners.length) {
if (data.owners.indexOf(edPublic) !== -1) {
owned = true;
} else {
Object.keys(priv.teams || {}).some(function (id) {
var team = priv.teams[id] || {};
if (team.viewer) { return; }
if (data.owners.indexOf(team.edPublic) === -1) { return; }
owned = Number(id);
return true;
});
}
}
return owned;
};
var blocked = false;
Modal.getModal = function (common, opts, _tabs, cb) {
if (blocked) { return; }
blocked = true;
var Env = {
common: common,
evRedrawAll: Util.mkEvent()
};
var data;
var button = [{
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [13,27]
}];
var tabs = [];
nThen(function (waitFor) {
Modal.getPadData(Env, opts, waitFor(function (e, _data) {
if (e) {
blocked = false;
waitFor.abort();
return void cb(e);
}
data = _data;
}));
}).nThen(function (waitFor) {
var owned = Modal.isOwned(Env, data);
if (typeof(owned) !== "boolean") {
data.teamId = Number(owned);
}
_tabs.forEach(function (obj, i) {
obj.getTab(Env, data, opts, waitFor(function (e, c) {
if (e) {
blocked = false;
waitFor.abort();
return void cb(e);
}
var node = (c instanceof $) ? c[0] : c;
tabs[i] = {
content: c && UI.dialog.customModal(node, {
buttons: obj.buttons || button,
onClose: function () { blocked = false; }
}),
disabled: !c,
title: obj.title,
icon: obj.icon
};
}));
});
}).nThen(function () {
var tabsContent = UI.dialog.tabs(tabs);
var modal = UI.openCustomModal(tabsContent, {
wide: opts.wide
});
cb (void 0, modal);
var sframeChan = common.getSframeChannel();
var handler = sframeChan.on('EV_RT_METADATA', function (md) {
if (!$(modal).length) {
return void handler.stop();
}
Modal.override(data, Util.clone(md));
Env.evRedrawAll.fire();
});
var metadataMgr = common.getMetadataMgr();
var f = function () {
if (!$(modal).length) {
return void metadataMgr.off('change', f);
}
Env.evRedrawAll.fire();
};
metadataMgr.onChange(f);
});
};
return Modal;
});

@ -0,0 +1,181 @@
define([
'jquery',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/inner/common-modal.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, Modal, h,
Messages, nThen) {
var Properties = {};
var getPadProperties = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
opts = opts || {};
var $d = $('<div>');
if (!data) { return void cb(void 0, $d); }
if (data.href) {
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
$d.append(UI.dialog.selectable(data.href, {
id: 'cp-app-prop-link',
}));
}
if (data.roHref && !opts.noReadOnly) {
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
$d.append(UI.dialog.selectable(data.roHref, {
id: 'cp-app-prop-rolink',
}));
}
if (data.tags && Array.isArray(data.tags)) {
$d.append(h('div.cp-app-prop', [Messages.fm_prop_tagsList, h('br'), h('span.cp-app-prop-content', data.tags.join(', '))]));
}
if (data.ctime) {
$d.append(h('div.cp-app-prop', [Messages.fm_creation, h('br'), h('span.cp-app-prop-content', new Date(data.ctime).toLocaleString())]));
}
if (data.atime) {
$d.append(h('div.cp-app-prop', [Messages.fm_lastAccess, h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
}
if (!common.isLoggedIn()) { return void cb(void 0, $d); }
// File and history size...
var owned = Modal.isOwned(Env, data);
// check the size of this file, including additional channels
var bytes = 0;
var historyBytes;
var chan = [data.channel];
if (data.rtChannel) { chan.push(data.rtChannel); }
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
// Get the channels with history (no blobs)
var channels = chan.filter(function (c) { return c.length === 32; }).map(function (id) {
if (id === data.rtChannel && data.lastVersion && data.lastCpHash) {
return {
channel: id,
lastKnownHash: data.lastCpHash
};
}
return {
channel: id
};
});
var history = common.makeUniversal('history');
var trimChannels = [];
nThen(function (waitFor) {
// Get total size
chan.forEach(function (c) {
common.getFileSize(c, waitFor(function (e, _bytes) {
if (e) {
// there was a problem with the RPC
console.error(e);
}
bytes += _bytes;
}));
});
if (!owned) { return; }
// Get history size
history.execCommand('GET_HISTORY_SIZE', {
pad: true,
channels: channels,
teamId: typeof(owned) === "number" && owned
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
historyBytes = obj.size;
trimChannels = obj.channels;
}));
}).nThen(function () {
if (bytes === 0) { return void cb(void 0, $d); }
var formatted = UIElements.prettySize(bytes);
if (!owned || !historyBytes || historyBytes > bytes || historyBytes < 0) {
$d.append(h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('span.cp-app-prop-content', formatted)
]));
return void cb(void 0, $d);
}
var p = Math.round((historyBytes / bytes) * 100);
var historyPrettySize = UIElements.prettySize(historyBytes);
var contentsPrettySize = UIElements.prettySize(bytes - historyBytes);
var button;
var spinner = UI.makeSpinner();
var size = h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('div.cp-app-prop-size-container', [
h('div.cp-app-prop-size-history', { style: 'width:'+p+'%;' })
]),
h('div.cp-app-prop-size-legend', [
h('div.cp-app-prop-history-size', [
h('span.cp-app-prop-history-size-color'),
h('span.cp-app-prop-content', Messages._getKey('historyTrim_historySize', [historyPrettySize]))
]),
h('div.cp-app-prop-contents-size', [
h('span.cp-app-prop-contents-size-color'),
h('span.cp-app-prop-content', Messages._getKey('historyTrim_contentsSize', [contentsPrettySize]))
]),
]),
button = h('button.btn.btn-danger-alt.no-margin', Messages.trimHistory_button),
spinner.spinner
]);
$d.append(size);
var $button = $(button);
UI.confirmButton(button, {
classes: 'btn-danger'
}, function () {
$button.remove();
spinner.spin();
history.execCommand('TRIM_HISTORY', {
pad: true,
channels: trimChannels,
teamId: typeof(owned) === "number" && owned
}, function (obj) {
spinner.hide();
if (obj && obj.error) {
$(size).append(h('div.alert.alert-danger', Messages.trimHistory_error));
return;
}
$(size).remove();
var formatted = UIElements.prettySize(bytes - historyBytes);
$d.append(h('div.cp-app-prop', [
Messages.upload_size,
h('br'),
h('span.cp-app-prop-content', formatted)
]));
$d.append(h('div.alert.alert-success', Messages.trimHistory_success));
});
});
cb(void 0, $d);
});
};
Properties.getPropertiesModal = function (common, opts, cb) {
cb = cb || function () {};
opts = opts || {};
var tabs = [{
getTab: getPadProperties,
title: Messages.fc_prop,
icon: "fa fa-info-circle",
}];
Modal.getModal(common, opts, tabs, cb);
};
return Properties;
});

@ -1499,6 +1499,13 @@ define([
return;
}
var onError = function (err) {
channel.bcast("PAD_ERROR", err);
// If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
Store.leavePad(null, data, function () {});
};
var conf = {
onReady: function (pad) {
var padData = pad.metadata || {};
@ -1522,14 +1529,8 @@ define([
onLeave: function (m) {
channel.bcast("PAD_LEAVE", m);
},
onError: function (err) {
channel.bcast("PAD_ERROR", err);
Store.leavePad(null, data, function () {});
},
onChannelError: function (err) {
channel.bcast("PAD_ERROR", err);
Store.leavePad(null, data, function () {});
},
onError: onError,
onChannelError: onError,
onRejected: function (allowed, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
@ -1595,7 +1596,10 @@ define([
onConnect: function (wc, sendMessage) {
channel.sendMessage = function (msg, cId, cb) {
// Send to server
sendMessage(msg, function () {
sendMessage(msg, function (err) {
if (err) {
return void cb({ error: err });
}
// Broadcast to other tabs
channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
channel.bcast("PAD_MESSAGE", {

@ -45,10 +45,13 @@ define([
var txid = mkTxid();
opts = opts || {};
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
cb('TIMEOUT');
}, to);
var timeout;
if (to > 0) {
timeout = setTimeout(function () {
delete queries[txid];
cb('TIMEOUT');
}, to);
}
acks[txid] = function (err) {
clearTimeout(timeout);
delete acks[txid];

@ -10,6 +10,7 @@ define([
json: '/bower_components/requirejs-plugins/src/json',
// jquery declares itself as literally "jquery" so it cannot be pulled by path :(
"jquery": "/bower_components/jquery/dist/jquery.min",
"mermaid": "/code/mermaid.min",
// json.sortify same
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
//"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",

@ -325,6 +325,11 @@ define([
UI.updateLoadingProgress({ state: -1 }, false);
if (toolbar) {
// Check if we have a new chainpad instance
toolbar.resetChainpad(cpNfInner.chainpad);
}
var newPad = false;
if (newContentStr === '') { newPad = true; }
@ -364,6 +369,9 @@ define([
}).nThen(function () {
stateChange(STATE.READY);
firstConnection = false;
oldContent = undefined;
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);

@ -49,24 +49,31 @@ define([
config = undefined;
var evPatchSent = Util.mkEvent();
var chainpad;
var chainpad = ChainPad.create({
userName: userName,
initialState: initialState,
patchTransformer: patchTransformer,
validateContent: validateContent,
avgSyncMilliseconds: avgSyncMilliseconds,
logLevel: logLevel
});
chainpad.onMessage(function(message, cb) {
sframeChan.query('Q_RT_MESSAGE', message, function (err) {
if (!err) { evPatchSent.fire(); }
cb(err);
var makeChainPad = function () {
var _chainpad = ChainPad.create({
userName: userName,
initialState: initialState,
patchTransformer: patchTransformer,
validateContent: validateContent,
avgSyncMilliseconds: avgSyncMilliseconds,
logLevel: logLevel
});
});
chainpad.onPatch(function () {
onRemote({ realtime: chainpad });
});
_chainpad.onMessage(function(message, cb) {
// -1 ==> no timeout, we may receive the callback only when we reconnect
sframeChan.query('Q_RT_MESSAGE', message, function (_err, obj) {
var err = _err || (obj && obj.error);
if (!err) { evPatchSent.fire(); }
cb(err);
}, { timeout: -1 });
});
_chainpad.onPatch(function () {
onRemote({ realtime: chainpad });
});
return _chainpad;
};
chainpad = makeChainPad();
var myID;
var isReady = false;
@ -96,15 +103,25 @@ define([
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;
chainpad.abort();
if (err.type === 'EUNKNOWN') {
// Recoverable error: make a new chainpad
chainpad = makeChainPad();
return;
}
onError(err);
});
sframeChan.on('EV_RT_CONNECT', function (content) {
//content.members.forEach(userList.onJoin);
if (isReady && myID === content.myID) {
// We are connected and we are "reconnecting" ==> we probably had to rejoin
// the channel because of a server error (enoent), don't update the toolbar
return;
}
isReady = false;
if (myID) {
// it's a reconnect
myID = content.myID;
chainpad.start();
//chainpad.start();
onConnectionChange({ state: true, myId: myID });
return;
}
@ -149,15 +166,18 @@ define([
});
};
return Object.freeze({
var cpNfInner = {
getMyID: function () { return myID; },
metadataMgr: metadataMgr,
whenRealtimeSyncs: whenRealtimeSyncs,
onInfiniteSpinner: evInfiniteSpinner.reg,
onPatchSent: evPatchSent.reg,
offPatchSent: evPatchSent.unreg,
chainpad: chainpad,
};
cpNfInner.__defineGetter__("chainpad", function () {
return chainpad;
});
return Object.freeze(cpNfInner);
};
return Object.freeze(module.exports);
});

@ -45,7 +45,8 @@ define([], function () {
return decryptedMsg;
} catch (err) {
console.error(err);
return msg;
console.warn(peer, msg);
return false;
}
};
@ -81,6 +82,7 @@ define([], function () {
validateKey = msgObj.validateKey;
}
var message = msgIn(msgObj.user, msgObj.msg);
if (!message) { return; }
verbose(message);

@ -1331,6 +1331,15 @@ MessengerUI, Messages) {
showColors = true;
};
// If we had to create a new chainpad instance, reset the one used in the toolbar
toolbar.resetChainpad = function (chainpad) {
if (config.realtime !== chainpad) {
config.realtime = chainpad;
config.realtime.onPatch(ks(toolbar, config));
config.realtime.onMessage(ks(toolbar, config, true));
}
};
// On log out, remove permanently the realtime elements of the toolbar
Common.onLogout(function () {
failed();

@ -1306,7 +1306,7 @@
"team_cat_general": "Info",
"properties_passwordWarning": "La password è stata cambiata ma non siamo in grado di aggiornare il tuo CryptDrive con i nuovi dati. Devi rimuovere i vecchi pad manualmente.<br>Premi OK per ricaricare ed aggiornare i tuoi diritti di accesso.",
"share_embedCategory": "Incorporamento",
"chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra tab e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.",
"chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra scheda e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.",
"crowdfunding_popup_text": "<h3>Abbiamo bisogno del tuo aiuto!</h3>Per garantire che CryptPad sia attivamente sviluppato, valuta se aiutare il progetto sulla <a href=\"https://opencollective.com/cryptpad\">pagina OpenCollective</a>, dove potrai vedere la nostra <b>Roadmap</b> e gli <b>obiettivi di finanziamento</b>.",
"admin_updateLimitHint": "Forzare un aggiornamento dei limiti dello spazio utente può essere fatto in qualsiasi momento, ma è necessario solo in caso di errori",
"admin_flushCacheTitle": "Svuota la cache HTTP",
@ -1316,7 +1316,7 @@
"fm_info_sharedFolderHistory": "questa è solo la cronologia delle tue cartelle condivise: <b>{0}</b><br/>Il tuo CryptDrive rimarrà in sola lettura durante la navigazione.",
"share_description": "Scegli cosa vuoi condividere e prendi il link o invialo direttamente ai tuoi contatti CryptPad.",
"admin_supportInitHelp": "Il tuo server non è ancora configurato per avere una mailbox di supporto. Se vuoi una mailbox di supporto per ricevere messaggi dai tuoi utenti, chiedi all'amministratore del server di avviare lo script posizionato in \"./scripts/generate-admin-keys.js\", quindi salvare la chiave pubblica nel file \"config.js\" ed inviarti la chiave privata.",
"admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa la form che segue per aggiungere o aggiornare la chiave privata del tuo account.",
"admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa il modulo che segue per aggiungere o aggiornare la chiave privata del tuo account.",
"admin_supportInitHint": "Puoi configurare una mailbox di supporto per dare agli utenti del tuo CryptPad un modo per contattarti in maniera sicura se hanno problemi con i loro account.",
"admin_supportListHint": "Questa è la lista dei ticket inviati dagli utenti alla mailbox di supporto. Tutti gli amministratori possono vedere i messaggi e le risposte. Un ticket chiuso non può essere riaperto. Puoi solo rimuovere (nascondere) i ticket chiusi, ma i ticket rimossi rimangono visibili agli altri amministratori.",
"requestEdit_confirm": "{1} ha richiesto la possibilità di modificare il pad <b>{0}</b>. Vuoi fornirgli l'accesso?",

@ -177,6 +177,7 @@ define([
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
APP.disableSF = !privateData.enableSF && AppConfig.disableSharedFolders;
if (APP.newSharedFolder && !APP.loggedIn) {
@ -204,13 +205,19 @@ define([
var $rightside = toolbar.$rightside;
$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
var $displayName = APP.$bar.find('.' + Toolbar.constants.username);
metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous;
$displayName.text(name);
});
$displayName.text(user.name || Messages.anonymous);
/* add the usage */
var usageBar;
if (APP.loggedIn) {
common.createUsageBar(null, function (err, $limitContainer) {
usageBar = common.createUsageBar(null, function (err) {
if (err) { return void DriveUI.logError(err); }
APP.$limit = $limitContainer;
}, true);
}
@ -256,16 +263,12 @@ define([
.addClass('fa-ban');
}
metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous;
APP.$displayName.text(name);
});
$('body').css('display', '');
if (!proxy.drive || typeof(proxy.drive) !== 'object') {
throw new Error("Corrupted drive");
}
var drive = DriveUI.create(common, {
$limit: usageBar && usageBar.$container,
proxy: proxy,
folders: folders,
updateObject: updateObject,

@ -135,8 +135,12 @@ define([
var PROPERTIES = ['title', 'body', 'tags', 'color'];
var BOARD_PROPERTIES = ['title', 'color'];
var createEditModal = function (framework, kanban) {
if (framework.isReadOnly()) { return; }
if (editModal) { return editModal; }
var dataObject = {};
var isBoard, id;
var offline = false;
var update = Util.throttle(function () {
kanban.setBoards(kanban.options.boards);
@ -147,7 +151,7 @@ define([
framework.localChange();
update();
};
if (editModal) { return editModal; }
var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
var content = h('div', [
conflictContainer = h('div#cp-kanban-edit-conflicts', [
@ -167,7 +171,6 @@ define([
]);
var $tags = $(tagsDiv);
var $conflict = $(conflicts);
var $cc = $(conflictContainer);
var conflict = {
@ -282,6 +285,7 @@ define([
});
var commitTags = function () {
if (offline) { return; }
setTimeout(function () {
dataObject.tags = Util.deduplicateString(_field.getTokens().map(function (t) {
return t.toLowerCase();
@ -305,6 +309,7 @@ define([
var $color = $(h('span.cp-kanban-palette.fa'));
$color.addClass('cp-kanban-palette-'+(color || 'nocolor'));
$color.click(function () {
if (offline) { return; }
if (color === selectedColor) { return; }
selectedColor = color;
$colors.find('.cp-kanban-palette').removeClass('fa-check');
@ -363,6 +368,17 @@ define([
buttons: button
});
modal.classList.add('cp-kanban-edit-modal');
var $modal = $(modal);
framework.onEditableChange(function (unlocked) {
editor.setOption('readOnly', !unlocked);
$title.prop('disabled', unlocked ? '' : 'disabled');
$(_field.element).tokenfield(unlocked ? 'enable' : 'disable');
$modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled');
offline = !unlocked;
});
var setId = function (_isBoard, _id) {
// Reset the mdoal with a new id
@ -386,7 +402,7 @@ define([
.show();
}
// Also reset the buttons
$(modal).find('nav').after(UI.dialog.getButtons(button)).remove();
$modal.find('nav').after(UI.dialog.getButtons(button)).remove();
};
onRemoteChange.reg(function () {
@ -975,6 +991,7 @@ define([
}
kanban.options.readOnly = true;
$container.addClass('cp-app-readonly');
$container.find('.kanban-edit-item').remove();
});
var getCursor = function () {

@ -794,6 +794,11 @@ define([
var userDoc = JSON.stringify(proxy);
if (userDoc === "" || userDoc === "{}") { isNew = true; }
if (APP.toolbar && APP.rt.cpCnInner) {
// Check if we have a new chainpad instance
APP.toolbar.resetChainpad(APP.rt.cpCnInner.chainpad);
}
if (!isNew) {
if (proxy.info) {
// Migration

@ -295,22 +295,28 @@ define([
if (!proxy.drive || typeof(proxy.drive) !== 'object') {
throw new Error("Corrupted drive");
}
if (APP.usageBar) { APP.usageBar.stop(); }
APP.usageBar = common.createUsageBar(APP.team, function (err, $limitContainer) {
if (err) { return void DriveUI.logError(err); }
driveAPP.$limit = $limitContainer;
$limitContainer.attr('title', Messages.team_quota);
}, true);
driveAPP.team = id;
// Provide secondaryKey
var teamData = (privateData.teams || {})[id] || {};
driveAPP.readOnly = !teamData.hasSecondaryKey;
if (APP.usageBar) { APP.usageBar.stop(); }
APP.usageBar = undefined;
if (!driveAPP.readOnly) {
APP.usageBar = common.createUsageBar(APP.team, function (err, $limitContainer) {
if (err) { return void DriveUI.logError(err); }
$limitContainer.attr('title', Messages.team_quota);
}, true);
}
var drive = DriveUI.create(common, {
proxy: proxy,
folders: folders,
updateObject: updateObject,
updateSharedFolders: updateSharedFolders,
$limit: APP.usageBar && APP.usageBar.$container,
APP: driveAPP,
edPublic: APP.teamEdPublic,
editKey: teamData.secondaryKey
@ -1281,6 +1287,7 @@ define([
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
readOnly = driveAPP.readOnly = metadataMgr.getPrivateData().readOnly;
@ -1305,11 +1312,12 @@ define([
var toolbar = Toolbar.create(configTb);
toolbar.$rightside.hide(); // hide the bottom part of the toolbar
// Update the name in the user menu
driveAPP.$displayName = $bar.find('.' + Toolbar.constants.username);
var $displayName = $bar.find('.' + Toolbar.constants.username);
metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous;
driveAPP.$displayName.text(name);
$displayName.text(name);
});
$displayName.text(user.name || Messages.anonymous);
// Load the Team module
var onEvent = function (obj) {

@ -1,5 +1,6 @@
@import (reference) '../../customize/src/less2/include/tools.less';
@import (reference) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/buttons.less";
&.cp-app-whiteboard {
@ -36,6 +37,7 @@
flex: 1;
display: flex;
overflow: auto;
flex-flow: column;
}
// created in the html
@ -74,6 +76,8 @@
padding: 10px;
.buttons_main();
& > * + * {
margin: 0;
margin-left: 1em;
@ -82,10 +86,27 @@
#cp-app-whiteboard-width, #cp-app-whiteboard-opacity {
.middle;
}
#cp-app-whiteboard-clear, #cp-app-whiteboard-delete, #cp-app-whiteboard-toggledraw {
#cp-app-whiteboard-clear {
display: inline;
vertical-align: middle;
}
#cp-app-whiteboard-delete {
min-width: 40px;
}
.cp-whiteboard-type {
button {
min-width: 40px;
text-align: center;
&:not(:first-child) {
margin-left: -1px;
}
&.btn-primary:hover {
cursor: default;
}
}
}
.cp-app-whiteboard-selected {
display: flex;
align-items: center;
@ -96,27 +117,34 @@
height: 100px;
}
.cp-app-whiteboard-range-group {
.cp-whiteboard-brush {
display: flex;
flex-direction: column;
position: relative;
input[type="range"] {
background-color: inherit;
}
& > span {
cursor: default;
position: absolute;
top: 0;
right: 0;
}
}
.cp-app-whiteboard-range-group:first-of-type {
margin-left: 2em;
}
.cp-app-whiteboard-range-group:last-of-type {
margin-right: 1em;
.cp-app-whiteboard-range-group {
display: flex;
align-items: center;
position: relative;
label {
margin-bottom: 0;
margin-right: 5px;
flex: 1;
}
input[type="range"] {
background-color: inherit;
margin-right: 5px;
width: 150px;
padding: 0;
}
& > span {
width: 50px;
cursor: default;
}
}
}
}
@ -126,22 +154,22 @@
z-index: 100;
background: white;
display: flex;
justify-content: space-between;
padding: 10px;
flex-wrap: wrap;
max-width: 320px;
span.cp-app-whiteboard-palette-color {
height: 4vw;
width: 4vw;
height: 30px;
width: 30px;
display: block;
margin: 5px;
border: 1px solid black;
border: 1px solid #bbb;
vertical-align: top;
border-radius: 50%;
cursor: pointer;
transition: transform 0.1s;
&:hover {
transform: scale(1.2);
transform: scale(1.1);
}
}
}

@ -50,7 +50,9 @@ define([
var $widthLabel = $('label[for="cp-app-whiteboard-width"]');
var $opacity = $('#cp-app-whiteboard-opacity');
var $opacityLabel = $('label[for="cp-app-whiteboard-opacity"]');
var $toggle = $('#cp-app-whiteboard-toggledraw');
var $type = $('.cp-whiteboard-type');
var $brush = $('.cp-whiteboard-type .brush');
var $move = $('.cp-whiteboard-type .move');
var $deleteButton = $('#cp-app-whiteboard-delete');
var metadataMgr = framework._.cpNfInner.metadataMgr;
@ -97,7 +99,7 @@ define([
var updateBrushWidth = function () {
var val = $width.val();
canvas.freeDrawingBrush.width = Number(val);
$widthLabel.text(Messages._getKey("canvas_widthLabel", [val]));
$widthLabel.text(Messages._getKey("canvas_widthLabel", ['']));
$('#cp-app-whiteboard-width-val').text(val + 'px');
createCursor();
};
@ -108,7 +110,7 @@ define([
var val = $opacity.val();
brush.opacity = Number(val);
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
$opacityLabel.text(Messages._getKey("canvas_opacityLabel", [val]));
$opacityLabel.text(Messages._getKey("canvas_opacityLabel", ['']));
$('#cp-app-whiteboard-opacity-val').text((Number(val) * 100) + '%');
createCursor();
};
@ -116,17 +118,27 @@ define([
$opacity.on('change', updateBrushOpacity);
APP.draw = true;
var toggleDrawMode = function () {
$brush.click(function () {
if (APP.draw) { return; }
canvas.deactivateAll().renderAll();
APP.draw = !APP.draw;
APP.draw = true;
canvas.isDrawingMode = APP.draw;
$toggle.text(APP.draw ? Messages.canvas_disable : Messages.canvas_enable);
if (APP.draw) { $deleteButton.hide(); }
else { $deleteButton.show(); }
};
$toggle.click(toggleDrawMode);
$type.find('button').removeClass('btn-primary');
$brush.addClass('btn-primary');
$deleteButton.prop('disabled', 'disabled');
});
$move.click(function () {
if (!APP.draw) { return; }
canvas.deactivateAll().renderAll();
APP.draw = false;
canvas.isDrawingMode = APP.draw;
$type.find('button').removeClass('btn-primary');
$move.addClass('btn-primary');
$deleteButton.prop('disabled', '');
});
var deleteSelection = function () {
if (APP.draw) { return; }
if (canvas.getActiveObject()) {
canvas.getActiveObject().remove();
}
@ -211,6 +223,7 @@ define([
if (first || Sortify(palette) !== Sortify(newPalette)) {
palette = newPalette;
$colors.html('<div class="hidden">&nbsp;</div>');
$colors.css('width', (palette.length * 20)+'px');
palette.forEach(addColorToPalette);
first = false;
}
@ -494,6 +507,7 @@ define([
framework.start();
};
var initialContent = function () {
return [
h('div#cp-toolbar.cp-toolbar-container'),
@ -509,42 +523,56 @@ define([
}
}, [
h('button#cp-app-whiteboard-clear.btn.btn-danger', Messages.canvas_clear), ' ',
h('div.cp-whiteboard-type', [
h('button.brush.fa.fa-paint-brush.btn-primary', {title: Messages.canvas_brush}),
h('button.move.fa.fa-arrows', {title: Messages.canvas_select}),
]),
h('button.fa.fa-trash#cp-app-whiteboard-delete', {
disabled: 'disabled',
title: Messages.canvas_delete
}),
/*
h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Messages.canvas_disable),
h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Messages.canvas_disable),
h('button#cp-app-whiteboard-delete.btn.btn-secondary', {
style: {
display: 'none',
}
}, Messages.canvas_delete),
h('div.cp-app-whiteboard-range-group', [
h('label', {
'for': 'cp-app-whiteboard-width'
}, Messages.canvas_width),
h('input#cp-app-whiteboard-width', {
type: 'range',
min: "1",
max: "100"
}),
h('span#cp-app-whiteboard-width-val', '5px')
}, Messages.canvas_delete),*/
h('div.cp-whiteboard-brush', [
h('div.cp-app-whiteboard-range-group', [
h('label', {
'for': 'cp-app-whiteboard-width'
}, Messages.canvas_width),
h('input#cp-app-whiteboard-width', {
type: 'range',
value: "20",
min: "1",
max: "100"
}),
h('span#cp-app-whiteboard-width-val', '5px')
]),
h('div.cp-app-whiteboard-range-group', [
h('label', {
'for': 'cp-app-whiteboard-opacity',
}, Messages.canvas_opacity),
h('input#cp-app-whiteboard-opacity', {
type: 'range',
value: "1",
min: "0.1",
max: "1",
step: "0.1"
}),
h('span#cp-app-whiteboard-opacity-val', '100%')
]),
]),
h('div.cp-app-whiteboard-range-group', [
h('label', {
'for': 'cp-app-whiteboard-opacity',
}, Messages.canvas_opacity),
h('input#cp-app-whiteboard-opacity', {
type: 'range',
min: "0.1",
max: "1",
step: "0.1"
}),
h('span#cp-app-whiteboard-opacity-val', '100%')
]),
h('span.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [
h('div.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [
h('img', {
title: Messages.canvas_currentBrush
})
])
]),
UI.setHTML(h('div#cp-app-whiteboard-colors'), '&nbsp;'),
]),
UI.setHTML(h('div#cp-app-whiteboard-colors'), '&nbsp;'),
h('div#cp-app-whiteboard-cursors', {
style: {
display: 'none',

Loading…
Cancel
Save