diff --git a/customize.dist/src/less2/include/colortheme-dark.less b/customize.dist/src/less2/include/colortheme-dark.less index 875ad004b..68a456bcc 100644 --- a/customize.dist/src/less2/include/colortheme-dark.less +++ b/customize.dist/src/less2/include/colortheme-dark.less @@ -428,6 +428,6 @@ @cp_calendar-now-fg: @cryptpad_color_grey_800; // Forms -@cp_forms-bg1: @cryptpad_color_grey_800; -@cp_forms-bg2: @cryptpad_color_grey_900; -@cp_forms-border: @cryptpad_color_grey_800; +@cp_form-bg1: @cryptpad_color_grey_800; +@cp_form-bg2: @cryptpad_color_grey_900; +@cp_form-border: @cryptpad_color_grey_800; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index 012a66239..c385cd83c 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -428,6 +428,6 @@ @cp_calendar-now-fg: @cryptpad_color_grey_200; // Forms -@cp_forms-bg1: @cryptpad_color_grey_200; -@cp_forms-bg2: @cryptpad_color_grey_100; -@cp_forms-border: @cryptpad_color_grey_200; +@cp_form-bg1: @cryptpad_color_grey_200; +@cp_form-bg2: @cryptpad_color_grey_100; +@cp_form-border: @cryptpad_color_grey_200; diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index ffe061fa3..4fb799e13 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -71,6 +71,12 @@ div.cp-button-confirm { display: inline-block; + &.new { + vertical-align: top; + button { + height: 35px; + } + } button { margin: 0 !important; } @@ -85,7 +91,7 @@ } } } - button.cp-button-confirm-placeholder { + button.cp-button-confirm-placeholder:not(.new) { margin-bottom: 3px !important; } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index dbcb089f3..bba514dbf 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -747,6 +747,7 @@ define([ cb = Util.once(cb); } var classes = 'btn ' + (config.classes || 'btn-primary'); + var newCls = config.new ? '.new' : ''; var button = h('button', { "class": classes, @@ -759,7 +760,7 @@ define([ }); var timer = h('div.cp-button-timer', div); - var content = h('div.cp-button-confirm', [ + var content = h('div.cp-button-confirm'+newCls, [ button, timer ]); @@ -795,7 +796,8 @@ define([ 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(); // If we have a validation function, continue only if it's true if (config.validate && !config.validate()) { return; } diff --git a/www/form/app-form.less b/www/form/app-form.less index ee62e2c89..4ea9d3cf6 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -11,6 +11,8 @@ display: flex; flex-flow: column; + font: @colortheme_app-font; + color: @cryptpad_text_col; #cp-app-form-editor { flex: 1; @@ -56,17 +58,26 @@ display: flex; flex-flow: column; flex: 1; + overflow: auto; + .cp-form-block { + .tools_unselectable(); + background: @cp_form-bg1; + padding: 10px; &:not(:last-child) { margin-bottom: 20px; } + .cp-form-block-question { + margin-bottom: 5px; + } .cp-form-input-block { display: flex; //width: @form_input-width; - &:not(:focus-within) { + &:not(.editing) { input { background: transparent; border: none; + padding: 0 !important; & ~ button:not(:disabled) { .cp-form-edit { display: inline; } .cp-form-save { display: none; } @@ -76,15 +87,24 @@ input { flex: 1; min-width: 100px; + padding: 0 10px !important; + height: auto; } button { .cp-form-edit { display: none; - margin: 0 !important; } .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-block { .cp-form-edit-block-input { @@ -107,7 +127,7 @@ flex-flow: column; position: relative; & > div { - background: @cp_forms-bg1; + background: @cp_form-bg1; padding: 10px; &:not(:last-child) { margin-bottom: 20px; @@ -122,14 +142,14 @@ margin-top: -10px; margin-right: -10px; i { margin-right: 5px; } - background: @cp_forms-bg2; + 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_forms-bg2; + background: @cp_form-bg2; &:not(:last-child) { margin-bottom: 1px; } } } @@ -137,12 +157,12 @@ display: table; .cp-form-results-type-radio-data { display: table-row; - border: 1px solid @cp_forms-border; + border: 1px solid @cp_form-border; & > span { - border: 1px solid @cp_forms-border; + border: 1px solid @cp_form-border; display: table-cell; padding: 5px 10px; - background: @cp_forms-bg2; + background: @cp_form-bg2; &.cp-value { min-width: 200px; } @@ -152,5 +172,14 @@ } } + .cp-form-type-radio { + display: flex; + flex-flow: column; + align-items: baseline; + .cp-radio { + display: inline-flex; + } + } + } diff --git a/www/form/inner.js b/www/form/inner.js index 246d8149f..b65e30278 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -20,6 +20,8 @@ define([ '/common/inner/access.js', '/common/inner/properties.js', + '/bower_components/sortablejs/Sortable.min.js', + '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/form/app-form.less', @@ -40,7 +42,8 @@ define([ h, Messages, AppConfig, - Share, Access, Properties + Share, Access, Properties, + Sortable ) { var SaveAs = window.saveAs; @@ -50,6 +53,7 @@ define([ Messages.button_newform = "New Form"; // XXX Messages.form_invalid = "Invalid form"; Messages.form_editBlock = "Edit options"; + Messages.form_editQuestion = "Edit question"; Messages.form_newOption = "New option"; @@ -63,6 +67,7 @@ define([ Messages.form_update = "Update"; Messages.form_reset = "Reset"; Messages.form_sent = "Sent"; + Messages.form_delete = "Delete block"; Messages.form_cantFindAnswers = "Unable to retrieve your existing answers for this form."; @@ -170,7 +175,7 @@ define([ $(radio).find('input').data('val', data); return radio; }); - var tag = h('div.radio-group', els); + var tag = h('div.radio-group.cp-form-type-radio', els); return { tag: tag, getValue: function () { @@ -266,7 +271,7 @@ define([ APP.formBlocks = []; // XXX order array later - var elements = Object.keys(form).map(function (uid) { + var elements = content.order.map(function (uid) { var block = form[uid]; var type = block.type; var model = TYPES[type]; @@ -277,7 +282,7 @@ define([ if (answers && answers[uid]) { data.setValue(answers[uid]); } var q = h('div.cp-form-block-question', block.q || Messages.form_default); - var edit, editContainer; + var editButtons, editContainer; APP.formBlocks.push(data); @@ -288,32 +293,64 @@ define([ value: block.q || Messages.form_default }); var $inputQ = $(inputQ); - var saveQ = h('button.btn.btn-primary', [ + var saveQ = h('button.btn.btn-primary.small', [ h('i.fa.fa-pencil.cp-form-edit'), + h('span.cp-form-edit', Messages.form_editQuestion), + h('i.fa.fa-floppy-o.cp-form-save'), h('span.cp-form-save', Messages.settings_save) ]); + var dragHandle = h('i.fa.fa-arrows-v.cp-form-block-drag'); + var $saveQ = $(saveQ).click(function () { + if (!$(q).hasClass('editing')) { + $(q).addClass('editing'); + $inputQ.focus(); + return; + } var v = $inputQ.val(); - if (!v || !v.trim() || v === block.q) { return; } + if (!v || !v.trim()) { return void UI.warn(Messages.error); } block.q = v.trim(); framework.localChange(); $saveQ.attr('disabled', 'disabled'); framework._.cpNfInner.chainpad.onSettle(function () { + $(q).removeClass('editing'); $saveQ.removeAttr('disabled'); - $saveQ.blur(); + $inputQ.blur(); UI.log(Messages.saved); }); }); - var onBlur = function (e) { + var onCancelQ = function (e) { if (e && e.relatedTarget && e.relatedTarget === saveQ) { return; } - $inputQ.val(block.q); + $inputQ.val(block.q || Messages.form_default); + if (!e) { $inputQ.blur(); } + $(q).removeClass('editing'); }; $inputQ.keydown(function (e) { if (e.which === 13) { return void $saveQ.click(); } - if (e.which === 27) { return void $inputQ.blur(); } + if (e.which === 27) { return void onCancelQ(); } + }); + $inputQ.focus(function () { + $(q).addClass('editing'); + }); + $inputQ.blur(onCancelQ); + q = h('div.cp-form-input-block', [inputQ, saveQ, dragHandle]); + + // Delete question + var edit; + var del = h('button.btn.btn-danger', [ + h('i.fa.fa-trash-o'), + h('span', Messages.form_delete) + ]); + UI.confirmButton(del, { + classes: 'btn-danger', + new: true + }, function () { + delete content.form[uid]; + var idx = content.order.indexOf(uid); + content.order.splice(idx, 1); + $('.cp-form-block[data-id="'+uid+'"]').remove(); + framework.localChange(); }); - $inputQ.blur(onBlur); - q = h('div.cp-form-input-block', [inputQ, saveQ]); // Values if (data.edit) { @@ -325,7 +362,7 @@ define([ var onSave = function (newOpts) { if (!newOpts) { // Cancel edit $(editContainer).empty(); - $edit.show(); + $(editButtons).show(); $(data.tag).show(); return; } @@ -334,37 +371,62 @@ define([ framework.localChange(); var $oldTag = $(data.tag); framework._.cpNfInner.chainpad.onSettle(function () { - $edit.show(); + $(editButtons).show(); UI.log(Messages.saved); data = model.get(newOpts); $oldTag.before(data.tag).remove(); }); }; - var $edit = $(edit).click(function () { + $(edit).click(function () { $(data.tag).hide(); $(editContainer).append(data.edit(onSave)); - $edit.hide(); + $(editButtons).hide(); }); } + + editButtons = h('div.cp-form-edit-buttons-container', [ + edit, del + ]); } - return h('div.cp-form-block', [ + var editableCls = editable ? ".editable" : ""; + return h('div.cp-form-block'+editableCls, { + 'data-id':uid + }, [ q, h('div.cp-form-block-content', [ data.tag, - edit + editButtons ]), editContainer ]); }); $container.empty().append(elements); + + if (editable) { + Sortable.create($container[0], { + direction: "vertical", + filter: "input, button", + preventOnFilter: false, + store: { + set: function (s) { + content.order = s.toArray(); + framework.localChange(); + } + } + }); + + return; + } + + // In view mode, add "Submit" and "reset" buttons $container.append(makeFormControls(framework, content, Boolean(answers))); }; var renderResults = function (content, answers) { var $container = $('div.cp-form-creator-results').empty(); var form = content.form; - var elements = Object.keys(form).map(function (uid) { + var elements = content.order.map(function (uid) { var block = form[uid]; var type = block.type; var model = TYPES[type]; @@ -427,6 +489,26 @@ define([ // Button to clear all answers? }; + var checkIntegrity = function (getter) { + var changed = false; + content.order.forEach(function (uid) { + if (!content.form[uid]) { + var idx = content.order.indexOf(uid); + content.order.splice(idx, 1); + changed = true; + } + }); + Object.keys(content.form).forEach(function (uid) { + var idx = content.order.indexOf(uid); + if (idx === -1) { + changed = true; + content.order.push(uid); + } + }); + + if (!getter && changed) { framework.localChange(); } + }; + var makeFormCreator = function () { var controlContainer; @@ -444,6 +526,7 @@ define([ //opts: opts type: type, }; + content.order.push(uid); framework.localChange(); updateForm(framework, content, true); }); @@ -479,6 +562,10 @@ define([ content.form = {}; framework.localChange(); } + if (!content.order) { + content.order = []; + framework.localChange(); + } if (!content.answers || !content.answers.channel || !content.answers.publicKey || !content.answers.validateKey) { content.answers = { channel: Hash.createChannelId(), @@ -502,6 +589,7 @@ define([ publicKey: content.answers.publicKey }, function (err, obj) { if (obj) { APP.answers = obj; } + checkIntegrity(false); updateForm(framework, content, true); }); @@ -518,6 +606,7 @@ define([ } var answers; if (obj && !obj.error) { answers = obj; } + checkIntegrity(false); updateForm(framework, content, false, answers); }); @@ -530,6 +619,7 @@ define([ }); framework.setContentGetter(function () { + checkIntegrity(true); return content; });