mirror of https://github.com/xwiki-labs/cryptpad
Mutiple answers
This commit is contained in:
parent
b605c516f0
commit
c49949f810
|
@ -146,6 +146,7 @@ define([
|
|||
};
|
||||
common.storeFormAnswer = function (data, cb) {
|
||||
var answer = {
|
||||
uid: data.uid,
|
||||
hash: data.hash,
|
||||
curvePrivate: data.curvePrivate,
|
||||
anonymous: data.anonymous
|
||||
|
@ -171,8 +172,8 @@ define([
|
|||
}
|
||||
console.error(obj.error);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
cb();
|
||||
});
|
||||
};
|
||||
common.deleteFormAnswers = function (data, _cb) {
|
||||
|
|
|
@ -764,8 +764,13 @@
|
|||
justify-content: center;
|
||||
flex: 1;
|
||||
flex-flow: column;
|
||||
div.cp-form-submit-table {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.cp-form-submit-actions {
|
||||
button:not(:last-child) {
|
||||
span:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,29 +39,32 @@ define([
|
|||
array.push(questions);
|
||||
|
||||
Object.keys(answers || {}).forEach(function (key) {
|
||||
var obj = answers[key];
|
||||
csv += '\n';
|
||||
var time = new Date(obj.time).toISOString();
|
||||
var msg = obj.msg || {};
|
||||
var user = msg._userdata || {};
|
||||
var line = [];
|
||||
line.push(time);
|
||||
line.push(user.name || Messages.anonymous);
|
||||
order.forEach(function (key) {
|
||||
var type = form[key].type;
|
||||
if (!TYPES[type]) { return; } // Ignore static types
|
||||
if (TYPES[type].exportCSV) {
|
||||
var res = TYPES[type].exportCSV(msg[key], form[key]);
|
||||
Array.prototype.push.apply(line, res);
|
||||
return;
|
||||
}
|
||||
line.push(String(msg[key] || ''));
|
||||
var _obj = answers[key];
|
||||
Object.keys(_obj).forEach(function (uid) {
|
||||
var obj = _obj[uid];
|
||||
csv += '\n';
|
||||
var time = new Date(obj.time).toISOString();
|
||||
var msg = obj.msg || {};
|
||||
var user = msg._userdata || {};
|
||||
var line = [];
|
||||
line.push(time);
|
||||
line.push(user.name || Messages.anonymous);
|
||||
order.forEach(function (key) {
|
||||
var type = form[key].type;
|
||||
if (!TYPES[type]) { return; } // Ignore static types
|
||||
if (TYPES[type].exportCSV) {
|
||||
var res = TYPES[type].exportCSV(msg[key], form[key]);
|
||||
Array.prototype.push.apply(line, res);
|
||||
return;
|
||||
}
|
||||
line.push(String(msg[key] || ''));
|
||||
});
|
||||
line.forEach(function (v, i) {
|
||||
if (i) { csv += ','; }
|
||||
csv += escapeCSV(v);
|
||||
});
|
||||
array.push(line);
|
||||
});
|
||||
line.forEach(function (v, i) {
|
||||
if (i) { csv += ','; }
|
||||
csv += escapeCSV(v);
|
||||
});
|
||||
array.push(line);
|
||||
});
|
||||
if (isArray) { return array; }
|
||||
return csv;
|
||||
|
|
|
@ -776,6 +776,7 @@ define([
|
|||
bodyEls.push(h('div', els));
|
||||
if (resultsPageObj && (APP.isEditor || APP.isAuditor)) {
|
||||
$(nameCell).addClass('cp-clickable').click(function () {
|
||||
resultsPageObj.answers._parsed = true;
|
||||
APP.renderResults(resultsPageObj.content, resultsPageObj.answers, answerObj.curve);
|
||||
});
|
||||
}
|
||||
|
@ -958,13 +959,14 @@ define([
|
|||
|
||||
var getBlockAnswers = function (answers, uid, filterCurve) {
|
||||
if (!answers) { return; }
|
||||
return Object.keys(answers || {}).map(function (user) {
|
||||
return Object.keys(answers || {}).map(function (key) {
|
||||
var user = key.split('|')[0];
|
||||
if (filterCurve && user === filterCurve) { return; }
|
||||
try {
|
||||
return {
|
||||
curve: user,
|
||||
user: answers[user].msg._userdata,
|
||||
results: answers[user].msg[uid]
|
||||
user: answers[key].msg._userdata,
|
||||
results: answers[key].msg[uid]
|
||||
};
|
||||
} catch (e) { console.error(e); }
|
||||
}).filter(Boolean);
|
||||
|
@ -2507,10 +2509,7 @@ define([
|
|||
|
||||
// If content is defined, we'll be able to click on a row to display
|
||||
// all the answers of this user
|
||||
var lines = makePollTable(_answers, opts, content && {
|
||||
content: content,
|
||||
answers: answers
|
||||
});
|
||||
var lines = makePollTable(_answers, opts, content);
|
||||
|
||||
var total = makePollTotal(_answers, opts);
|
||||
if (total) { lines.push(h('div', total)); }
|
||||
|
@ -2575,8 +2574,10 @@ define([
|
|||
//var answersByTime = {};
|
||||
Object.keys(answers).forEach(function (curve) {
|
||||
var obj = answers[curve];
|
||||
var day = getDay(new Date(obj.time));
|
||||
Util.inc(tally, +day);
|
||||
Object.keys(obj).forEach(function (uid) {
|
||||
var day = getDay(new Date(obj[uid].time));
|
||||
Util.inc(tally, +day);
|
||||
});
|
||||
});
|
||||
|
||||
var times = Object.keys(tally).map(Number).filter(Boolean);
|
||||
|
@ -2614,6 +2615,23 @@ define([
|
|||
|
||||
Messages.form_deleteAll = "Delete all"; // XXX
|
||||
|
||||
var parseAnswers = function (answers) {
|
||||
var _answers = {};
|
||||
Object.keys(answers || {}).forEach(function (curve) {
|
||||
var all = answers[curve];
|
||||
Object.keys(all || {}).forEach(function (uid) {
|
||||
_answers[curve + '|' + uid] = all[uid]
|
||||
});
|
||||
});
|
||||
return _answers;
|
||||
};
|
||||
|
||||
var getAnswersLength = function (answers) {
|
||||
return Object.values(answers || {}).reduce(function (size, obj) {
|
||||
return size + Object.keys(obj).length;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
var renderResults = APP.renderResults = function (content, answers, showUser) {
|
||||
var $container = $('div.cp-form-creator-results').empty().css('display', '');
|
||||
|
||||
|
@ -2626,7 +2644,7 @@ define([
|
|||
var titleDiv = h('h1.cp-form-view-title', title);
|
||||
$container.append(titleDiv);
|
||||
|
||||
var answerCount = Object.keys(answers || {}).length;
|
||||
var answerCount = getAnswersLength(answers);
|
||||
|
||||
if (!answerCount) {
|
||||
$container.append(h('div.alert.alert-info', Messages.form_results_empty));
|
||||
|
@ -2676,7 +2694,6 @@ define([
|
|||
});
|
||||
|
||||
APP.common.getPadMetadata({channel: content.answers.channel}, function (md) {
|
||||
if (!Object.keys(answers || {}).length) { return; }
|
||||
var owners = md.owners;
|
||||
if (!Array.isArray(owners) || !owners.length) { return; }
|
||||
var owned = APP.common.isOwned(owners);
|
||||
|
@ -2709,7 +2726,11 @@ define([
|
|||
if (!model || !model.printResults) { return; }
|
||||
|
||||
// Only use content if we're not viewing individual answers
|
||||
var print = model.printResults(answers, uid, form, !header && content);
|
||||
var _answers = parseAnswers(answers);
|
||||
var print = model.printResults(_answers, uid, form, !header && {
|
||||
content: content,
|
||||
answers: answers
|
||||
});
|
||||
|
||||
var q = h('div.cp-form-block-question', block.q || Messages.form_default);
|
||||
|
||||
|
@ -2754,62 +2775,67 @@ define([
|
|||
return '#';
|
||||
};
|
||||
|
||||
var els = Object.keys(answers).map(function (curve) {
|
||||
var obj = answers[curve];
|
||||
var answer = obj.msg;
|
||||
var date = new Date(obj.time).toLocaleString();
|
||||
var text, warning, badge;
|
||||
if (!answer._userdata || (!answer._userdata.name && !answer._userdata.curvePublic)) {
|
||||
text = Messages._getKey('form_answerAnonymous', [date]);
|
||||
} else {
|
||||
var ud = answer._userdata;
|
||||
var user;
|
||||
if (ud.profile) {
|
||||
if (priv && priv.friends[curve]) {
|
||||
badge = h('span.cp-form-friend', [
|
||||
h('i.fa.fa-address-book'),
|
||||
Messages._getKey('isContact', [ud.name || Messages.anonymous])
|
||||
]);
|
||||
}
|
||||
user = h('a', {
|
||||
href: getHref(ud.profile) // Only used visually
|
||||
}, Util.fixHTML(ud.name || Messages.anonymous));
|
||||
if (curve !== ud.curvePublic) {
|
||||
warning = h('span.cp-form-warning', Messages.form_answerWarning);
|
||||
}
|
||||
var els = [];
|
||||
Object.keys(answers).forEach(function (curve) {
|
||||
var userAnswers = answers[curve];
|
||||
Object.keys(userAnswers).forEach(function (uid) {
|
||||
var obj = userAnswers[uid];
|
||||
var answer = obj.msg;
|
||||
var date = new Date(obj.time).toLocaleString();
|
||||
var text, warning, badge;
|
||||
if (!answer._userdata || (!answer._userdata.name && !answer._userdata.curvePublic)) {
|
||||
text = Messages._getKey('form_answerAnonymous', [date]);
|
||||
} else {
|
||||
user = h('b', Util.fixHTML(ud.name || Messages.anonymous));
|
||||
var ud = answer._userdata;
|
||||
var user;
|
||||
if (ud.profile) {
|
||||
if (priv && priv.friends[curve]) {
|
||||
badge = h('span.cp-form-friend', [
|
||||
h('i.fa.fa-address-book'),
|
||||
Messages._getKey('isContact', [ud.name || Messages.anonymous])
|
||||
]);
|
||||
}
|
||||
user = h('a', {
|
||||
href: getHref(ud.profile) // Only used visually
|
||||
}, Util.fixHTML(ud.name || Messages.anonymous));
|
||||
if (curve !== ud.curvePublic) {
|
||||
warning = h('span.cp-form-warning', Messages.form_answerWarning);
|
||||
}
|
||||
} else {
|
||||
user = h('b', Util.fixHTML(ud.name || Messages.anonymous));
|
||||
}
|
||||
text = Messages._getKey('form_answerName', [user.outerHTML, date]);
|
||||
}
|
||||
text = Messages._getKey('form_answerName', [user.outerHTML, date]);
|
||||
}
|
||||
var span = UI.setHTML(h('span'), text);
|
||||
var viewButton = h('button.btn.btn-secondary.small', Messages.form_viewButton);
|
||||
var div = h('div.cp-form-individual', [span, viewButton, warning, badge]);
|
||||
$(viewButton).click(function () {
|
||||
var res = {};
|
||||
res[curve] = obj;
|
||||
var back = h('button.btn.btn-secondary.small', Messages.form_backButton);
|
||||
$(back).click(function () {
|
||||
summary = true;
|
||||
$s.click();
|
||||
var span = UI.setHTML(h('span'), text);
|
||||
var viewButton = h('button.btn.btn-secondary.small', Messages.form_viewButton);
|
||||
var div = h('div.cp-form-individual', [span, viewButton, warning, badge]);
|
||||
$(viewButton).click(function () {
|
||||
var res = {};
|
||||
res[curve] = {};
|
||||
res[curve][uid] = obj;
|
||||
var back = h('button.btn.btn-secondary.small', Messages.form_backButton);
|
||||
$(back).click(function () {
|
||||
summary = true;
|
||||
$s.click();
|
||||
});
|
||||
var header = h('div.cp-form-individual', [
|
||||
span.cloneNode(true),
|
||||
back
|
||||
]);
|
||||
show(res, header);
|
||||
});
|
||||
var header = h('div.cp-form-individual', [
|
||||
span.cloneNode(true),
|
||||
back
|
||||
]);
|
||||
show(res, header);
|
||||
});
|
||||
$(div).find('a').click(function (e) {
|
||||
e.preventDefault();
|
||||
APP.common.openURL(Hash.hashToHref(ud.profile, 'profile'));
|
||||
});
|
||||
if (showUser === curve) {
|
||||
setTimeout(function () {
|
||||
showUser = undefined;
|
||||
$(viewButton).click();
|
||||
$(div).find('a').click(function (e) {
|
||||
e.preventDefault();
|
||||
APP.common.openURL(Hash.hashToHref(ud.profile, 'profile'));
|
||||
});
|
||||
}
|
||||
return div;
|
||||
if (showUser === curve) {
|
||||
setTimeout(function () {
|
||||
showUser = undefined;
|
||||
$(viewButton).click();
|
||||
});
|
||||
}
|
||||
els.push(div);
|
||||
});
|
||||
});
|
||||
$results.append(els);
|
||||
});
|
||||
|
@ -2818,11 +2844,6 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var getAnswersLength = function (answers) {
|
||||
return Object.keys(answers || {}).filter(function (key) {
|
||||
return key && key.slice(0,1) !== "_";
|
||||
}).length;
|
||||
};
|
||||
var addResultsButton = function (framework, content, answers) {
|
||||
var $container = $('.cp-forms-results-participant');
|
||||
var l = getAnswersLength(answers);
|
||||
|
@ -2878,6 +2899,7 @@ define([
|
|||
var $formContainer = $('div.cp-form-creator-content').hide();
|
||||
var $resContainer = $('div.cp-form-creator-results').hide();
|
||||
var $container = $('div.cp-form-creator-answered').empty().css('display', '');
|
||||
APP.inAnsweredPage = true;
|
||||
|
||||
if (APP.answeredInForm) {
|
||||
$container.hide();
|
||||
|
@ -2887,29 +2909,6 @@ define([
|
|||
$resContainer.css('display', '');
|
||||
}
|
||||
|
||||
var viewOnly = content.answers.cantEdit || APP.isClosed;
|
||||
var action = h(viewOnly ? 'button.btn.btn-secondary' : 'button.btn.btn-primary', [
|
||||
viewOnly ? h('i.fa.fa-bar-chart') : h('i.fa.fa-pencil'),
|
||||
h('span', viewOnly ? Messages.form_viewAnswer : Messages.form_editAnswer)
|
||||
]);
|
||||
|
||||
$(action).click(function () {
|
||||
$formContainer.css('display', '');
|
||||
$container.hide();
|
||||
APP.answeredInForm = true;
|
||||
if (viewOnly) {
|
||||
$formContainer.find('.cp-form-send-container .cp-open').hide();
|
||||
if (Array.isArray(APP.formBlocks)) {
|
||||
APP.formBlocks.forEach(function (b) {
|
||||
if (!b.setEditable) { return; }
|
||||
b.setEditable(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (answers._time) { APP.lastAnswerTime = answers._time; }
|
||||
|
||||
// If responses are public, show button to view them
|
||||
var responses;
|
||||
if (content.answers.privateKey) {
|
||||
|
@ -2930,12 +2929,31 @@ define([
|
|||
var answers = obj && obj.results;
|
||||
if (answers) { APP.answers = answers; }
|
||||
$('body').addClass('cp-app-form-results');
|
||||
APP.inAnsweredPage = false;
|
||||
renderResults(content, answers);
|
||||
$container.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Messages.form_answer_new = "New responses"; // XXX
|
||||
var newAnswer;
|
||||
if (content.answers.multiple) {
|
||||
newAnswer = h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-plus'),
|
||||
h('span', Messages.form_answer_new)
|
||||
]);
|
||||
$(newAnswer).click(function () {
|
||||
$formContainer.css('display', '');
|
||||
$container.hide();
|
||||
delete APP.editingUid;
|
||||
delete APP.hasAnswered;
|
||||
APP.answeredInForm = true;
|
||||
APP.inAnsweredPage = false;
|
||||
updateForm(framework, content, false);
|
||||
});
|
||||
}
|
||||
|
||||
var description = h('div.cp-form-creator-results-description#cp-form-response-msg');
|
||||
if (content.answers.msg) {
|
||||
var $desc = $(description);
|
||||
|
@ -2943,38 +2961,80 @@ define([
|
|||
$desc.find('a').click(linkClickHandler);
|
||||
}
|
||||
|
||||
var del;
|
||||
var canDelete = !viewOnly && content.answers.version >= 2;
|
||||
if (answers._hash && canDelete) {
|
||||
del = h('button.btn.btn-danger', 'DELETE');
|
||||
$(del).click(function () {
|
||||
var sframeChan = framework._.sfCommon.getSframeChannel();
|
||||
sframeChan.query("Q_FORM_DELETE_ANSWER", {
|
||||
channel: content.answers.channel,
|
||||
hash: answers._hash
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.error) {
|
||||
console.error(obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
framework._.sfCommon.gotoURL();
|
||||
});
|
||||
});
|
||||
}
|
||||
var entries = [];
|
||||
Object.keys(answers).forEach(function (uid) {
|
||||
var answer = answers[uid];
|
||||
|
||||
var actions = h('div.cp-form-submit-actions', [
|
||||
action,
|
||||
del,
|
||||
responses || undefined
|
||||
]);
|
||||
var viewOnly = content.answers.cantEdit || APP.isClosed;
|
||||
|
||||
var action = h(viewOnly ? 'button.btn.btn-secondary' : 'button.btn.btn-primary', [
|
||||
viewOnly ? h('i.fa.fa-bar-chart') : h('i.fa.fa-pencil'),
|
||||
h('span', viewOnly ? Messages.form_viewAnswer : Messages.form_editAnswer)
|
||||
]);
|
||||
|
||||
$(action).click(function () {
|
||||
$formContainer.css('display', '');
|
||||
$container.hide();
|
||||
APP.answeredInForm = true;
|
||||
APP.editingUid = uid;
|
||||
APP.editingTime = answer._time;
|
||||
APP.inAnsweredPage = false;
|
||||
updateForm(framework, content, false, answer);
|
||||
if (viewOnly) {
|
||||
$formContainer.find('.cp-form-send-container .cp-open').hide();
|
||||
if (Array.isArray(APP.formBlocks)) {
|
||||
APP.formBlocks.forEach(function (b) {
|
||||
if (!b.setEditable) { return; }
|
||||
b.setEditable(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var date = new Date(answer._time).toLocaleString();
|
||||
|
||||
var del;
|
||||
var canDelete = !viewOnly && content.answers.version >= 2;
|
||||
if (answer._hash && canDelete) {
|
||||
del = h('button.btn.btn-danger', [
|
||||
h('i.fa.fa-trash-o'),
|
||||
h('span', Messages.kanban_delete)
|
||||
]);
|
||||
$(del).click(function () {
|
||||
var sframeChan = framework._.sfCommon.getSframeChannel();
|
||||
sframeChan.query("Q_FORM_DELETE_ANSWER", {
|
||||
channel: content.answers.channel,
|
||||
hash: answers._hash
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.error) {
|
||||
console.error(obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
framework._.sfCommon.gotoURL();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
entries.push(h('div.cp-form-submit-actions', [
|
||||
h('span.cp-form-submit-time', date),
|
||||
h('span.cp-form-submit-action', action),
|
||||
h('span.cp-form-submit-del', del),
|
||||
]));
|
||||
});
|
||||
|
||||
var table = h('div.cp-form-submit-table', entries);
|
||||
|
||||
Messages.form_alreadyAnsweredMult = "You responded to this form on:";
|
||||
|
||||
var title = framework._.title.title || framework._.title.defaultTitle;
|
||||
|
||||
$container.append(h('div.cp-form-submit-success', [
|
||||
h('h3.cp-form-view-title', title),
|
||||
h('div.alert.alert-info', Messages._getKey('form_alreadyAnswered', [
|
||||
new Date(APP.lastAnswerTime).toLocaleString()])),
|
||||
description,
|
||||
actions
|
||||
h('div', Messages.form_alreadyAnsweredMult),
|
||||
table,
|
||||
newAnswer,
|
||||
responses ? h('div', responses) : undefined
|
||||
]));
|
||||
$container.append(getLogo());
|
||||
};
|
||||
|
@ -3125,6 +3185,9 @@ define([
|
|||
// You've already answered with your credentials
|
||||
$(anonRadioOn).find('input').attr('disabled', 'disabled');
|
||||
$(anonRadioOff).find('input[type="radio"]').prop('checked', true);
|
||||
} else if (APP.editingUid) {
|
||||
// We're edting and we can answer anonymously: our previous answer is anonymous
|
||||
$(anonRadioOn).find('input[type="radio"]').prop('checked', true);
|
||||
}
|
||||
if (!$anonName) {
|
||||
$(anonOffContent).append(h('div.cp-form-anon-answer-input', [
|
||||
|
@ -3153,13 +3216,10 @@ define([
|
|||
h('span.cp-form-anon-answer-registered', user.name || Messages.anonymous)
|
||||
]));
|
||||
}
|
||||
if (APP.hasAnswered && content.answers.cantEdit || APP.isClosed) {
|
||||
$radio.hide();
|
||||
}
|
||||
|
||||
var send = h('button.cp-open.btn.btn-primary', APP.hasAnswered ? Messages.form_update : Messages.form_submit);
|
||||
var reset = h('button.cp-open.cp-reset-button.btn.btn-danger-alt', Messages.form_reset);
|
||||
$(reset).click(function () {
|
||||
var $reset = $(reset).click(function () {
|
||||
if (!Array.isArray(APP.formBlocks)) { return; }
|
||||
APP.formBlocks.forEach(function (data) {
|
||||
if (typeof(data.reset) === "function") { data.reset(); }
|
||||
|
@ -3172,10 +3232,11 @@ define([
|
|||
return UI.warn(Messages.error);
|
||||
}
|
||||
|
||||
$send.attr('disabled', 'disabled');
|
||||
var results = getFormResults();
|
||||
if (!results) { return; }
|
||||
|
||||
$send.attr('disabled', 'disabled');
|
||||
|
||||
var wantName = Util.isChecked($(anonRadioOff).find('input[type="radio"]'));
|
||||
|
||||
var user = metadataMgr.getUserData();
|
||||
|
@ -3191,6 +3252,8 @@ define([
|
|||
};
|
||||
}
|
||||
|
||||
var uid = APP.editingUid;
|
||||
if (uid) { results._uid = uid; }
|
||||
var sframeChan = framework._.sfCommon.getSframeChannel();
|
||||
sframeChan.query('Q_FORM_SUBMIT', {
|
||||
mailbox: content.answers,
|
||||
|
@ -3206,6 +3269,8 @@ define([
|
|||
console.error(err || data.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
delete APP.editingUid;
|
||||
delete APP.editingTime;
|
||||
if (results._userdata && loggedIn) {
|
||||
$(anonRadioOn).find('input').attr('disabled', 'disabled');
|
||||
$(anonRadioOff).find('input[type="radio"]').prop('checked', true);
|
||||
|
@ -3216,15 +3281,18 @@ define([
|
|||
|
||||
$send.removeAttr('disabled');
|
||||
$send.text(Messages.form_update);
|
||||
APP.hasAnswered = data.results;
|
||||
APP.answeredInForm = false;
|
||||
showAnsweredPage(framework, content, APP.hasAnswered);
|
||||
if (content.answers.cantEdit) {
|
||||
$(radioContainer).hide();
|
||||
}
|
||||
|
||||
APP.getMyAnswers();
|
||||
});
|
||||
});
|
||||
|
||||
if (APP.hasAnswered && content.answers.cantEdit || APP.isClosed) {
|
||||
$radio.hide();
|
||||
$send.hide();
|
||||
$reset.hide();
|
||||
}
|
||||
|
||||
if (APP.isClosed) {
|
||||
send = undefined;
|
||||
reset = undefined;
|
||||
|
@ -3512,7 +3580,8 @@ define([
|
|||
var user = metadataMgr.getUserData();
|
||||
// If we are a participant, our results shouldn't be in the table but in the
|
||||
// editable part: remove them from _answers
|
||||
_answers = getBlockAnswers(APP.answers, uid, !editable && user.curvePublic);
|
||||
var _ans = parseAnswers(APP.answers);
|
||||
_answers = getBlockAnswers(_ans, uid, !editable && user.curvePublic);
|
||||
name = user.name;
|
||||
}
|
||||
|
||||
|
@ -3965,14 +4034,14 @@ define([
|
|||
});
|
||||
|
||||
// If the form is already submitted, show an info message
|
||||
if (APP.hasAnswered) {
|
||||
showAnsweredPage(framework, content, APP.hasAnswered);
|
||||
var lastTime = (answers && answers._time) || APP.editingTime;
|
||||
if (APP.hasAnswered && lastTime) {
|
||||
$container.prepend(h('div.alert.alert-info',
|
||||
Messages._getKey('form_alreadyAnswered', [
|
||||
new Date(answers._time || APP.lastAnswerTime).toLocaleString()])));
|
||||
new Date(lastTime).toLocaleString()])));
|
||||
}
|
||||
|
||||
if (APP.isClosed) {
|
||||
if (APP.isClosed || content.answers.cantEdit) {
|
||||
APP.formBlocks.forEach(function (b) {
|
||||
if (!b.setEditable) { return; }
|
||||
b.setEditable(false);
|
||||
|
@ -3980,6 +4049,7 @@ define([
|
|||
}
|
||||
|
||||
// In view mode, add "Submit" and "reset" buttons
|
||||
APP.cantAnon = Object.keys(answers || {}).length && !answers._isAnon;
|
||||
$container.append(makeFormControls(framework, content, evOnChange));
|
||||
|
||||
// In view mode, tell the user if answers are forced to be anonymous or authenticated
|
||||
|
@ -4254,30 +4324,43 @@ define([
|
|||
Messages.form_editable_off = "One time only"; // XXX
|
||||
Messages.form_editable_on = "One time and edit"; // XXX
|
||||
Messages.form_editable_on_del = "One time and edit/delete"; // XXX
|
||||
Messages.form_editable_mult = "Multiple times"; // XXX
|
||||
Messages.form_multiple = "Multiple times"; // XXX
|
||||
Messages.form_multiple_edit = "Multiple times and edit/delete"; // XXX
|
||||
|
||||
var editable = !content.answers.cantEdit;
|
||||
var mult = !!content.answers.multiple;
|
||||
var radioOn = UI.createRadio('cp-form-editable', 'cp-form-editable-on',
|
||||
Messages.form_editable_off, !editable, {
|
||||
Messages.form_editable_off, !editable && !mult, {
|
||||
input: { value: 0 },
|
||||
});
|
||||
var editableOnStr = canDelete ? Messages.form_editable_on_del
|
||||
: Messages.form_editable_on;
|
||||
var radioOff = UI.createRadio('cp-form-editable', 'cp-form-editable-off',
|
||||
editableOnStr, !!editable, {
|
||||
editableOnStr, editable && !mult, {
|
||||
input: { value: 1 },
|
||||
});
|
||||
var radioContainer = h('div.cp-form-editable-radio', [radioOn, radioOff]);
|
||||
|
||||
var radioMult = UI.createRadio('cp-form-editable', 'cp-form-editable-mult',
|
||||
Messages.form_multiple, mult && !editable, {
|
||||
input: { value: 2 },
|
||||
});
|
||||
var radioMultEdit = UI.createRadio('cp-form-editable', 'cp-form-editable-multedit',
|
||||
Messages.form_multiple_edit, mult && editable, {
|
||||
input: { value: 3 },
|
||||
});
|
||||
var radioContainer = h('div.cp-form-editable-radio', [radioOn, radioOff, radioMult, radioMultEdit]);
|
||||
$(radioContainer).find('input[type="radio"]').on('change', function() {
|
||||
var val = $('input:radio[name="cp-form-editable"]:checked').val();
|
||||
val = Number(val) || 0;
|
||||
content.answers.cantEdit = !val;
|
||||
|
||||
content.answers.cantEdit = val === 0 || val === 2;
|
||||
content.answers.multiple = val === 2 || val === 3;
|
||||
if (canDelete) {
|
||||
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||
channel: content.answers.channel,
|
||||
command: 'ALLOW_LINE_DELETION',
|
||||
value: [Boolean(val)],
|
||||
//teamId: teamOwner // XXX?
|
||||
//teamId: teamOwner // XXX TODO
|
||||
}, function (err, res) {
|
||||
err = err || (res && res.error);
|
||||
if (err) { console.error(err); }
|
||||
|
@ -4701,76 +4784,92 @@ define([
|
|||
showAnonBlockedAlert();
|
||||
}
|
||||
|
||||
var getMyAnswers = APP.getMyAnswers = function () {
|
||||
sframeChan.query("Q_FETCH_MY_ANSWERS", {
|
||||
channel: content.answers.channel,
|
||||
validateKey: content.answers.validateKey,
|
||||
publicKey: content.answers.publicKey
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.error) {
|
||||
if (obj.error === "EANSWERED") {
|
||||
// No drive mode already answered: can't answer again
|
||||
if (content.answers.privateKey) {
|
||||
return void getResults(content.answers.privateKey);
|
||||
}
|
||||
// Here, we know results are private so we can use an error screen
|
||||
return void UI.errorLoadingScreen(Messages.form_answered);
|
||||
}
|
||||
UI.warn(Messages.form_cantFindAnswers);
|
||||
}
|
||||
checkIntegrity(false);
|
||||
var answers;
|
||||
if (obj && !obj.error && Object.keys(obj).length) {
|
||||
answers = obj;
|
||||
APP.hasAnswered = answers;
|
||||
// If we have a non-anon answer, we can't answer anonymously later
|
||||
showAnsweredPage(framework, content, APP.hasAnswered);
|
||||
return;
|
||||
}
|
||||
updateForm(framework, content, false, answers);
|
||||
});
|
||||
};
|
||||
|
||||
// If the results are public and there is at least one doodle, fetch the results now
|
||||
if (content.answers.privateKey && Object.keys(content.form).some(function (uid) {
|
||||
return content.form[uid].type === "poll";
|
||||
})) {
|
||||
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
||||
channel: content.answers.channel,
|
||||
validateKey: content.answers.validateKey,
|
||||
publicKey: content.answers.publicKey,
|
||||
privateKey: content.answers.privateKey,
|
||||
cantEdit: content.answers.cantEdit
|
||||
}, function (err, obj) {
|
||||
var answers = obj && obj.results;
|
||||
if (answers) { APP.answers = answers; }
|
||||
getMyAnswers = APP.getMyAnswers = function () {
|
||||
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
||||
channel: content.answers.channel,
|
||||
validateKey: content.answers.validateKey,
|
||||
publicKey: content.answers.publicKey,
|
||||
privateKey: content.answers.privateKey,
|
||||
cantEdit: content.answers.cantEdit
|
||||
}, function (err, obj) {
|
||||
var answers = obj && obj.results;
|
||||
if (answers) { APP.answers = answers; }
|
||||
|
||||
if (obj && obj.noDriveAnswered) {
|
||||
// No drive mode already answered: can't answer again
|
||||
if (obj && obj.noDriveAnswered) {
|
||||
// No drive mode already answered: can't answer again
|
||||
if (answers) {
|
||||
$body.addClass('cp-app-form-results');
|
||||
renderResults(content, answers);
|
||||
} else {
|
||||
return void UI.errorLoadingScreen(Messages.form_answered);
|
||||
}
|
||||
return;
|
||||
}
|
||||
checkIntegrity(false);
|
||||
var myAnswers = {};
|
||||
var curve1 = user.curvePublic;
|
||||
var curve2 = obj && obj.myKey; // Anonymous answer key
|
||||
if (answers) {
|
||||
$body.addClass('cp-app-form-results');
|
||||
renderResults(content, answers);
|
||||
} else {
|
||||
return void UI.errorLoadingScreen(Messages.form_answered);
|
||||
var myAnonAnswersObj = answers[curve2];
|
||||
if (Object.keys(myAnonAnswersObj || {}).length) {
|
||||
Object.keys(myAnonAnswersObj).forEach(function (uid) {
|
||||
myAnswers[uid] = myAnonAnswersObj[uid].msg;
|
||||
myAnswers[uid]._isAnon = true;
|
||||
});
|
||||
}
|
||||
var myAnswersObj = answers[curve1];
|
||||
if (Object.keys(myAnswersObj || {}).length) {
|
||||
Object.keys(myAnswersObj).forEach(function (uid) {
|
||||
myAnswers[uid] = myAnswersObj[uid].msg;
|
||||
myAnswers[uid]._isAnon = false;
|
||||
});
|
||||
}
|
||||
APP.hasAnswered = Object.keys(myAnswers).length ? myAnswers : undefined;
|
||||
if (APP.hasAnswered) {
|
||||
return void showAnsweredPage(framework, content, APP.hasAnswered);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
checkIntegrity(false);
|
||||
var myAnswers;
|
||||
var curve1 = user.curvePublic;
|
||||
var curve2 = obj && obj.myKey; // Anonymous answer key
|
||||
if (answers) {
|
||||
var myAnswersObj = answers[curve1] || answers[curve2] || undefined;
|
||||
if (myAnswersObj) {
|
||||
myAnswers = myAnswersObj.msg;
|
||||
myAnswers._time = myAnswersObj.time;
|
||||
APP.hasAnswered = myAnswers;
|
||||
}
|
||||
}
|
||||
// If we have a non-anon answer, we can't answer anonymously later
|
||||
if (answers[curve1]) { APP.cantAnon = true; }
|
||||
|
||||
updateForm(framework, content, false, myAnswers);
|
||||
});
|
||||
return;
|
||||
updateForm(framework, content, false, myAnswers);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
sframeChan.query("Q_FETCH_MY_ANSWERS", {
|
||||
channel: content.answers.channel,
|
||||
validateKey: content.answers.validateKey,
|
||||
publicKey: content.answers.publicKey
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.error) {
|
||||
if (obj.error === "EANSWERED") {
|
||||
// No drive mode already answered: can't answer again
|
||||
if (content.answers.privateKey) {
|
||||
return void getResults(content.answers.privateKey);
|
||||
}
|
||||
// Here, we know results are private so we can use an error screen
|
||||
return void UI.errorLoadingScreen(Messages.form_answered);
|
||||
}
|
||||
UI.warn(Messages.form_cantFindAnswers);
|
||||
}
|
||||
var answers;
|
||||
if (obj && !obj.error) {
|
||||
answers = obj;
|
||||
APP.hasAnswered = answers;
|
||||
// If we have a non-anon answer, we can't answer anonymously later
|
||||
if (!obj._isAnon) { APP.cantAnon = true; }
|
||||
}
|
||||
checkIntegrity(false);
|
||||
updateForm(framework, content, false, answers);
|
||||
});
|
||||
getMyAnswers();
|
||||
|
||||
});
|
||||
|
||||
|
@ -4780,8 +4879,11 @@ define([
|
|||
// repeatedly, we'll only "redraw" once with the latest content.
|
||||
var _redraw = Util.notAgainForAnother(function (framework, content) {
|
||||
var answers, temp;
|
||||
if (!APP.isEditor) { answers = getFormResults(); }
|
||||
if (!APP.isEditor) { answers = getFormResults(true); }
|
||||
else { temp = getTempFields(); }
|
||||
if (APP.inAnsweredPage) {
|
||||
return void APP.getMyAnswers();
|
||||
}
|
||||
updateForm(framework, content, APP.isEditor, answers, temp);
|
||||
}, 500);
|
||||
var redrawTo;
|
||||
|
|
107
www/form/main.js
107
www/form/main.js
|
@ -162,6 +162,9 @@ define([
|
|||
Cryptpad.makeNetwork(w(function (err, nw) {
|
||||
network = nw;
|
||||
}));
|
||||
Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) {
|
||||
if (md && md.deleteLines) { deleteLines = true; }
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (!network) { return void cb({error: "E_CONNECT"}); }
|
||||
|
||||
|
@ -212,9 +215,6 @@ define([
|
|||
});
|
||||
};
|
||||
config.onReady = function (obj) {
|
||||
if (obj && obj.metadata && obj.metadata.deleteLines) {
|
||||
deleteLines = true;
|
||||
}
|
||||
var myKey;
|
||||
// If we have submitted an anonymous answer, retrieve it
|
||||
if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) {
|
||||
|
@ -236,8 +236,14 @@ define([
|
|||
delete results[parsed._proof.key];
|
||||
}
|
||||
}
|
||||
|
||||
parsed._time = cfg && cfg.time;
|
||||
if (deleteLines) { parsed._hash = hash; }
|
||||
|
||||
if (data.cantEdit && results[senderCurve]) { return; }
|
||||
results[senderCurve] = {
|
||||
results[senderCurve] = results[senderCurve] || {};
|
||||
var uid = parsed._uid || '000';
|
||||
results[senderCurve][uid] = {
|
||||
msg: parsed,
|
||||
hash: hash,
|
||||
time: cfg && cfg.time
|
||||
|
@ -247,13 +253,13 @@ define([
|
|||
});
|
||||
});
|
||||
sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) {
|
||||
var answer;
|
||||
var answers = [];
|
||||
var myKeys;
|
||||
nThen(function (w) {
|
||||
Cryptpad.getFormKeys(w(function (keys) {
|
||||
myKeys = keys;
|
||||
}));
|
||||
Cryptpad.getFormAnswer({channel: data.channel}, true, w(function (obj) {
|
||||
Cryptpad.getFormAnswer({channel: data.channel}, false, w(function (obj) {
|
||||
if (!obj || obj.error) {
|
||||
if (obj && obj.error === "ENODRIVE") {
|
||||
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
|
||||
|
@ -267,42 +273,68 @@ define([
|
|||
w.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
answer = obj;
|
||||
// Get the latest edit per uid
|
||||
var temp = {};
|
||||
obj.forEach(function (ans) {
|
||||
var uid = ans.uid || '000';
|
||||
temp[uid] = ans;
|
||||
});
|
||||
answers = Object.values(temp);
|
||||
}));
|
||||
Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) {
|
||||
if (md && md.deleteLines) { deleteLines = true; }
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (answer.anonymous) {
|
||||
if (!myKeys.formSeed) { return void cb({ error: "ANONYMOUS_ERROR" }); }
|
||||
myKeys = getAnonymousKeys(myKeys.formSeed, data.channel);
|
||||
}
|
||||
Cryptpad.getHistoryRange({
|
||||
channel: data.channel,
|
||||
lastKnownHash: answer.hash,
|
||||
toHash: answer.hash,
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { return void cb(obj); }
|
||||
var messages = obj.messages;
|
||||
if (!messages.length) { return void cb(); }
|
||||
if (obj.lastKnownHash !== answer.hash) { return void cb(); }
|
||||
try {
|
||||
var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, {
|
||||
validateKey: data.validateKey,
|
||||
ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate),
|
||||
my_private: Nacl.util.decodeBase64(myKeys.curvePrivate),
|
||||
their_public: Nacl.util.decodeBase64(data.publicKey)
|
||||
});
|
||||
var parsed = JSON.parse(res.content);
|
||||
parsed._isAnon = answer.anonymous;
|
||||
parsed._time = messages[0].time;
|
||||
if (deleteLines) { parsed._hash = answer.hash; }
|
||||
cb(parsed);
|
||||
} catch (e) {
|
||||
cb({error: e});
|
||||
}
|
||||
var n = nThen;
|
||||
var err;
|
||||
var all = {};
|
||||
answers.forEach(function (answer) {
|
||||
n = n(function(waitFor) {
|
||||
var finalKeys = myKeys;
|
||||
if (answer.anonymous) {
|
||||
if (!myKeys.formSeed) {
|
||||
err = 'ANONYMOUS_ERROR';
|
||||
console.error('ANONYMOUS_ERROR', answer);
|
||||
return;
|
||||
}
|
||||
finalKeys = getAnonymousKeys(myKeys.formSeed, data.channel);
|
||||
}
|
||||
Cryptpad.getHistoryRange({
|
||||
channel: data.channel,
|
||||
lastKnownHash: answer.hash,
|
||||
toHash: answer.hash,
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { err = obj.error; return; }
|
||||
var messages = obj.messages;
|
||||
if (!messages.length) {
|
||||
// XXX TODO delete from drive.forms
|
||||
return;
|
||||
}
|
||||
if (obj.lastKnownHash !== answer.hash) { return; }
|
||||
try {
|
||||
var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, {
|
||||
validateKey: data.validateKey,
|
||||
ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate),
|
||||
my_private: Nacl.util.decodeBase64(finalKeys.curvePrivate),
|
||||
their_public: Nacl.util.decodeBase64(data.publicKey)
|
||||
});
|
||||
var parsed = JSON.parse(res.content);
|
||||
parsed._isAnon = answer.anonymous;
|
||||
parsed._time = messages[0].time;
|
||||
if (deleteLines) { parsed._hash = answer.hash; }
|
||||
var uid = parsed._uid || '000';
|
||||
if (all[uid] && !all[uid]._isAnon) { parsed._isAnon = false; }
|
||||
all[uid] = parsed;
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
}));
|
||||
}).nThen;
|
||||
});
|
||||
n(function () {
|
||||
if (err) { return void cb({error: err}); }
|
||||
cb(all);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -346,6 +378,8 @@ define([
|
|||
}
|
||||
|
||||
var crypto = Utils.Crypto.Mailbox.createEncryptor(myKeys);
|
||||
var uid = data.results._uid || Utils.Util.uid();
|
||||
data.results._uid = uid;
|
||||
var text = JSON.stringify(data.results);
|
||||
var ciphertext = crypto.encrypt(text, box.publicKey);
|
||||
|
||||
|
@ -355,6 +389,7 @@ define([
|
|||
ciphertext
|
||||
], function (err, response) {
|
||||
Cryptpad.storeFormAnswer({
|
||||
uid: uid,
|
||||
channel: box.channel,
|
||||
hash: hash,
|
||||
curvePrivate: ephemeral_private,
|
||||
|
|
Loading…
Reference in New Issue