Merge branch 'form' into staging

pull/1/head
ansuz 4 years ago
commit ff5ba8eb1c

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 39 KiB

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: 'cptools'; font-family: 'cptools';
src: src:
url('fonts/cptools.ttf?n9y2kz') format('truetype'), url('fonts/cptools.ttf?am461j') format('truetype'),
url('fonts/cptools.woff?n9y2kz') format('woff'), url('fonts/cptools.woff?am461j') format('woff'),
url('fonts/cptools.svg?n9y2kz#cptools') format('svg'); url('fonts/cptools.svg?am461j#cptools') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -25,11 +25,35 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.cptools-sheet:before { .cptools-form-list-check:before {
content: "\e908"; content: "\e916";
} }
.cptools-slide:before { .cptools-form-grid-check:before {
content: "\e907"; content: "\e917";
}
.cptools-form-poll:before {
content: "\e910";
}
.cptools-form-grid-radio:before {
content: "\e918";
}
.cptools-form-list-radio:before {
content: "\e919";
}
.cptools-form-page-break:before {
content: "\e91a";
}
.cptools-form-paragraph:before {
content: "\e91b";
}
.cptools-form-text:before {
content: "\e91c";
}
.cptools-form-list-ordered:before {
content: "\e91d";
}
.cptools-folder-no-color:before {
content: "\e900";
} }
.cptools-whiteboard:before { .cptools-whiteboard:before {
content: "\e901"; content: "\e901";
@ -37,6 +61,9 @@
.cptools-new-template:before { .cptools-new-template:before {
content: "\e902"; content: "\e902";
} }
.cptools-shared-folder:before {
content: "\e903";
}
.cptools-file-upload:before { .cptools-file-upload:before {
content: "\e904"; content: "\e904";
} }
@ -46,9 +73,24 @@
.cptools-poll:before { .cptools-poll:before {
content: "\e906"; content: "\e906";
} }
.cptools-slide:before {
content: "\e907";
}
.cptools-sheet:before {
content: "\e908";
}
.cptools-folder-open:before {
content: "\e909";
}
.cptools-kanban:before { .cptools-kanban:before {
content: "\e90a"; content: "\e90a";
} }
.cptools-folder:before {
content: "\e90b";
}
.cptools-shared-folder-open:before {
content: "\e90c";
}
.cptools-code:before { .cptools-code:before {
content: "\e90d"; content: "\e90d";
} }
@ -58,8 +100,11 @@
.cptools-file:before { .cptools-file:before {
content: "\e90f"; content: "\e90f";
} }
.cptools-destroy:before { .cptools-palette:before {
content: "\e915"; content: "\e911";
}
.cptools-folder-upload:before {
content: "\e912";
} }
.cptools-add-bottom:before { .cptools-add-bottom:before {
content: "\e913"; content: "\e913";
@ -67,24 +112,6 @@
.cptools-add-top:before { .cptools-add-top:before {
content: "\e914"; content: "\e914";
} }
.cptools-folder-upload:before { .cptools-destroy:before {
content: "\e912"; content: "\e915";
}
.cptools-folder-no-color:before {
content: "\e900";
}
.cptools-shared-folder:before {
content: "\e903";
}
.cptools-folder-open:before {
content: "\e909";
}
.cptools-folder:before {
content: "\e90b";
}
.cptools-shared-folder-open:before {
content: "\e90c";
}
.cptools-palette:before {
content: "\e911";
} }

@ -10,6 +10,7 @@
code: #EAA000; code: #EAA000;
slide: #e57614; slide: #e57614;
poll: #2c9e98; poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7; whiteboard: #a72ba7;
kanban: #8C4; kanban: #8C4;
sheet: #40865c; sheet: #40865c;
@ -426,3 +427,13 @@
@cp_calendar-now: @cryptpad_color_brand_300; @cp_calendar-now: @cryptpad_color_brand_300;
@cp_calendar-now-fg: @cryptpad_color_grey_800; @cp_calendar-now-fg: @cryptpad_color_grey_800;
// Forms
@cp_form-bg1: @cryptpad_color_grey_800;
@cp_form-bg2: @cryptpad_color_grey_900;
@cp_form-border: @cryptpad_color_grey_800;
@cp_form-poll-color: @cryptpad_color_grey_800;
@cp_form-poll-no: @cryptpad_color_light_red;
@cp_form-poll-yes: @cryptpad_color_light_green;
@cp_form-poll-maybe: @cryptpad_color_light_yellow;
@cp_form-poll-yes-color: @cryptpad_color_green;
@cp_form-invalid: @cryptpad_color_red;

@ -10,6 +10,7 @@
code: #EAA000; code: #EAA000;
slide: #e57614; slide: #e57614;
poll: #2c9e98; poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7; whiteboard: #a72ba7;
kanban: #8C4; kanban: #8C4;
sheet: #40865c; sheet: #40865c;
@ -425,3 +426,14 @@
@cp_calendar-border: @cryptpad_color_grey_300; @cp_calendar-border: @cryptpad_color_grey_300;
@cp_calendar-now: @cryptpad_color_brand; @cp_calendar-now: @cryptpad_color_brand;
@cp_calendar-now-fg: @cryptpad_color_grey_200; @cp_calendar-now-fg: @cryptpad_color_grey_200;
// Forms
@cp_form-bg1: @cryptpad_color_grey_200;
@cp_form-bg2: @cryptpad_color_grey_100;
@cp_form-border: @cryptpad_color_grey_200;
@cp_form-poll-color: @cryptpad_color_grey_800;
@cp_form-poll-no: @cryptpad_color_light_red;
@cp_form-poll-yes: @cryptpad_color_light_green;
@cp_form-poll-maybe: @cryptpad_color_light_yellow;
@cp_form-poll-yes-color: @cryptpad_color_green;
@cp_form-invalid: @cryptpad_color_red;

@ -71,6 +71,12 @@
div.cp-button-confirm { div.cp-button-confirm {
display: inline-block; display: inline-block;
&.new {
vertical-align: top;
button {
height: 35px;
}
}
button { button {
margin: 0 !important; margin: 0 !important;
} }
@ -85,7 +91,7 @@
} }
} }
} }
button.cp-button-confirm-placeholder { button.cp-button-confirm-placeholder:not(.new) {
margin-bottom: 3px !important; margin-bottom: 3px !important;
} }

