cryptpad/www/common/inner/access.js

1249 lines
51 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
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',
'/components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, Modal, h,
Messages, nThen) {
var Access = {};
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();
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
var owners = data.owners || [];
var pending_owners = data.pending_owners || [];
var teamOwner = data.teamId;
var title = opts.title;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
opts = opts || {};
var redrawAll = function () {};
var addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);
var div1 = h('div.cp-share-column.cp-ownership');
var divMid = h('div.cp-share-column-mid', addBtn);
var div2 = h('div.cp-share-column.cp-ownership');
var $div1 = $(div1);
var $div2 = $(div2);
// Remove owner column
var drawRemove = function (pending) {
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
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(priv.teams).some(function (id) {
if (priv.teams[id].edPublic === ed) {
f = priv.teams[id];
f.teamId = id;
}
});
if (ed === priv.edPublic) {
f = f || user;
if (f.name) { f.edPublic = priv.edPublic; }
}
_owners[ed] = f ? Util.clone(f) : {
displayName: Messages._getKey('owner_unknownUser', [ed]),
edPublic: ed,
};
});
var remove = function (el) {
// Check selection
var me = false;
var $el = $(el);
var ed = $el.attr('data-ed');
if (!ed) { return; }
if (teamOwner && priv.teams[teamOwner] && priv.teams[teamOwner].edPublic === ed) {
me = true;
}
if (ed === priv.edPublic && !teamOwner) { me = true; }
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: data.channel || priv.channel,
channels: otherChan,
command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
value: [ed],
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll(true);
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
UI.log(Messages.saved);
}));
}).nThen(function (waitFor) {
var curve = $el.attr('data-curve');
if (curve === user.curvePublic) { return; }
var friend = friends[curve];
if (!friend) { return; }
common.mailbox.sendTo("RM_OWNER", {
channel: data.channel || priv.channel,
title: data.title || title,
pending: pending
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, waitFor());
}).nThen(function () {
redrawAll(true);
});
};
if (pending && !Object.keys(_owners).length) {
return $();
}
var msg = pending ? Messages.owner_removePendingText
: Messages.owner_removeText;
var removeCol = UIElements.getUserGrid(msg, {
common: common,
large: true,
data: _owners,
noSelect: true,
list: true,
remove: remove
}, function () {
});
var $div = $(removeCol.div);
return $div;
};
// Add owners column
var drawAdd = function () {
var priv = metadataMgr.getPrivateData();
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
var $div = $(h('div.cp-share-column'));
var _friends = Util.clone(friends);
var friendKeys = Object.keys(_friends);
friendKeys.forEach(function (curve) {
if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
pending_owners.indexOf(_friends[curve].edPublic) !== -1 ||
!_friends[curve].notifications) {
delete _friends[curve];
}
});
if (!Object.keys(_friends).length) {
var friendText;
if (!friendKeys.length) {
//console.error(UIElements.noContactsMessage(common));
var findContacts = UIElements.noContactsMessage(common);
friendText = h('span.cp-app-prop-content',
findContacts.content
);
} else {
friendText = h('span.cp-app-prop-content', Messages.access_noContact);
}
$div.append(h('div.cp-app-prop', [
Messages.contacts,
h('br'),
friendText
]));
} else {
var addCol = UIElements.getUserGrid(Messages.contacts, {
common: common,
large: true,
data: _friends
}, function () {
//console.log(arguments);
});
$div.append(addCol.div);
}
var _teamsData = Util.clone(teamsData);
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];
}
});
if (!Object.keys(_teamsData).length) { return $div; }
var teamsList = UIElements.getUserGrid(Messages.teams, {
common: common,
large: true,
noFilter: true,
data: _teamsData
}, function () {});
$div.append(teamsList.div);
return $div;
};
$(addBtn).click(function () {
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
var $div = $div2.find('.cp-share-column');
// Check selection
var $sel = $div.find('.cp-usergrid-user.cp-selected');
var sel = $sel.toArray();
if (!sel.length) { return; }
var addMe = false;
var toAdd = sel.map(function (el) {
var curve = $(el).attr('data-curve');
// If the pad is owned by a team, we can transfer ownership to ourselves
if (curve === user.curvePublic && teamOwner) {
addMe = true;
return;
}
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: data.channel || priv.channel,
channels: otherChan,
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(true);
var text = err === "INSUFFICIENT_PERMISSIONS" ?
Messages.fm_forbidden : Messages.error;
return void UI.warn(text);
}
var isTemplate = priv.isTemplate || opts.isTemplate;
// never store calendars in the team drive
if (opts.calendar) { return; }
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 || 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: data.channel || priv.channel,
channels: otherChan,
command: 'ADD_PENDING_OWNERS',
value: toAdd,
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll(true);
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
}));
}
}).nThen(function (waitFor) {
// Offer ownership to a friend
if (addMe) {
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: data.channel || priv.channel,
channels: otherChan,
command: 'ADD_OWNERS',
value: [priv.edPublic],
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
if (err) {
waitFor.abort();
redrawAll(true);
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
if (err || (res && res.error)) {
return void console.error(err || res.error);
}
UI.log(Messages.saved);
});
}));
}
}).nThen(function (waitFor) {
var href = data.href;
sel.forEach(function (el) {
var curve = $(el).attr('data-curve');
if (curve === user.curvePublic) { return; }
var friend = friends[curve];
if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", {
channel: data.channel || priv.channel,
channels: otherChan,
href: href,
calendar: opts.calendar,
password: data.password || priv.password,
title: data.title || title
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, waitFor());
});
}).nThen(function () {
redrawAll(true);
UI.log(Messages.saved);
});
});
$(addBtn).on('keydown', function () {
if (event.keyCode === 13) {
event.preventDefault();
event.stopPropagation();
$(addBtn).click();
}
});
var called = false;
redrawAll = function (reload) {
if (called) { return; }
called = true;
nThen(function (waitFor) {
if (!reload) { return; }
Modal.loadMetadata(Env, data, waitFor, "owner");
}).nThen(function () {
var owned = Modal.isOwned(Env, data);
if (typeof(owned) !== "boolean") {
teamOwner = Number(owned);
} else {
teamOwner = undefined;
}
owners = data.owners || [];
pending_owners = data.pending_owners || [];
$div1.empty();
$div2.empty();
$div1.append(h('p', Messages.owner_text));
$div1.append(drawRemove(false)).append(drawRemove(true));
$div2.append(drawAdd());
called = false;
});
};
redrawAll();
Env.evRedrawAll.reg(function (type) {
if (type === "owner") { return; }
setTimeout(function () {
redrawAll();
});
});
// Create modal
var link = h('div.cp-share-columns', [
div1,
divMid,
div2
]);
cb(void 0, link);
};
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';
if (disabled) { return void cb(); }
opts = opts || {};
var friends = common.getFriends(true);
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
var owners = data.owners || [];
var restricted = data.restricted || false;
var allowed = data.allowed || [];
var teamOwner = data.teamId;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
var redrawAll = function () {};
var addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);
var div1 = h('div.cp-share-column.cp-allowlist');
var divMid = h('div.cp-share-column-mid.cp-overlay-container', [
addBtn,
h('div.cp-overlay')
]);
var div2 = h('div.cp-share-column.cp-allowlist');
var $div1 = $(div1);
var $div2 = $(div2);
// Create modal
var link = h('div.cp-share-columns', [
div1,
divMid,
div2
]);
var setLock = function (locked) {
$(link).find('.cp-overlay').toggle(locked);
$(link).find('.cp-usergrid-user').attr('tabindex', locked ? -1 : 0);
$(link).find('.cp-usergrid-filter input').prop('disabled', locked);
$(link).find('.cp-access-add').prop('disabled', locked);
};
// Remove owner column
var drawRemove = function () {
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
var _allowed = {};
var all = Util.deduplicateString(owners.concat(allowed));
all.forEach(function (ed) {
var f;
Object.keys(friends).some(function (c) {
if (friends[c].edPublic === ed) {
f = friends[c];
return true;
}
});
Object.keys(priv.teams).some(function (id) {
if (priv.teams[id].edPublic === ed) {
f = priv.teams[id];
f.teamId = id;
}
});
if (ed === priv.edPublic) {
f = f || user;
if (f.name) { f.edPublic = priv.edPublic; }
}
_allowed[ed] = f ? Util.clone(f) : {
displayName: Messages._getKey('owner_unknownUser', [ed]),
edPublic: ed,
};
if (owners.indexOf(ed) !== -1) {
_allowed[ed].notRemovable = true;
}
});
var remove = function (el) {
// Check selection
var $el = $(el);
var ed = $el.attr('data-ed');
if (!ed) { return; }
nThen(function (waitFor) {
/*
var msg = Messages.allow_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: data.channel || priv.channel,
channels: otherChan,
command: 'RM_ALLOWED',
value: [ed],
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
redrawAll(true);
if (err) {
waitFor.abort();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
UI.log(Messages.saved);
}));
});
};
var cbox = UI.createCheckbox('cp-allowlist', Messages.allow_checkbox, restricted);
var $cbox = $(cbox);
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function () {
if (spinner.getState()) {
$checkbox.prop('checked', !$checkbox.prop('checked'));
return;
}
spinner.spin();
var val = $checkbox.is(':checked');
sframeChan.query('Q_SET_PAD_METADATA', {
channel: data.channel || priv.channel,
channels: otherChan,
command: 'RESTRICT_ACCESS',
value: [Boolean(val)],
teamId: teamOwner
}, function (err, res) {
err = err || (res && res.error);
redrawAll(true);
if (err) {
spinner.hide();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
spinner.done();
UI.log(Messages.saved);
});
});
var msg = Messages._getKey('allow_label', ['']);
var removeCol = UIElements.getUserGrid(msg, {
common: common,
large: true,
data: _allowed,
noSelect: true,
list: true,
remove: remove
}, function () { });
$(removeCol.div).addClass('cp-overlay-container').append(h('div.cp-overlay'));
return h('div', [
h('p', Messages.allow_text),
h('p', cbox),
removeCol.div
]);
};
// Add allow list column
var drawAdd = function () {
var priv = metadataMgr.getPrivateData();
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
var $div = $(h('div.cp-share-column'));
$div.addClass('cp-overlay-container').append(h('div.cp-overlay'));
var _friends = Util.clone(friends);
var friendKeys = Object.keys(_friends);
friendKeys.forEach(function (curve) {
if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
allowed.indexOf(_friends[curve].edPublic) !== -1) {
delete _friends[curve];
}
});
if (!Object.keys(_friends).length) {
var friendText;
if (!friendKeys.length) {
var findContacts = UIElements.noContactsMessage(common);
friendText = h('span.cp-app-prop-content',
findContacts.content
);
} else {
friendText = h('span.cp-app-prop-content', Messages.access_noContact);
}
$div.append(h('div.cp-app-prop', [
Messages.contacts,
h('br'),
friendText
]));
} else {
var addCol = UIElements.getUserGrid(Messages.contacts, {
common: common,
large: true,
data: _friends
}, function () {
//console.log(arguments);
});
$div.append(addCol.div);
}
var _teamsData = Util.clone(teamsData);
Object.keys(_teamsData).forEach(function (id) {
var t = _teamsData[id];
t.teamId = id;
if (owners.indexOf(t.edPublic) !== -1 || allowed.indexOf(t.edPublic) !== -1) {
delete _teamsData[id];
}
});
if (!Object.keys(_teamsData).length) { return $div; }
var teamsList = UIElements.getUserGrid(Messages.teams, {
common: common,
large: true,
noFilter: true,
data: _teamsData
}, function () {});
$div.append(teamsList.div);
return $div;
};
$(addBtn).click(function () {
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
var $div = $div2.find('.cp-share-column');
// Check selection
var $sel = $div.find('.cp-usergrid-user.cp-selected');
var sel = $sel.toArray();
if (!sel.length) { return; }
var dataToAdd = [];
var toAdd = sel.map(function (el) {
var curve = $(el).attr('data-curve');
var teamId = $(el).attr('data-teamid');
// If the pad is woned by a team, we can transfer ownership to ourselves
if (curve === user.curvePublic && teamOwner) { return priv.edPublic; }
var data = friends[curve] || teamsData[teamId];
dataToAdd.push(data);
if (!data) { return; }
return data.edPublic;
}).filter(function (x) { return x; });
nThen(function (waitFor) {
/*
var msg = Messages.allow_addConfirm;
UI.confirm(msg, waitFor(function (yes) {
if (!yes) {
waitFor.abort();
return;
}
}));
}).nThen(function (waitFor) {
*/
// Offer ownership to a friend
if (toAdd.length) {
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: data.channel || priv.channel,
channels: otherChan,
command: 'ADD_ALLOWED',
value: toAdd,
teamId: teamOwner
}, waitFor(function (err, res) {
err = err || (res && res.error);
redrawAll(true);
dataToAdd.forEach(function(mailbox) {
if (mailbox.notifications && mailbox.curvePublic) {
common.mailbox.sendTo("ADD_TO_ACCESS_LIST", {
channel: data.channel || priv.channel,
}, {
channel: mailbox.notifications,
curvePublic: mailbox.curvePublic
});
} else {
return;
}
});
if (err) {
waitFor.abort();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
return void UI.warn(text);
}
}));
}
}).nThen(function () {
UI.log(Messages.saved);
});
});
$(addBtn).on('keydown', function () {
if (event.keyCode === 13) {
event.preventDefault();
event.stopPropagation();
$(addBtn).click();
}
});
var called = false;
redrawAll = function (reload) {
if (called) { return; }
called = true;
nThen(function (waitFor) {
if (!reload) { return; }
Modal.loadMetadata(Env, data, waitFor, "allow");
}).nThen(function () {
var owned = Modal.isOwned(Env, data);
if (typeof(owned) !== "boolean") {
teamOwner = Number(owned);
} else {
teamOwner = undefined;
}
owners = data.owners || [];
restricted = data.restricted || false;
allowed = data.allowed || [];
$div1.empty();
$div2.empty();
$div1.append(drawRemove());
$div2.append(drawAdd());
setLock(!restricted);
called = false;
});
};
redrawAll();
Env.evRedrawAll.reg(function (type) {
if (type === "allow") { return; }
setTimeout(function () {
redrawAll();
});
});
cb(void 0, link);
};
var getUserList = function (common, list) {
if (!Array.isArray(list)) { return; }
var priv = common.getMetadataMgr().getPrivateData();
var user = common.getMetadataMgr().getUserData();
var edPublic = priv.edPublic;
//var strangers = 0;
var _owners = {};
list.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,
uid: user.uid
};
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,
uid: friend.uid,
};
return true;
})) {
return;
}
// Otherwise it's a stranger
_owners[ed] = {
avatar: '?',
name: Messages.owner_unknownUser,
// TODO a possible enhancement is to use data from the context
// ie. if you have opened the access modal from within the pad
// its owner might be present or they might have left some data
// in the pad itself (as is the case of the uid in rich text comments)
// TODO or just implement "Acquaintances"
};
//strangers++;
});
if (!Object.keys(_owners).length) { return; }
/*
if (strangers) {
_owners['stangers'] = {
name: Messages._getKey('properties_unknownUser', [strangers]),
};
}
*/
return UIElements.getUserGrid(null, {
common: common,
noSelect: true,
data: _owners,
large: true
}, function () {});
};
var getAccessTab = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
opts = opts || {};
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
var $div = $(h('div.cp-share-columns'));
if (priv.offline) {
$div.append(h('p', Messages.access_offline));
return void cb(void 0, $div);
}
if (!data) { return void cb(void 0, $div); }
var div1 = h('div.cp-usergrid-user.cp-share-column.cp-access');
var div2 = h('div.cp-usergrid-user.cp-share-column.cp-access');
var $div1 = $(div1).appendTo($div);
var $div2 = $(div2).appendTo($div);
var parsed = Hash.parsePadUrl(data.href || data.roHref);
if (!parsed || !parsed.hashData) { return void console.error("Invalid href"); }
var drawLeft = function () {
var priv = metadataMgr.getPrivateData();
var $d = $('<div>');
var owned = Modal.isOwned(Env, data);
if (!opts.noExpiration) {
var expire = Messages.creation_expireFalse;
if (data.expire && typeof (data.expire) === "number") {
expire = new Date(data.expire).toLocaleString();
}
$d.append(h('div.cp-app-prop', [
Messages.creation_expiration,
h('br'),
h('span.cp-app-prop-content', expire)
]));
}
var $pwLabel = $('<label>', {'for': 'cp-app-prop-password'})
.text(Messages.creation_passwordValue).appendTo($d);
var hasPassword = data.password;
var password = UI.passwordInput({
id: 'cp-app-prop-password',
readonly: 'readonly'
});
var $password = $(password).appendTo($d);
var $pwInput = $password.find('.cp-password-input');
$pwInput.val(data.password || '').click(function () {
$pwInput[0].select();
});
if (!hasPassword) {
$password.hide();
$pwLabel.hide();
}
// 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 && !opts.noEditPassword && owned && data.href) {
if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption)
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.btn', 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 + '%');
});
}
var href = data.href;
var isNotStored = Boolean(data.isNotStored);
sframeChan.query(q, {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
href: href,
oldPassword: data.password || priv.password,
password: newPass
}, function (err, res) {
$(passwordOk).text(Messages.properties_changePasswordButton);
pLocked = false;
err = err || res.error;
if (err) {
if (err === "PASSWORD_ALREADY_USED") {
return void UI.alert(Messages.access_passwordUsed);
}
console.error(err);
return void UI.alert(Messages.properties_passwordError);
}
UI.findOKButton().click();
data.password = newPass;
data.href = res.href;
data.roHref = res.roHref;
data.channel = res.channel;
$pwInput.val(newPass);
if (newPass) {
$password.show();
$pwLabel.show();
} else {
$password.hide();
$pwLabel.hide();
}
// 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 = res.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']);
if (isNotStored) { useUnsafe = true; }
var _href = (priv.readOnly && res.roHref) ? res.roHref : res.href;
if (useUnsafe !== true) {
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);
}
// Trigger a page reload if the href didn't change
if (_href === href) { _href = undefined; }
if (res.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () {
if (isNotStored) {
return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href); });
}
common.gotoURL(_href);
}, {force: true});
}
return void UI.alert(UIElements.fixInlineBRs(Messages.properties_passwordSuccess), function () {
if (!isSharedFolder) {
if (isNotStored) {
return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href); });
}
common.gotoURL(_href);
}
});
});
});
});
$(passwordOk).on('keydown', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
$(passwordOk).click();
}
});
$d.append(changePass);
}
if (owned) {
var deleteOwned = h('button.btn.btn-danger', [h('i.cptools.cptools-destroy'), Messages.fc_delete_owned]);
var spinner = UI.makeSpinner();
UI.confirmButton(deleteOwned, {
classes: 'btn-danger'
}, function () {
spinner.spin();
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: data.channel || priv.channel
}, function (err, obj) {
spinner.done();
UI.findCancelButton().click();
if (err || (obj && obj.error)) { UI.warn(Messages.error); }
});
// If this is a form wiht a answer channel, delete it too
var p = priv.propChannels;
if (p && p.answersChannel) {
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: p.answersChannel
}, function () {});
}
});
if (!opts.noEditPassword) { $d.append(h('br')); }
$d.append(h('div', [
h('label', Messages.access_destroyPad),
h('br'),
deleteOwned,
spinner.spinner
]));
}
return $d;
};
var drawRight = function () {
var priv = metadataMgr.getPrivateData();
// Owners
var content = [];
var _ownersGrid = getUserList(common, data.owners);
if (_ownersGrid && _ownersGrid.div) {
content.push(h('label', Messages.creation_owners));
content.push(_ownersGrid.div);
} else if (!data.rejected) {
content.push(UI.dialog.selectable(Messages.creation_noOwner, {
id: 'cp-app-prop-owners',
}));
}
// Stop here for files: no allow list, no access request
// Also stop for shared folders
if (parsed.hashData.type !== 'pad' || parsed.type === 'drive') { return h('div', content); }
var owned = Modal.isOwned(Env, data);
// Request edit access
if (common.isLoggedIn() && data.roHref && !owned && !opts.calendar && priv.app !== 'form') {
var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right',
Messages.requestEdit_button);
var requestBlock = h('p', requestButton);
var $requestBlock = $(requestBlock).hide();
content.push(requestBlock);
sframeChan.query('Q_CONTACT_OWNER', {
send: false,
metadata: data
}, function (err, obj) {
// Abort if no mailbox available
if (!(obj && obj.state)) { return; }
var spinner = UI.makeSpinner($requestBlock);
$requestBlock.show().find('button').click(function () {
if (spinner.getState()) { return; }
spinner.spin();
sframeChan.query('Q_CONTACT_OWNER', {
send: true,
metadata: data,
query: "REQUEST_PAD_ACCESS"
}, function (err, obj) {
if (obj && obj.state) {
UI.log(Messages.requestEdit_sent);
$requestBlock.find('button').prop('disabled', true);
spinner.done();
} else {
spinner.hide();
}
});
});
});
}
// Mute access requests
var edPublic = priv.edPublic;
var canMute = data.mailbox && owned === true && (
(typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
data.mailbox[edPublic]);
if (owned === true && !opts.calendar && priv.app !== 'form') {
var cbox = UI.createCheckbox('cp-access-mute', Messages.access_muteRequests, !canMute);
var $cbox = $(cbox);
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function () {
if (spinner.getState()) {
$checkbox.prop('checked', !$checkbox.prop('checked'));
return;
}
spinner.spin();
var val = $checkbox.is(':checked');
sframeChan.query('Q_UPDATE_MAILBOX', {
metadata: data,
add: !val
}, function (err, res) {
err = err || (res && res.error);
if (err) {
spinner.hide();
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
: Messages.error;
console.error(err);
return void UI.warn(text);
}
spinner.done();
UI.log(Messages.saved);
});
});
$cbox.find('.cp-checkmark-label').addClass('cp-access-margin-right');
$cbox.find('.cp-checkmark-mark')
.after(h('span.fa.fa-bell-slash.cp-access-margin-right'));
content.push(h('p', cbox));
}
// Allow list
var state = data.restricted ? Messages.allow_enabled : Messages.allow_disabled;
content.push(h('label', Messages._getKey('allow_label', [state])));
if (data.restricted) {
var _allowed = Util.deduplicateString((data.owners || []).concat(data.allowed));
var _allowedGrid = getUserList(common, _allowed);
content.push(_allowedGrid.div);
}
return h('div', content);
};
var redraw = function (right) {
if (!right) {
$div1.empty();
$div1.append(drawLeft());
}
$div2.empty();
$div2.append(drawRight());
};
redraw();
Env.evRedrawAll.reg(function (ownersOrAllow) {
setTimeout(function () {
redraw(ownersOrAllow);
});
});
cb(void 0, $div);
};
Access.getAccessModal = function (common, opts, cb) {
cb = cb || function () {};
opts = opts || {};
opts.wide = true;
opts.access = true;
var hasFriends = Object.keys(common.getFriends()).length;
var buttons = hasFriends? []: UIElements.noContactsMessage(common).buttons;
buttons.unshift({
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [27],
});
var tabs = [{
getTab: getAccessTab,
title: Messages.access_main,
icon: "fa fa-unlock-alt",
}, {
getTab: getAllowTab,
title: Messages.access_allow,
icon: "fa fa-list",
buttons: buttons,
}, {
getTab: getOwnersTab,
title: Messages.creation_owners,
icon: "fa fa-id-badge",
buttons: buttons,
}];
Modal.getModal(common, opts, tabs, cb);
};
return Access;
});