From 8eb5c3e6dbe822347181ad8ef105d6858f1dc51a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 4 Jun 2021 16:21:54 +0200 Subject: [PATCH] Add sorted list, textarea and typed input --- .../src/less2/include/colortheme-dark.less | 1 + .../src/less2/include/colortheme.less | 3 +- www/form/app-form.less | 23 +- www/form/inner.js | 306 ++++++++++++++++-- 4 files changed, 313 insertions(+), 20 deletions(-) diff --git a/customize.dist/src/less2/include/colortheme-dark.less b/customize.dist/src/less2/include/colortheme-dark.less index 8dbe77271..5c3091b6b 100644 --- a/customize.dist/src/less2/include/colortheme-dark.less +++ b/customize.dist/src/less2/include/colortheme-dark.less @@ -436,3 +436,4 @@ @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; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index e68da8835..1102f6074 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -435,4 +435,5 @@ @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-poll-yes-color: @cryptpad_color_green; +@cp_form-invalid: @cryptpad_color_red; diff --git a/www/form/app-form.less b/www/form/app-form.less index 484033c90..c7a8e5fea 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -203,6 +203,9 @@ display: flex; justify-content: space-between; } + input:invalid { + border: 1px solid @cp_form-invalid; + } } .cp-form-input-block { //width: @form_input-width; @@ -328,6 +331,12 @@ &: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 { @@ -390,6 +399,18 @@ } } } + .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; @@ -461,7 +482,7 @@ border: 5px double @cp_form-bg1; } div.cp-form-poll-answer { - color: @cp_form_poll-yes-color; + color: @cp_form-poll-yes-color; } } diff --git a/www/form/inner.js b/www/form/inner.js index cea932e3f..216d6ac63 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -80,14 +80,23 @@ define([ Messages.form_invalid = "Invalid form"; Messages.form_editBlock = "Edit"; Messages.form_editMax = "Max selectable options"; + Messages.form_editMaxLength = "Maximum characters"; Messages.form_editType = "Options type"; Messages.form_poll_text = "Text"; Messages.form_poll_day = "Day"; Messages.form_poll_time = "Time"; + + Messages.form_textType = "Text type"; + Messages.form_text_text = "Text"; + Messages.form_text_url = "URL"; + Messages.form_text_email = "Email"; + Messages.form_text_number = "Number"; + Messages.form_default = "Your question here?"; Messages.form_type_input = "Text"; // XXX + Messages.form_type_textarea = "Textarea"; // XXX Messages.form_type_radio = "Radio"; // XXX Messages.form_type_multiradio = "Multiline Radio"; // XXX Messages.form_type_checkbox = "Checkbox"; // XXX @@ -161,6 +170,109 @@ define([ var MAX_OPTIONS = 15; // XXX var MAX_ITEMS = 10; // XXX + var saveAndCancelOptions = function (getRes, cb) { + // Cancel changes + var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); + $(cancelBlock).click(function () { cb(); }); + + // Save changes + var saveBlock = h('button.btn.btn-primary', [ + h('i.fa.fa-floppy-o'), + h('span', Messages.settings_save) + ]); + + $(saveBlock).click(function () { + $(saveBlock).attr('disabled', 'disabled'); + cb(getRes()); + }); + + return h('div', [cancelBlock, saveBlock]); + }; + var editTextOptions = function (opts, setCursorGetter, cb, tmp) { + if (tmp && tmp.content && Sortify(opts) === Sortify(tmp.old)) { + opts = tmp.content; + } + + var maxLength, getLengthVal; + if (opts.maxLength) { + var lengthInput = h('input', { + type:"number", + value: opts.maxLength, + min: 100, + max: 5000 + }); + maxLength = h('div.cp-form-edit-max-options', [ + h('span', Messages.form_editMaxLength), + lengthInput + ]); + getLengthVal = function () { + var val = Number($(lengthInput).val()) || 1000; + if (val < 100) { val = 1; } + if (val > 5000) { val = 5000; } + return val; + }; + + var $l = $(lengthInput).on('input', Util.throttle(function () { + $l.val(getLengthVal()); + }, 500)); + + } + + var type, typeSelect; + if (opts.type) { + var options = ['text', 'number', 'url', 'email'].map(function (t) { + return { + tag: 'a', + attributes: { + 'class': 'cp-form-type-value', + 'data-value': t, + 'href': '#', + }, + content: Messages['form_text_'+t] + }; + }); + var dropdownConfig = { + text: '', // Button initial text + options: options, // Entries displayed in the menu + //left: true, // Open to the left of the button + //container: $(type), + isSelect: true, + caretDown: true, + buttonCls: 'btn btn-secondary' + }; + typeSelect = UIElements.createDropdown(dropdownConfig); + typeSelect.setValue(opts.type); + + type = h('div.cp-form-edit-type', [ + h('span', Messages.form_textType), + typeSelect[0] + ]); + } + + setCursorGetter(function () { + return { + old: (tmp && tmp.old) || opts, + content: { + maxLength: getLengthVal ? getLengthVal() : undefined, + type: typeSelect ? typeSelect.getValue() : undefined + } + }; + }); + + var getSaveRes = function () { + return { + maxLength: getLengthVal ? getLengthVal() : undefined, + type: typeSelect ? typeSelect.getValue() : undefined + }; + }; + var saveAndCancel = saveAndCancelOptions(getSaveRes, cb); + + return [ + maxLength, + type, + saveAndCancel + ]; + }; var editOptions = function (v, setCursorGetter, cb, tmp) { var add = h('button.btn.btn-secondary', [ h('i.fa.fa-plus'), @@ -415,10 +527,6 @@ define([ if ($container.find('input').length >= MAX_OPTIONS) { $add.hide(); } if ($(containerItems).find('input').length >= MAX_ITEMS) { $addItem.hide(); } - // Cancel changes - var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); - $(cancelBlock).click(function () { cb(); }); - // Set cursor getter (to handle remote changes to the form) setCursorGetter(function () { var values = []; @@ -471,14 +579,7 @@ define([ }; }); - // Save changes - var saveBlock = h('button.btn.btn-primary', [ - h('i.fa.fa-floppy-o'), - h('span', Messages.settings_save) - ]); - $(saveBlock).click(function () { - $(saveBlock).attr('disabled', 'disabled'); - + var getSaveRes = function () { // Get values var values = []; var duplicates = false; @@ -538,8 +639,10 @@ define([ res.type = typeSelect.getValue(); } - cb(res); - }); + return res; + }; + + var saveAndCancel = saveAndCancelOptions(getSaveRes, cb); return [ type, @@ -547,7 +650,7 @@ define([ calendarView, h('div.cp-form-edit-options-block', [containerItems, container]), addMultiple, - h('div', [cancelBlock, saveBlock]) + saveAndCancel ]; }; @@ -767,16 +870,33 @@ define([ }; var TYPES = { input: { + defaultOpts: { + type: 'text' + }, get: function (opts, a, n, evOnChange) { - var tag = h('input'); + if (!opts) { opts = TYPES.input.defaultOpts; } + var tag = h('input', { + type: opts.type + }); var $tag = $(tag); $tag.on('change keypress', Util.throttle(function () { evOnChange.fire(); }, 500)); + var cursorGetter; + var setCursorGetter = function (f) { cursorGetter = f; }; return { tag: tag, - getValue: function () { return $tag.val(); }, + getValue: function () { + var invalid = $tag.is(':invalid'); + if (invalid) { return; } // XXX invalid answers are ignored? + return $tag.val(); + }, setValue: function (val) { $tag.val(val); }, + edit: function (cb, tmp) { + var v = Util.clone(opts); + return editTextOptions(v, setCursorGetter, cb, tmp); + }, + getCursor: function () { return cursorGetter(); }, reset: function () { $tag.val(''); } }; }, @@ -795,6 +915,46 @@ define([ }, icon: h('i.fa.fa-font') }, + textarea: { + defaultOpts: { + maxLength: 1000 + }, + get: function (opts, a, n, evOnChange) { + if (!opts) { opts = TYPES.textarea.defaultOpts; } + var tag = h('textarea', {maxlength: opts.maxLength}); + var $tag = $(tag); + $tag.on('change keypress', Util.throttle(function () { + evOnChange.fire(); + }, 500)); + var cursorGetter; + var setCursorGetter = function (f) { cursorGetter = f; }; + return { + tag: tag, + getValue: function () { return $tag.val(); }, + setValue: function (val) { $tag.val(val); }, + edit: function (cb, tmp) { + var v = Util.clone(opts); + return editTextOptions(v, setCursorGetter, cb, tmp); + }, + getCursor: function () { return cursorGetter(); }, + reset: function () { $tag.val(''); } + }; + }, + printResults: function (answers, uid) { + var results = []; + var empty = 0; + Object.keys(answers).forEach(function (author) { + var obj = answers[author]; + var answer = obj.msg[uid]; + if (!answer || !answer.trim()) { return empty++; } + results.push(h('div.cp-form-results-type-textarea-data', answer)); + }); + results.push(getEmpty(empty)); + + return h('div.cp-form-results-type-text', results); + }, + icon: h('i.fa.fa-font') + }, radio: { defaultOpts: { values: [1,2].map(function (i) { @@ -1285,6 +1445,116 @@ define([ }, icon: h('i.cptools.cptools-poll') }, + sort: { + defaultOpts: { + values: [1,2].map(function (i) { + return Messages._getKey('form_defaultOption', [i]); + }) + }, + get: function (opts, a, n, evOnChange) { + if (!opts) { opts = TYPES.radio.defaultOpts; } + if (!Array.isArray(opts.values)) { return; } + var map = {}; + var invMap = {}; + var els = opts.values.map(function (data, i) { + var uid = Util.uid(); + map[uid] = data; + invMap[data] = uid; + var div = h('div.cp-form-type-sort', {'data-id': uid}, [ + h('span.cp-form-handle', [ + h('i.fa.fa-ellipsis-v'), + h('i.fa.fa-ellipsis-v'), + ]), + h('span.cp-form-sort-order', (i+1)), + h('span', data) + ]); + $(div).data('val', data); + return div; + }); + var tag = h('div.cp-form-type-sort-container', els); + var $tag = $(tag); + var reorder = function () { + $tag.find('.cp-form-type-sort').each(function (i, el) { + $(el).find('.cp-form-sort-order').text(i+1); + }); + }; + var cursorGetter; + var setCursorGetter = function (f) { cursorGetter = f; }; + + var sortable = Sortable.create(tag, { + direction: "vertical", + draggable: ".cp-form-type-sort", + forceFallback: true, + store: { + set: function () { + evOnChange.fire(); + reorder(); + } + } + }); + + $(tag).find('input[type="radio"]').on('change', function () { + evOnChange.fire(); + }); + return { + tag: tag, + getValue: function () { + return sortable.toArray().map(function (id) { + return map[id]; + }); + }, + reset: function () { + var toSort = (opts.values).map(function (val) { + return invMap[val]; + }); + sortable.sort(toSort); + reorder(); + }, + edit: function (cb, tmp) { + var v = Util.clone(opts); + return editOptions(v, setCursorGetter, cb, tmp); + }, + getCursor: function () { return cursorGetter(); }, + setValue: function (val) { + var toSort = (val || []).map(function (val) { + return invMap[val]; + }); + sortable.sort(toSort); + reorder(); + } + }; + + }, + printResults: function (answers, uid, form) { + var opts = form[uid].opts || TYPES.radio.defaultOpts; + var l = (opts.values || []).length; + var results = []; + var empty = 0; + var count = {}; + Object.keys(answers).forEach(function (author) { + var obj = answers[author]; + var answer = obj.msg[uid]; + if (!Array.isArray(answer) || !answer.length) { return empty++; } + answer.forEach(function (el, i) { + var score = l - i; + count[el] = (count[el] || 0) + score; + }); + }); + var sorted = Object.keys(count).sort(function (a, b) { + return count[b] - count[a]; + }); + sorted.forEach(function (value) { + results.push(h('div.cp-form-results-type-radio-data', [ + h('span.cp-value', value), + h('span.cp-count', count[value]) + ])); + }); + results.push(getEmpty(empty)); + + return h('div.cp-form-results-type-radio', results); + }, + icon: h('i.fa.fa-list-ul') + }, }; var renderResults = function (content, answers) { @@ -1848,7 +2118,7 @@ define([ if (editable) { Sortable.create($container[0], { direction: "vertical", - filter: "input, button, .CodeMirror", + filter: "input, button, .CodeMirror, .cp-form-type-sort", preventOnFilter: false, draggable: ".cp-form-block", forceFallback: true,