@ -12,7 +12,7 @@ define(function() {
* You should never remove the drive from this list. * You should never remove the drive from this list.
*/ */
AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard', AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */]; /*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts', 'form'];
/* The registered only types are apps restricted to registered users. /* The registered only types are apps restricted to registered users.
* You should never remove apps from this list unless you know what you're doing. The apps * You should never remove apps from this list unless you know what you're doing. The apps
* listed here by default can't work without a user account. * listed here by default can't work without a user account.
@ -117,6 +117,7 @@ define(function() {
code: 'cptools-code', code: 'cptools-code',
slide: 'cptools-slide', slide: 'cptools-slide',
poll: 'cptools-poll', poll: 'cptools-poll',
form: 'cptools-poll',
whiteboard: 'cptools-whiteboard', whiteboard: 'cptools-whiteboard',
todo: 'cptools-todo', todo: 'cptools-todo',
contacts: 'fa-address-book', contacts: 'fa-address-book',

@ -34,6 +34,12 @@ var factory = function (Util, Crypto, Keys, Nacl) {
var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey); var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey);
return Nacl.util.encodeBase64(keyPair.publicKey); return Nacl.util.encodeBase64(keyPair.publicKey);
}; };
Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) {
var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr);
var privateKey = Nacl.util.decodeBase64(curvePrivateStr);
var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey);
return Nacl.util.encodeBase64(keyPair.publicKey);
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version; var version = secret.version;
@ -209,6 +215,17 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
}); });
return k ? Crypto.b64AddSlashes(k) : ''; return k ? Crypto.b64AddSlashes(k) : '';
}; };
var getAuditorKey = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^auditor=/.test(data)) {
k = data.slice(8);
return true;
}
});
return k ? Crypto.b64AddSlashes(k) : '';
};
var getOwnerKey = function (hashArr) { var getOwnerKey = function (hashArr) {
var k; var k;
// Check if we have a ownerKey for this pad // Check if we have a ownerKey for this pad
@ -231,6 +248,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
parsed.present = options.indexOf('present') !== -1; parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1; parsed.embed = options.indexOf('embed') !== -1;
parsed.versionHash = getVersionHash(options); parsed.versionHash = getVersionHash(options);
parsed.auditorKey = getAuditorKey(options);
parsed.newPadOpts = getNewPadOpts(options); parsed.newPadOpts = getNewPadOpts(options);
parsed.loginOpts = getLoginOpts(options); parsed.loginOpts = getLoginOpts(options);
parsed.ownerKey = getOwnerKey(options); parsed.ownerKey = getOwnerKey(options);
@ -272,6 +290,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
present: parsed.present, present: parsed.present,
ownerKey: parsed.ownerKey, ownerKey: parsed.ownerKey,
versionHash: parsed.versionHash, versionHash: parsed.versionHash,
auditorKey: parsed.auditorKey,
newPadOpts: parsed.newPadOpts, newPadOpts: parsed.newPadOpts,
loginOpts: parsed.loginOpts, loginOpts: parsed.loginOpts,
password: parsed.password password: parsed.password
@ -298,6 +317,10 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
if (versionHash) { if (versionHash) {
hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/';
} }
var auditorKey = typeof(opts.auditorKey) !== "undefined" ? opts.auditorKey : parsed.auditorKey;
if (auditorKey) {
hash += 'auditor=' + Crypto.b64RemoveSlashes(auditorKey) + '/';
}
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash; return hash;

@ -747,6 +747,7 @@ define([
cb = Util.once(cb); cb = Util.once(cb);
} }
var classes = 'btn ' + (config.classes || 'btn-primary'); var classes = 'btn ' + (config.classes || 'btn-primary');
var newCls = config.new ? '.new' : '';
var button = h('button', { var button = h('button', {
"class": classes, "class": classes,
@ -759,7 +760,7 @@ define([
}); });
var timer = h('div.cp-button-timer', div); var timer = h('div.cp-button-timer', div);
var content = h('div.cp-button-confirm', [ var content = h('div.cp-button-confirm'+newCls, [
button, button,
timer timer
]); ]);
@ -795,7 +796,8 @@ define([
to = setTimeout(todo, INTERVAL); to = setTimeout(todo, INTERVAL);
}; };
$(originalBtn).addClass('cp-button-confirm-placeholder').click(function (e) { var newCls2 = config.new ? 'new' : '';
$(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).click(function (e) {
e.stopPropagation(); e.stopPropagation();
// If we have a validation function, continue only if it's true // If we have a validation function, continue only if it's true
if (config.validate && !config.validate()) { return; } if (config.validate && !config.validate()) { return; }
@ -1175,6 +1177,7 @@ define([
var label = h('span.cp-checkmark-label', labelTxt); var label = h('span.cp-checkmark-label', labelTxt);
$mark.keydown(function (e) { $mark.keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 32) { if (e.which === 32) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -1220,20 +1223,22 @@ define([
$.extend(markOpts, opts.mark || {}); $.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts); var input = h('input', inputOpts);
var $input = $(input);
var mark = h('span.cp-radio-mark', markOpts); var mark = h('span.cp-radio-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt); var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) { $(mark).keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 32) { if (e.which === 32) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if ($(input).is(':checked')) { return; } if ($input.is(':checked')) { return; }
$(input).prop('checked', !$(input).is(':checked')); $input.prop('checked', !$input.is(':checked'));
$(input).change(); $input.change();
} }
}); });
$(input).change(function () { $(mark).focus(); }); $input.change(function () { $(mark).focus(); });
var radio = h('label', labelOpts, [ var radio = h('label', labelOpts, [
input, input,

@ -936,7 +936,8 @@ define([
return button; return button;
}; };
var createMdToolbar = function (common, editor) { var createMdToolbar = function (common, editor, cfg) {
cfg = cfg || {};
var $toolbar = $('<div>', { var $toolbar = $('<div>', {
'class': 'cp-markdown-toolbar' 'class': 'cp-markdown-toolbar'
}); });
@ -1025,9 +1026,39 @@ define([
icon: 'fa-newspaper-o' icon: 'fa-newspaper-o'
} }
}; };
if (typeof(cfg.embed) === "function") {
actions.embed = {
icon: 'fa-picture-o',
action: function () {
var _cfg = {
types: ['file'],
where: ['root']
};
common.openFilePicker(_cfg, function (data) {
if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type);
return;
}
if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; }
common.setPadAttribute('atime', +new Date(), null, data.href);
var privateDat = common.getMetadataMgr().getPrivateData();
var origin = privateDat.fileHost || privateDat.origin;
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
cfg.embed($('<media-tag src="' + src +
'" data-crypto-key="cryptpad:' + data.key + '"></media-tag>'), data);
});
}
};
}
var onClick = function () { var onClick = function () {
var type = $(this).attr('data-type'); var type = $(this).attr('data-type');
var texts = editor.getSelections(); var texts = editor.getSelections();
if (actions[type].action) {
return actions[type].action();
}
var newTexts = texts.map(function (str) { var newTexts = texts.map(function (str) {
str = str || Messages.mdToolbar_defaultText; str = str || Messages.mdToolbar_defaultText;
if (actions[type].apply) { if (actions[type].apply) {
@ -1054,7 +1085,7 @@ define([
}).appendTo($toolbar); }).appendTo($toolbar);
return $toolbar; return $toolbar;
}; };
UIElements.createMarkdownToolbar = function (common, editor) { UIElements.createMarkdownToolbar = function (common, editor, opts) {
var readOnly = common.getMetadataMgr().getPrivateData().readOnly; var readOnly = common.getMetadataMgr().getPrivateData().readOnly;
if (readOnly) { if (readOnly) {
return { return {
@ -1064,7 +1095,7 @@ define([
}; };
} }
var $toolbar = createMdToolbar(common, editor); var $toolbar = createMdToolbar(common, editor, opts);
var cfg = { var cfg = {
title: Messages.mdToolbar_button, title: Messages.mdToolbar_button,
element: $toolbar element: $toolbar
@ -1133,6 +1164,7 @@ define([
sheet: 'sheets', sheet: 'sheets',
poll: 'poll', poll: 'poll',
kanban: 'kanban', kanban: 'kanban',
form: 'form',
whiteboard: 'whiteboard', whiteboard: 'whiteboard',
}; };
@ -1472,11 +1504,13 @@ define([
if (config.isSelect) { if (config.isSelect) {
var pressed = ''; var pressed = '';
var to; var to;
$container.onChange = Util.mkEvent();
$container.on('click', 'a', function () { $container.on('click', 'a', function () {
value = $(this).data('value'); value = $(this).data('value');
var $val = $(this); var $val = $(this);
var textValue = $val.html() || value; var textValue = $val.html() || value;
$button.find('.cp-dropdown-button-title').html(textValue); $button.find('.cp-dropdown-button-title').html(textValue);
$container.onChange.fire(textValue, value);
}); });
$container.keydown(function (e) { $container.keydown(function (e) {
var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible'); var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible');
@ -2050,6 +2084,7 @@ define([
AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; } AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; }
return true; return true;
}); });
Messages.type.form = "Form"; // XXX
types.forEach(function (p) { types.forEach(function (p) {
var $element = $('<li>', { var $element = $('<li>', {
'class': 'cp-icons-element', 'class': 'cp-icons-element',
@ -3012,6 +3047,7 @@ define([
// ACCEPT // ACCEPT
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel, channel: msg.content.channel,
channels: msg.content.channels,
command: 'ADD_OWNERS', command: 'ADD_OWNERS',
value: [priv.edPublic] value: [priv.edPublic]
}, function (err, res) { }, function (err, res) {
@ -3061,6 +3097,7 @@ define([
// Remove yourself from the pending owners // Remove yourself from the pending owners
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel, channel: msg.content.channel,
channels: msg.content.channels,
command: 'RM_PENDING_OWNERS', command: 'RM_PENDING_OWNERS',
value: [priv.edPublic] value: [priv.edPublic]
}, function (err, res) { }, function (err, res) {
@ -3077,6 +3114,7 @@ define([
// Remove yourself from the pending owners // Remove yourself from the pending owners
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel, channel: msg.content.channel,
channels: msg.content.channels,
command: 'RM_PENDING_OWNERS', command: 'RM_PENDING_OWNERS',
value: [priv.edPublic] value: [priv.edPublic]
}, function (err, res) { }, function (err, res) {

@ -76,7 +76,7 @@ define([
postMessage("GET", { postMessage("GET", {
key: ['edPrivate'], key: ['edPrivate'],
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj.error) { return; } if (!obj || obj.error) { return; }
try { try {
keys.push({ keys.push({
edPrivate: obj, edPrivate: obj,
@ -84,14 +84,16 @@ define([
}); });
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
})); }));
// Push teams keys // Push teams keys
postMessage("GET", { postMessage("GET", {
key: ['teams'], key: ['teams'],
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj.error) { return; } if (!obj || obj.error) { return; }
Object.keys(obj || {}).forEach(function (id) { Object.keys(obj || {}).forEach(function (id) {
var t = obj[id]; var t = obj[id];
var _keys = t.keys.drive || {}; var _keys = t.keys.drive || {};
_keys.id = id;
if (!_keys.edPrivate) { return; } if (!_keys.edPrivate) { return; }
keys.push(t.keys.drive); keys.push(t.keys.drive);
}); });
@ -101,6 +103,57 @@ define([
}); });
}; };
common.getFormKeys = function (cb) {
var curvePrivate;
var formSeed;
Nthen(function (waitFor) {
postMessage("GET", {
key: ['curvePrivate'],
}, waitFor(function (obj) {
if (!obj || obj.error) { return; }
curvePrivate = obj;
}));
postMessage("GET", {
key: ['form_seed'],
}, waitFor(function (obj) {
if (!obj || obj.error) { return; }
formSeed = obj;
}));
}).nThen(function () {
cb({
curvePrivate: curvePrivate,
curvePublic: curvePrivate && Hash.getCurvePublicFromPrivate(curvePrivate),
formSeed: formSeed
});
});
};
common.getFormAnswer = function (data, cb) {
postMessage("GET", {
key: ['forms', data.channel],
}, cb);
};
common.storeFormAnswer = function (data) {
postMessage("SET", {
key: ['forms', data.channel],
value: {
hash: data.hash,
curvePrivate: data.curvePrivate,
anonymous: data.anonymous
}
}, function (obj) {
if (obj && obj.error) {
if (obj.error === "ENODRIVE") {
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
if (answered.indexOf(data.channel) === -1) { answered.push(data.channel); }
localStorage.CP_formAnswered = JSON.stringify(answered);
return;
}
console.error(obj.error);
}
});
};
common.makeNetwork = function (cb) { common.makeNetwork = function (cb) {
require([ require([
'/bower_components/netflux-websocket/netflux-client.js', '/bower_components/netflux-websocket/netflux-client.js',
@ -712,6 +765,10 @@ define([
delete meta.chat2; delete meta.chat2;
delete meta.chat; delete meta.chat;
delete meta.cursor; delete meta.cursor;
if (meta.type === "form") {
delete parsed.answers;
}
} }
}; };

@ -32,6 +32,12 @@ define([
var teamOwner = data.teamId; var teamOwner = data.teamId;
var title = opts.title; var title = opts.title;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
opts = opts || {}; opts = opts || {};
var redrawAll = function () {}; var redrawAll = function () {};
@ -255,6 +261,7 @@ define([
// Send the command // Send the command
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'ADD_OWNERS', command: 'ADD_OWNERS',
value: toAddTeams.map(function (obj) { return obj.edPublic; }), value: toAddTeams.map(function (obj) { return obj.edPublic; }),
teamId: teamOwner teamId: teamOwner
@ -290,6 +297,7 @@ define([
// Send the command // Send the command
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'ADD_PENDING_OWNERS', command: 'ADD_PENDING_OWNERS',
value: toAdd, value: toAdd,
teamId: teamOwner teamId: teamOwner
@ -310,6 +318,7 @@ define([
// Send the command // Send the command
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'ADD_OWNERS', command: 'ADD_OWNERS',
value: [priv.edPublic], value: [priv.edPublic],
teamId: teamOwner teamId: teamOwner
@ -338,6 +347,7 @@ define([
if (!friend) { return; } if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", { common.mailbox.sendTo("ADD_OWNER", {
channel: channel, channel: channel,
channels: otherChan,
href: href, href: href,
calendar: opts.calendar, calendar: opts.calendar,
password: data.password || priv.password, password: data.password || priv.password,
@ -417,6 +427,12 @@ define([
var allowed = data.allowed || []; var allowed = data.allowed || [];
var teamOwner = data.teamId; var teamOwner = data.teamId;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
var redrawAll = function () {}; 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 addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);
@ -495,6 +511,7 @@ define([
// Send the command // Send the command
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'RM_ALLOWED', command: 'RM_ALLOWED',
value: [ed], value: [ed],
teamId: teamOwner teamId: teamOwner
@ -524,6 +541,7 @@ define([
var val = $checkbox.is(':checked'); var val = $checkbox.is(':checked');
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'RESTRICT_ACCESS', command: 'RESTRICT_ACCESS',
value: [Boolean(val)], value: [Boolean(val)],
teamId: teamOwner teamId: teamOwner
@ -659,6 +677,7 @@ define([
// Send the command // Send the command
sframeChan.query('Q_SET_PAD_METADATA', { sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel, channel: channel,
channels: otherChan,
command: 'ADD_ALLOWED', command: 'ADD_ALLOWED',
value: toAdd, value: toAdd,
teamId: teamOwner teamId: teamOwner
@ -987,6 +1006,15 @@ define([
UI.findCancelButton().click(); UI.findCancelButton().click();
if (err || (obj && obj.error)) { UI.warn(Messages.error); } 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.answersChannel) {
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: p.answersChannel
}, function () {});
}
}); });
if (!opts.noEditPassword) { $d.append(h('br')); } if (!opts.noEditPassword) { $d.append(h('br')); }
$d.append(h('div', [ $d.append(h('div', [
@ -1020,7 +1048,7 @@ define([
var owned = Modal.isOwned(Env, data); var owned = Modal.isOwned(Env, data);
// Request edit access // Request edit access
if (common.isLoggedIn() && ((data.roHref && !data.href) || data.fakeHref) && !owned && !opts.calendar) { if (common.isLoggedIn() && ((data.roHref && !data.href) || data.fakeHref) && !owned && !opts.calendar && priv.app !== 'form') {
var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right', var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right',
Messages.requestEdit_button); Messages.requestEdit_button);
var requestBlock = h('p', requestButton); var requestBlock = h('p', requestButton);
@ -1058,7 +1086,7 @@ define([
var canMute = data.mailbox && owned === true && ( var canMute = data.mailbox && owned === true && (
(typeof (data.mailbox) === "string" && data.owners[0] === edPublic) || (typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
data.mailbox[edPublic]); data.mailbox[edPublic]);
if (owned === true && !opts.calendar) { if (owned === true && !opts.calendar && priv.app !== 'form') {
var cbox = UI.createCheckbox('cp-access-mute', Messages.access_muteRequests, !canMute); var cbox = UI.createCheckbox('cp-access-mute', Messages.access_muteRequests, !canMute);
var $cbox = $(cbox); var $cbox = $(cbox);
var spinner = UI.makeSpinner($cbox); var spinner = UI.makeSpinner($cbox);

@ -24,6 +24,7 @@ define([
if (privateData.propChannels) { if (privateData.propChannels) {
var p = privateData.propChannels; var p = privateData.propChannels;
data.channel = data.channel || p.channel; data.channel = data.channel || p.channel;
data.answersChannel = data.answersChannel || p.answersChannel;
data.rtChannel = data.rtChannel || p.rtChannel; data.rtChannel = data.rtChannel || p.rtChannel;
data.lastVersion = data.lastVersion || p.lastVersion; data.lastVersion = data.lastVersion || p.lastVersion;
data.lastCpHash = data.lastCpHash || p.lastCpHash; data.lastCpHash = data.lastCpHash || p.lastCpHash;
@ -75,6 +76,7 @@ define([
var bytes = 0; var bytes = 0;
var historyBytes; var historyBytes;
var chan = [data.channel]; var chan = [data.channel];
if (data.answersChannel) { chan.push(data.answersChannel); }
if (data.rtChannel) { chan.push(data.rtChannel); } if (data.rtChannel) { chan.push(data.rtChannel); }
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); } if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }

@ -494,7 +494,23 @@ define([
var parsed = Hash.parsePadUrl(pathname); var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1; var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
var versionHash = hashes.viewHash && opts.versionHash; var versionHash = hashes.viewHash && opts.versionHash;
var canBAR = parsed.type !== 'drive' && !versionHash; var isForm = parsed.type === "form"; // && opts.auditorHash;
var canBAR = parsed.type !== 'drive' && !versionHash && !isForm;
var labelEdit = Messages.share_linkEdit;
var labelView = Messages.share_linkView;
var auditor;
if (isForm) {
Messages.share_formEdit = "Author"; // XXX
Messages.share_formView = "Participant"; // XXX
Messages.share_formAuditor = "Auditor"; // XXX
labelEdit = Messages.share_formEdit;
labelView = Messages.share_formView;
auditor = UI.createRadio('accessRights', 'cp-share-form', Messages.share_formAuditor, false, {
mark: {tabindex:1},
});
}
var burnAfterReading = (hashes.viewHash && canBAR) ? var burnAfterReading = (hashes.viewHash && canBAR) ?
UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, { UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, {
@ -505,12 +521,13 @@ define([
h('label', Messages.share_linkAccess), h('label', Messages.share_linkAccess),
h('div.radio-group',[ h('div.radio-group',[
UI.createRadio('accessRights', 'cp-share-editable-false', UI.createRadio('accessRights', 'cp-share-editable-false',
Messages.share_linkView, true, { mark: {tabindex:1} }), labelView, true, { mark: {tabindex:1} }),
canPresent ? UI.createRadio('accessRights', 'cp-share-present', canPresent ? UI.createRadio('accessRights', 'cp-share-present',
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined, Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true', UI.createRadio('accessRights', 'cp-share-editable-true',
Messages.share_linkEdit, false, { mark: {tabindex:1} })]), labelEdit, false, { mark: {tabindex:1} }),
burnAfterReading auditor]),
burnAfterReading,
]); ]);
// Burn after reading // Burn after reading
@ -553,6 +570,7 @@ define([
var embed = val.embed; var embed = val.embed;
var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present')); var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present'));
var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar')); var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar'));
var formAuditor = Util.isChecked($rights.find('#cp-share-form'));
if (versionHash) { if (versionHash) {
edit = false; edit = false;
present = false; present = false;
@ -569,6 +587,9 @@ define([
} }
var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash
: hashes.viewHash; : hashes.viewHash;
if (formAuditor && opts.auditorHash) {
hash = opts.auditorHash;
}
var href = burnAfterReading ? opts.burnAfterReadingUrl var href = burnAfterReading ? opts.burnAfterReadingUrl
: (origin + pathname + '#' + hash); : (origin + pathname + '#' + hash);
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
@ -594,6 +615,9 @@ define([
$rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true); $rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true);
$rights.find('#cp-share-editable-true').attr('checked', true); $rights.find('#cp-share-editable-true').attr('checked', true);
} }
if (isForm && !opts.auditorHash) {
$rights.find('#cp-share-form').removeAttr('checked').attr('disabled', true);
}
var getLink = function () { var getLink = function () {
return $rights.parent().find('#cp-share-link-preview'); return $rights.parent().find('#cp-share-link-preview');

@ -112,6 +112,7 @@ define([
Store.set = function (clientId, data, cb) { Store.set = function (clientId, data, cb) {
var s = getStore(data.teamId); var s = getStore(data.teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); } if (!s) { return void cb({ error: 'ENOTFOUND' }); }
if (!s.proxy) { return void cb({ error: 'ENODRIVE' }); }
var path = data.key.slice(); var path = data.key.slice();
var key = path.pop(); var key = path.pop();
var obj = Util.find(s.proxy, path); var obj = Util.find(s.proxy, path);
@ -629,6 +630,7 @@ define([
if (!proxy.uid) { if (!proxy.uid) {
store.noDriveUid = store.noDriveUid || Hash.createChannelId(); store.noDriveUid = store.noDriveUid || Hash.createChannelId();
} }
var metadata = { var metadata = {
// "user" is shared with everybody via the userlist // "user" is shared with everybody via the userlist
user: { user: {
@ -655,7 +657,7 @@ define([
accountName: proxy.login_name || '', accountName: proxy.login_name || '',
offline: store.proxy && store.offline, offline: store.proxy && store.offline,
teams: teams, teams: teams,
plan: account.plan plan: account.plan,
} }
}; };
cb(JSON.parse(JSON.stringify(metadata))); cb(JSON.parse(JSON.stringify(metadata)));
@ -2139,11 +2141,23 @@ define([
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (!data.command) { return void cb({ error: 'EINVAL' }); } if (!data.command) { return void cb({ error: 'EINVAL' }); }
var s = getStore(data.teamId); var s = getStore(data.teamId);
var otherChannels = data.channels;
delete data.channels;
s.rpc.setMetadata(data, function (err, res) { s.rpc.setMetadata(data, function (err, res) {
if (err) { return void cb({ error: err }); } if (err) { return void cb({ error: err }); }
if (!Array.isArray(res) || !res.length) { return void cb({}); } if (!Array.isArray(res) || !res.length) { return void cb({}); }
cb(res[0]); cb(res[0]);
}); });
// If we have other related channels, send the command for them too
if (Array.isArray(otherChannels)) {
otherChannels.forEach(function (chan) {
var _d = Util.clone(data);
_d.channel = chan;
Store.setPadMetadata(clientId, _d, function () {
});
});
}
}; };
// GET_FULL_HISTORY from sframe-common-outer // GET_FULL_HISTORY from sframe-common-outer
@ -2696,7 +2710,12 @@ define([
nThen(function (waitFor) { nThen(function (waitFor) {
if (!proxy.settings) { proxy.settings = NEW_USER_SETTINGS; } if (!proxy.settings) { proxy.settings = NEW_USER_SETTINGS; }
if (!proxy.forms) { proxy.forms = {}; }
if (!proxy.friends_pending) { proxy.friends_pending = {}; } if (!proxy.friends_pending) { proxy.friends_pending = {}; }
// Form seed is used to generate a box encryption keypair when
// answering a form anonymously
if (!proxy.form_seed) { proxy.form_seed = Hash.createChannelId(); }
// Call onCacheReady if the manager is not yet defined // Call onCacheReady if the manager is not yet defined
if (!manager) { if (!manager) {

@ -818,6 +818,7 @@ define([
_findChannels(Env, toUnpin).forEach(function (id) { _findChannels(Env, toUnpin).forEach(function (id) {
var data = _getFileData(Env, id); var data = _getFileData(Env, id);
var arr = [data.channel]; var arr = [data.channel];
if (data.answersChannel) { arr.push(data.answersChannel); }
if (data.rtChannel) { arr.push(data.rtChannel); } if (data.rtChannel) { arr.push(data.rtChannel); }
if (data.lastVersion) { arr.push(Hash.hrefToHexChannelId(data.lastVersion)); } if (data.lastVersion) { arr.push(Hash.hrefToHexChannelId(data.lastVersion)); }
Array.prototype.push.apply(toKeep, arr); Array.prototype.push.apply(toKeep, arr);
@ -1184,6 +1185,10 @@ define([
result.push(otherChan); result.push(otherChan);
} }
} }
// Pin form answers channels
if (data.answersChannel && result.indexOf(data.answersChannel) === -1) {
result.push(data.answersChannel);
}
// Pin onlyoffice realtime patches // Pin onlyoffice realtime patches
if (data.rtChannel && result.indexOf(data.rtChannel) === -1) { if (data.rtChannel && result.indexOf(data.rtChannel) === -1) {
result.push(data.rtChannel); result.push(data.rtChannel);

@ -553,11 +553,13 @@ MessengerUI, Messages, Pages) {
if (toolbar.isDeleted) { if (toolbar.isDeleted) {
return void UI.warn(Messages.deletedFromServer); return void UI.warn(Messages.deletedFromServer);
} }
var privateData = config.metadataMgr.getPrivateData();
var title = (config.title && config.title.getTitle && config.title.getTitle()) var title = (config.title && config.title.getTitle && config.title.getTitle())
|| (config.title && config.title.defaultName) || (config.title && config.title.defaultName)
|| ""; || "";
Common.getSframeChannel().event('EV_SHARE_OPEN', { Common.getSframeChannel().event('EV_SHARE_OPEN', {
title: title title: title,
auditorHash: privateData.form_auditorHash
}); });
}); });

@ -0,0 +1,566 @@
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/tools.less';
@import (reference) '../../customize/src/less2/include/avatar.less';
&.cp-app-form {
@form_input-width: 400px;
.framework_main(
@bg-color: @colortheme_apps[form]
);
display: flex;
flex-flow: column;
font: @colortheme_app-font;
color: @cryptpad_text_col;
background-color: @cp_app-bg;
#cp-app-form-editor {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
&.cp-app-form-results {
div.cp-form-creator-content, .cp-app-form-button-results {
display: none !important;
}
}
&:not(.cp-app-form-results) {
div.cp-form-creator-results, .cp-app-form-button-creator {
display: none !important;
}
}
#cp-app-form-container {
display: flex;
flex: 1;
justify-content: center;
min-width: 300px;
.cp-form-input-block {
display: flex;
}
div.cp-form-creator-container {
display: flex;
flex: 1;
justify-content: center;
min-width: 300px;
//flex-wrap: wrap;
overflow: auto;
@media screen and (max-width: 1000px) {
flex-wrap: wrap;
justify-content: flex-start;
.cp-form-creator-control {
width: 100% !important;
.cp-form-creator-settings {
display: flex;
justify-content: space-evenly;
flex-wrap: wrap;
}
}
}
.cp-form-creator-settings {
padding: 30px;
& > div:not(:last-child) {
margin-bottom: 20px;
}
}
div.cp-form-filler-container {
width: 300px;
min-width: 0;
flex: 0 300 300px;
}
div.cp-form-creator-control {
padding: 10px;
display: flex;
flex-flow: column;
width: 300px;
.cp-form-creator-types {
margin-top: 20px;
display: flex;
flex-flow: column;
}
}
div.cp-form-creator-content, div.cp-form-creator-results {
max-width: 1000px;
min-width: 300px;
padding: 10px;
display: flex;
flex-flow: column;
flex: 1 1 1000px;
overflow: auto;
.cp-form-creator-add-inline {
display: flex;
flex-flow: row;
align-items: center;
margin-bottom: 20px;
button {
width: 50px;
i {
margin-right: 0;
}
}
.cp-form-creator-inline-add {
font-size: 25px;
margin-right: 30px;
.add-close { display: none; }
&.displayed {
.add-close { display: inline; }
.add-open { display: none; }
}
}
.cp-form-creator-control-inline {
display: flex;
justify-content: space-around;
button:not(:last-child) {
margin-right: 5px;
}
.cp-form-creator-types {
.btn-default {
background: transparent;
&:hover, &:not(:disabled):active, &:focus {
background-color: @cp_buttons-default;
}
}
button {
border: 0px;
//padding-bottom: 3px;
i {
font-size: 35px;
line-height: 35px;
}
}
&:first-child {
margin-right: 50px;
}
}
}
}
.cp-form-creator-add-full {
display: flex;
align-items: center;
margin: 50px 0px 100px 0px;
&> div:first-child {
border-right: 1px solid fade(@cryptpad_text_col, 25%);
display: flex;
height: 100%;
align-items: center;
padding-right: 10px;
margin-right: 10px;
i {
color: fade(@cryptpad_text_col, 25%);
font-size: 30px;
}
}
.cp-form-creator-control-inline {
display: flex;
flex-flow: column;
justify-content: space-around;
button:not(:last-child) {
margin-right: 5px;
}
.cp-form-creator-types {
.btn-default {
background: transparent;
&:hover, &:not(:disabled):active, &:focus {
background-color: @cp_buttons-default;
}
}
button {
border: 0px;
padding:5px;
margin-right: 10px;
i {
font-size: 35px;
line-height: 35px;
}
}
&:first-child {
margin-bottom: 20px;
margin-right: 50px;
}
}
}
}
.cp-form-page + .cp-form-send-container {
margin-top: 10px;
}
.cp-form-page-container {
display: flex;
justify-content: center;
margin: 10px 0;
& > span {
margin: 0 20px;
width: 100px;
display: inline-flex;
align-items: center;
justify-content: center;
}
button {
&.cp-next {
.fa {
margin-right: 0;
margin-left: 5px;
}
}
}
}
.cp-form-block {
.tools_unselectable();
background: @cp_form-bg1;
padding: 10px;
&:not(:last-child) {
margin-bottom: 20px;
}
.cp-form-block-drag-handle {
display: flex;
flex-flow: column;
align-items: center;
color: @cp_sidebar-hint;
i {
cursor: grab;
&:first-child {
height: 3px;
margin-top: -10px;
margin-bottom: 1px;
}
}
}
&.sortable-ghost { visibility: hidden; }
&.sortable-drag { opacity: 0.9 !important; }
.cp-form-block-question {
margin-bottom: 5px;
}
.cp-form-block-content {
overflow-x: auto;
.cp-form-page-break-edit {
text-align: center;
padding: 10px;
i {
margin-right: 5px;
}
}
.cp-form-edit-buttons-container {
margin-top: 20px;
display: flex;
justify-content: space-between;
}
input:invalid {
border: 1px solid @cp_form-invalid;
}
}
.cp-form-input-block {
//width: @form_input-width;
padding-bottom: 10px;
border-bottom: 2px solid @cp_sidebar-hint;
margin-bottom: 10px;
&:not(.editing) {
input {
background: transparent;
border: none;
padding: 0 !important;
& ~ button:not(:disabled) {
.cp-form-edit { display: inline; }
.cp-form-save { display: none; }
}
}
}
input {
flex: 1;
min-width: 100px;
padding: 0 10px !important;
height: auto;
font-size: 20px;
}
button {
.cp-form-edit {
display: none;
}
.cp-form-save { display: inline; }
}
.cp-form-block-drag {
font-size: 22px;
width: 20px;
margin-left: 5px;
text-align: center;
line-height: 31px;
}
}
&.editable {
cursor: grab;
.cp-form-edit-save {
margin-top: 20px;
button {
margin-right: 10px;
}
}
.cp-form-edit-type {
margin-bottom: 10px;
.cp-dropdown-container {
margin-left: 10px;
}
}
}
}
.cp-form-edit-max-options {
display: flex;
align-items: center;
margin-bottom: 10px;
input {
width: 100px;
margin-left: 10px;
}
}
.cp-form-edit-options-block {
display: flex;
flex-wrap: wrap;
align-items: baseline;
.CodeMirror {
cursor: default;
flex: 1;
margin: auto;
min-width: 80%;
width: 80%;
min-height: 200px;
height: 200px;
border: 1px solid @cp_forms-border;
.CodeMirror-placeholder {
color: #777;
}
}
}
.cp-form-edit-block {
button.btn-secondary {
margin-left: 30px;
}
.cp-form-handle {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
color: @cp_sidebar-hint;
i:first-child {
margin-right: 1px;
}
}
.cp-form-edit-block-input {
margin-bottom: 5px; // XXX DB margin bug
&.sortable-ghost { visibility: hidden; }
&.sortable-drag { opacity: 0.9 !important; }
display: flex;
width: 400px;
input {
flex: 1;
min-width: 100px;
border-color: @cryptpad_text_col;
border-right: 0px;
}
button {
i { margin: 0 !important; }
}
}
}
}
}
div.cp-form-creator-results {
display: flex;
flex-flow: column;
position: relative;
& > div {
background: @cp_form-bg1;
padding: 10px;
&:not(:last-child) {
margin-bottom: 20px;
}
}
.cp-form-block-question {
margin-bottom: 5px;
}
.cp-form-block-type {
float: right;
padding: 5px;
margin-top: -10px;
margin-right: -10px;
i { margin-right: 5px; }
background: @cp_form-bg2;
}
.cp-form-results-type-text {
max-height: 300px;
overflow: auto;
.cp-form-results-type-text-data {
padding: 5px 10px;
background: @cp_form-bg2;
&:not(:last-child) { margin-bottom: 1px; }
}
}
.cp-form-results-type-textarea-data {
white-space: pre-wrap;
font-size: 14px;
border: 1px solid @cp_profile-hint;
padding: 0 5px;
}
.cp-form-results-type-radio {
display: table;
.cp-form-results-type-multiradio-data {
display: flex;
flex-flow: column;
}
.cp-form-results-type-radio-data {
display: table-row;
border: 1px solid @cp_form-border;
& > span {
border: 1px solid @cp_form-border;
display: table-cell;
padding: 5px 10px;
background: @cp_form-bg2;
&.cp-value {
min-width: 200px;
}
}
}
}
.cp-form-individual {
& > *:not(:last-child) {
margin-right: 10px;
}
.cp-form-warning {
color: @cp-limit-bar-warning;
}
.cp-form-friend {
color: @cp_profile-hint;
.fa {
margin-right: 5px;
}
}
}
}
}
.cp-form-type-radio, .cp-form-type-checkbox {
display: flex;
flex-flow: column;
align-items: baseline;
.cp-radio {
display: inline-flex;
}
}
.cp-form-type-multiradio {
display: table;
& > * {
display: table-row;
& > * {
display: table-cell;
padding: 5px 20px;
vertical-align: middle;
&:first-child {
min-width: 200px;
}
.cp-radio-mark {
margin: auto;
}
}
}
}
.cp-form-type-sort {
cursor: grab;
padding: 2px;
.cp-form-handle {
margin-right: 5px;
}
.cp-form-sort-order {
border: 1px solid @cp_profile-hint;
padding: 0 5px;
margin-right: 5px;
}
}
.cp-form-type-poll {
display: inline-flex;
flex-flow: column;
& > div {
display: flex;
}
.cp-poll-cell {
width: 100px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
&:first-child {
width: 200px;
}
button {
width: 100%;
}
}
.cp-poll-time-day {
flex-basis: 100px;
border-right: 1px solid @cryptpad_text_col;
border-left: 1px solid @cryptpad_text_col;
border-top: 1px solid @cryptpad_text_col;
}
&.cp-form-poll-switch {
flex-flow: row;
& > div {
flex-flow: column;
}
.cp-poll-cell:not(.cp-poll-switch) {
&:first-child {
width: 100px;
}
}
.cp-form-poll-option, .cp-poll-switch {
width: 200px;
}
.cp-poll-time-day {
flex-basis: 40px;
border-right: none;
border-bottom: 1px solid @cryptpad_text_col;
border-left: 1px solid @cryptpad_text_col;
border-top: 1px solid @cryptpad_text_col;
}
}
.cp-form-poll-choice, .cp-form-poll-answer {
.fa {
display: none;
}
color: @cp_form-poll-color;
&[data-value="0"] {
background: @cp_form-poll-no;
.cp-no { display: inline; }
}
&[data-value="1"] {
background: @cp_form-poll-yes;
.cp-yes { display: inline; }
}
&[data-value="2"] {
background: @cp_form-poll-maybe;
.cp-maybe { display: inline; }
}
}
div.cp-form-poll-choice {
cursor: pointer;
padding: 5px;
border: 5px double @cp_form-bg1;
}
div.cp-form-poll-answer {
color: @cp_form-poll-yes-color;
}
}
}

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
</head>
<body>
<iframe-placeholder>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/form/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>
</head>
<body class="cp-app-form">
<div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-app-form-editor">
<div id="cp-app-form-container"></div>
</div>
<noscript>
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
</noscript>
</body>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,358 @@
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/sframe-common-outer.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (nThen, ApiConfig, DomReady, SFCommonO) {
var Nacl = window.nacl;
var href, hash;
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
var privateKey, publicKey;
var channels = {};
var getPropChannels = function () {
return channels;
};
var addData = function (meta, CryptPad, user, Utils) {
var keys = Utils.secret && Utils.secret.keys;
var parsed = Utils.Hash.parseTypeHash('pad', hash.slice(1));
if (parsed && parsed.auditorKey) {
meta.form_auditorKey = parsed.auditorKey;
meta.form_auditorHash = hash;
}
var secondary = keys && keys.secondaryKey;
if (!secondary) { return; }
var curvePair = Nacl.box.keyPair.fromSecretKey(Nacl.util.decodeUTF8(secondary).slice(0,32));
var validateKey = keys.secondaryValidateKey;
meta.form_answerValidateKey = validateKey;
publicKey = meta.form_public = Nacl.util.encodeBase64(curvePair.publicKey);
privateKey = meta.form_private = Nacl.util.encodeBase64(curvePair.secretKey);
var auditorHash = Utils.Hash.getViewHashFromKeys({
version: 1,
channel: Utils.secret.channel,
keys: { viewKeyStr: Nacl.util.encodeBase64(keys.cryptKey) }
});
var _parsed = Utils.Hash.parseTypeHash('pad', auditorHash);
meta.form_auditorHash = _parsed.getHash({auditorKey: privateKey});
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('EV_FORM_PIN', function (data) {
channels.answersChannel = data.channel;
Cryptpad.getPadAttribute('answersChannel', function (err, res) {
// If already stored, don't pin it again
if (res && res === data.channel) { return; }
Cryptpad.pinPads([data.channel], function () {
Cryptpad.setPadAttribute('answersChannel', data.channel, function () {});
});
});
});
var getAnonymousKeys = function (formSeed, channel) {
var array = Nacl.util.decodeBase64(formSeed + channel);
var hash = Nacl.hash(array);
var secretKey = Nacl.util.encodeBase64(hash.subarray(32));
var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey);
return {
curvePrivate: secretKey,
curvePublic: publicKey,
};
};
var u8_slice = function (A, start, end) {
return new Uint8Array(Array.prototype.slice.call(A, start, end));
};
var u8_concat = function (A) {
var length = 0;
A.forEach(function (a) { length += a.length; });
var total = new Uint8Array(length);
var offset = 0;
A.forEach(function (a) {
total.set(a, offset);
offset += a.length;
});
return total;
};
var anonProof = function (channel, theirPub, anonKeys) {
var u8_plain = Nacl.util.decodeUTF8(channel);
var u8_nonce = Nacl.randomBytes(Nacl.box.nonceLength);
var u8_cipher = Nacl.box(
u8_plain,
u8_nonce,
Nacl.util.decodeBase64(theirPub),
Nacl.util.decodeBase64(anonKeys.curvePrivate)
);
var u8_bundle = u8_concat([
u8_nonce, // 24 uint8s
u8_cipher, // arbitrary length
]);
return {
key: anonKeys.curvePublic,
proof: Nacl.util.encodeBase64(u8_bundle)
};
};
var checkAnonProof = function (proofObj, channel, curvePrivate) {
var pub = proofObj.key;
var proofTxt = proofObj.proof;
try {
var u8_bundle = Nacl.util.decodeBase64(proofTxt);
var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength);
var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength);
var u8_plain = Nacl.box.open(
u8_cipher,
u8_nonce,
Nacl.util.decodeBase64(pub),
Nacl.util.decodeBase64(curvePrivate)
);
return channel === Nacl.util.encodeUTF8(u8_plain);
} catch (e) {
console.error(e);
return false;
}
};
sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) {
var cb = Utils.Util.once(_cb);
var myKeys = {};
var myFormKeys;
var accessKeys;
var CPNetflux, Pinpad;
var network;
var noDriveAnswered = false;
nThen(function (w) {
require([
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/pinpad.js',
], w(function (_CPNetflux, _Pinpad) {
CPNetflux = _CPNetflux;
Pinpad = _Pinpad;
}));
Cryptpad.getAccessKeys(w(function (_keys) {
if (!Array.isArray(_keys)) { return; }
accessKeys = _keys;
_keys.some(function (_k) {
if ((!Cryptpad.initialTeam && !_k.id) || Cryptpad.initialTeam === _k.id) {
myKeys = _k;
return true;
}
});
}));
Cryptpad.getFormKeys(w(function (keys) {
if (!keys.curvePublic && !keys.formSeed) {
// No drive mode
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
noDriveAnswered = answered.indexOf(data.channel) !== -1;
}
myFormKeys = keys;
}));
Cryptpad.makeNetwork(w(function (err, nw) {
network = nw;
}));
}).nThen(function () {
if (!network) { return void cb({error: "E_CONNECT"}); }
if (myFormKeys.formSeed) {
myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel);
}
var keys = Utils.secret && Utils.secret.keys;
var curvePrivate = privateKey || data.privateKey;
var crypto = Utils.Crypto.Mailbox.createEncryptor({
curvePrivate: curvePrivate,
curvePublic: publicKey || data.publicKey,
validateKey: data.validateKey
});
var config = {
network: network,
channel: data.channel,
noChainPad: true,
validateKey: keys.secondaryValidateKey,
owners: [myKeys.edPublic],
crypto: crypto,
// XXX Cache
};
var results = {};
config.onError = function (info) {
cb({ error: info.type });
};
config.onRejected = function (data, cb) {
if (!Array.isArray(data) || !data.length || data[0].length !== 16) {
return void cb(true);
}
if (!Array.isArray(accessKeys)) { return void cb(true); }
network.historyKeeper = data[0];
nThen(function (waitFor) {
accessKeys.forEach(function (obj) {
Pinpad.create(network, obj, waitFor(function (e) {
console.log('done', obj);
if (e) { console.error(e); }
}));
});
}).nThen(function () {
cb();
});
};
config.onReady = function () {
var myKey;
// If we have submitted an anonymous answer, retrieve it
if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) {
myKey = myFormKeys.curvePublic;
}
cb({
noDriveAnswered: noDriveAnswered,
myKey: myKey,
results: results
});
network.disconnect();
};
config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) {
var parsed = Utils.Util.tryParse(msg);
if (!parsed) { return; }
if (parsed._proof) {
var check = checkAnonProof(parsed._proof, data.channel, curvePrivate);
if (check) {
delete results[parsed._proof.key];
}
}
results[senderCurve] = {
msg: parsed,
hash: hash,
time: cfg.time
};
};
CPNetflux.start(config);
});
});
sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) {
var answer;
var myKeys;
nThen(function (w) {
Cryptpad.getFormKeys(w(function (keys) {
myKeys = keys;
}));
Cryptpad.getFormAnswer({channel: data.channel}, w(function (obj) {
if (!obj || obj.error) {
if (obj && obj.error === "ENODRIVE") {
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
if (answered.indexOf(data.channel) !== -1) {
cb({error:'EANSWERED'});
} else {
cb();
}
return void w.abort();
}
w.abort();
return void cb(obj);
}
answer = obj;
}));
}).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;
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)
});
res.content._isAnon = answer.anonymous;
cb(JSON.parse(res.content));
});
});
});
var noDriveSeed = Utils.Hash.createChannelId();
sframeChan.on("Q_FORM_SUBMIT", function (data, cb) {
var box = data.mailbox;
var myKeys;
nThen(function (w) {
Cryptpad.getFormKeys(w(function (keys) {
// If formSeed doesn't exists, it means we're probably in noDrive mode.
// We can create a seed in localStorage.
if (!keys.formSeed) {
// No drive mode
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
if(answered.indexOf(data.channel) !== -1) {
// Already answered: abort
return void cb({ error: "EANSWERED" });
}
keys = { formSeed: noDriveSeed };
}
myKeys = keys;
}));
}).nThen(function () {
var myAnonymousKeys;
if (data.anonymous) {
if (!myKeys.formSeed) { return void cb({ error: "ANONYMOUS_ERROR" }); }
myKeys = getAnonymousKeys(myKeys.formSeed, box.channel);
} else {
myAnonymousKeys = getAnonymousKeys(myKeys.formSeed, box.channel);
}
var keys = Utils.secret && Utils.secret.keys;
myKeys.signingKey = keys.secondarySignKey;
var ephemeral_keypair = Nacl.box.keyPair();
var ephemeral_private = Nacl.util.encodeBase64(ephemeral_keypair.secretKey);
myKeys.ephemeral_keypair = ephemeral_keypair;
if (myAnonymousKeys) {
var proof = anonProof(box.channel, box.publicKey, myAnonymousKeys);
data.results._proof = proof;
}
var crypto = Utils.Crypto.Mailbox.createEncryptor(myKeys);
var text = JSON.stringify(data.results);
var ciphertext = crypto.encrypt(text, box.publicKey);
var hash = ciphertext.slice(0,64);
Cryptpad.anonRpcMsg("WRITE_PRIVATE_MESSAGE", [
box.channel,
ciphertext
], function (err, response) {
Cryptpad.storeFormAnswer({
channel: box.channel,
hash: hash,
curvePrivate: ephemeral_private,
anonymous: Boolean(data.anonymous)
});
cb({error: err, response: response, hash: hash});
});
});
});
};
SFCommonO.start({
addData: addData,
addRpc: addRpc,
cache: true,
noDrive: true,
hash: hash,
href: href,
useCreationScreen: true,
messaging: true,
getPropChannels: getPropChannels
});
});
});

@ -241,7 +241,12 @@ define([
e.stopPropagation(); e.stopPropagation();
}); });
var common = framework._.sfCommon; var common = framework._.sfCommon;
var markdownTb = common.createMarkdownToolbar(editor); var markdownTb = common.createMarkdownToolbar(editor, {
embed: function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML);
}
});
$(text).before(markdownTb.toolbar); $(text).before(markdownTb.toolbar);
$(markdownTb.toolbar).show(); $(markdownTb.toolbar).show();
editor.refresh(); editor.refresh();

@ -58,6 +58,7 @@ define([
hashes: data.hashes || priv.hashes, hashes: data.hashes || priv.hashes,
common: common, common: common,
title: data.title, title: data.title,
auditorHash: data.auditorHash,
versionHash: data.versionHash, versionHash: data.versionHash,
friends: friends, friends: friends,
onClose: function () { onClose: function () {

Loading…
Cancel
Save