diff --git a/www/form/app-form.less b/www/form/app-form.less index e630c8ca1..d763ed204 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -344,8 +344,6 @@ color: @cp_sidebar-hint; margin-bottom: 10px; padding: 0; - text-align: center; - font-weight: bold; } .cp-form-block-drag-handle { @@ -404,6 +402,7 @@ .cp-form-required-radio { flex-direction: row; display: flex; + margin-bottom: 20px; span { margin-right: 10px; &.cp-radio-mark { diff --git a/www/form/inner.js b/www/form/inner.js index c6ec069a1..8bd1d34ec 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -19,7 +19,9 @@ define([ '/customize/application_config.js', '/common/diffMarked.js', '/common/sframe-common-codemirror.js', + '/common/text-cursor.js', 'cm/lib/codemirror', + '/bower_components/chainpad/chainpad.dist.js', '/common/inner/share.js', '/common/inner/access.js', @@ -61,7 +63,9 @@ define([ AppConfig, DiffMd, SFCodeMirror, + TextCursor, CMeditor, + ChainPad, Share, Access, Properties, Flatpickr, Sortable @@ -85,28 +89,18 @@ define([ var MAX_OPTIONS = 15; var MAX_ITEMS = 10; - var saveAndCancelOptions = function (getRes, cb) { - // Cancel changes - var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); - $(cancelBlock).click(function () { cb(); }); + var saveAndCancelOptions = function (cb) { + Messages.form_preview_button = "Preview"; // XXX + var cancelBlock = h('button.btn.btn-default.cp-form-preview-button',[ + h('i.fa.fa-eye'), + Messages.form_preview_button + ]); + $(cancelBlock).click(function () { cb(undefined, true); }); - // 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.cp-form-edit-save', [cancelBlock, saveBlock]); + return cancelBlock; }; - var editTextOptions = function (opts, setCursorGetter, cb, tmp) { - if (tmp && tmp.content && Sortify(opts) === Sortify(tmp.old)) { - opts = tmp.content; - } + var editTextOptions = function (opts, setCursorGetter, cb) { + var evOnSave = Util.mkEvent(); var maxLength, getLengthVal; if (opts.maxLength) { @@ -129,8 +123,8 @@ define([ var $l = $(lengthInput).on('input', Util.throttle(function () { $l.val(getLengthVal()); + evOnSave.fire(); }, 500)); - } var type, typeSelect; @@ -163,16 +157,11 @@ define([ h('span', Messages.form_textType), typeSelect[0] ]); + typeSelect.onChange.reg(evOnSave.fire()); } setCursorGetter(function () { - return { - old: (tmp && tmp.old) || opts, - content: { - maxLength: getLengthVal ? getLengthVal() : undefined, - type: typeSelect ? typeSelect.getValue() : undefined - } - }; + return {}; }); var getSaveRes = function () { @@ -181,7 +170,14 @@ define([ type: typeSelect ? typeSelect.getValue() : undefined }; }; - var saveAndCancel = saveAndCancelOptions(getSaveRes, cb); + + evOnSave.reg(function () { + var res = getSaveRes(); + if (!res) { return; } + cb(res); + }); + + var saveAndCancel = saveAndCancelOptions(cb); return [ maxLength, @@ -190,6 +186,8 @@ define([ ]; }; var editOptions = function (v, isDefaultOpts, setCursorGetter, cb, tmp) { + var evOnSave = Util.mkEvent(); + var add = h('button.btn.btn-secondary', [ h('i.fa.fa-plus'), h('span', Messages.form_add_option) @@ -200,11 +198,11 @@ define([ ]); var cursor; - if (tmp && tmp.content && Sortify(v) === Sortify(tmp.old)) { - v = tmp.content; + if (tmp && tmp.cursor) { cursor = tmp.cursor; } + // Checkbox: max options var maxOptions, maxInput; if (typeof(v.max) === "number") { maxInput = h('input', { @@ -217,8 +215,12 @@ define([ h('span', Messages.form_editMax), maxInput ]); + $(maxInput).on('input', function () { + setTimeout(evOnSave.fire); + }); } + // Poll: type (text/day/time) var type, typeSelect; if (v.type) { // Messages.form_poll_text.form_poll_day.form_poll_time @@ -285,6 +287,12 @@ define([ // if this element was active before the remote change, restore cursor var setCursor = function () { if (v.type && v.type !== 'text') { return; } + try { + var ops = ChainPad.Diff.diff(cursor.el, val); + ['start', 'end'].forEach(function (attr) { + cursor[attr] = TextCursor.transformCursor(cursor[attr], ops); + }); + } catch (e) { console.error(e); } input.selectionStart = cursor.start || 0; input.selectionEnd = cursor.end || 0; setTimeout(function () { input.focus(); }); @@ -321,6 +329,8 @@ define([ var currentMax = Number($maxInput.val()); $maxInput.val(Math.min(inputs, currentMax)); } + + evOnSave.fire(); }); if (!v.type || v.type === "text") { @@ -335,6 +345,10 @@ define([ }); } + $(input).on('input', function () { + evOnSave.fire(); + }); + return el; }; var inputs = v.values.map(function (val) { return getOption(val, isDefaultOpts, false); }); @@ -367,7 +381,8 @@ define([ // Calendar... var calendarView; - if (v.type) { + if (v.type) { // Polls + // Calendar inline for "day" type var calendarInput = h('input'); calendarView = h('div', calendarInput); var calendarDefault = v.type === "day" ? v.values.map(function (time) { @@ -379,12 +394,13 @@ define([ mode: 'multiple', inline: true, defaultDate: calendarDefault, - appendTo: calendarView + appendTo: calendarView, + onChange: function () { + evOnSave.fire(); + } }); - } - // Calendar time - if (v.type) { + // Calendar popup for "time" var multipleInput = h('input', {placeholder: Messages.form_addMultipleHint}); var multipleClearButton = h('button.btn', Messages.form_clear); var addMultipleButton = h('button.btn', [ @@ -416,6 +432,7 @@ define([ } }); multiplePickr.clear(); + evOnSave.fire(); }); } @@ -444,6 +461,7 @@ define([ typeSelect.onChange.reg(function (prettyVal, val) { v.type = val; refreshView(); + setTimeout(evOnSave.fire); if (val !== "text") { $container.find('.cp-form-edit-block-input').remove(); $(add).click(); @@ -481,7 +499,6 @@ define([ // Set cursor getter (to handle remote changes to the form) setCursorGetter(function () { - var values = []; var active = document.activeElement; var cursor = {}; $container.find('input').each(function (i, el) { @@ -491,44 +508,21 @@ define([ cursor.start = el.selectionStart; cursor.end = el.selectionEnd; } - values.push(val); }); - if (v.type === "day") { - var dayPickr = $(calendarView).find('input')[0]._flatpickr; - values = dayPickr.selectedDates.map(function (date) { - return +date; - }); - } - var _content = {values: values}; - - if (maxInput) { - _content.max = Number($(maxInput).val()) || 1; - } - - if (typeSelect) { - _content.type = typeSelect.getValue(); - } - if (v.items) { var items = []; $(containerItems).find('input').each(function (i, el) { + var val = $(el).val() || el.placeholder || ''; if (el === active) { cursor.item = true; cursor.uid= $(el).data('uid'); cursor.start = el.selectionStart; cursor.end = el.selectionEnd; + cursor.el = val; } - var val = $(el).val() || el.placeholder || ''; - items.push({ - uid: $(el).data('uid'), - v: val - }); }); - _content.items = items; } return { - old: (tmp && tmp.old) || v, - content: _content, cursor: cursor }; }); @@ -555,9 +549,10 @@ define([ else { duplicates = true; } }); } - values = values.filter(Boolean); // Block empty or undeinfed options + values = values.filter(Boolean); // Block empty or undefined options if (!values.length) { - return void UI.warn(Messages.error); + return; + //return void UI.warn(Messages.error); // XXX not anymore with autosave? } var res = { values: values }; @@ -575,12 +570,13 @@ define([ } else { duplicates = true; } }); + items = items.filter(Boolean); res.items = items; } // Show duplicates warning if (duplicates) { - UI.warn(Messages.form_duplicates); + UI.warn(Messages.form_duplicates); // XXX } // If checkboxes, get the maximum number of values the users can select @@ -597,7 +593,13 @@ define([ return res; }; - var saveAndCancel = saveAndCancelOptions(getSaveRes, cb); + evOnSave.reg(function () { + var res = getSaveRes(); + if (!res) { return; } + cb(res); + }); + + var saveAndCancel = saveAndCancelOptions(cb); return [ type, @@ -881,36 +883,45 @@ define([ return { tag: tag, edit: function (cb, tmp) { - var t = h('textarea'); - var block = h('div.cp-form-edit-options-block', [t]); - var cm = SFCodeMirror.create("gfm", CMeditor, t); - var editor = cm.editor; - editor.setOption('lineNumbers', true); - editor.setOption('lineWrapping', true); - editor.setOption('styleActiveLine', true); - editor.setOption('readOnly', false); + // Cancel changes + var cancelBlock = saveAndCancelOptions(cb); var text = opts.text; var cursor; - if (tmp && tmp.content && tmp.old.text === text) { - text = tmp.content.text; + if (tmp && tmp.cursor) { cursor = tmp.cursor; } + var block, editor; + if (tmp && tmp.block) { + block = tmp.block; + editor = tmp.editor; + } + + if (!block || !editor) { + var t = h('textarea'); + block = h('div.cp-form-edit-options-block', [t]); + var cm = SFCodeMirror.create("gfm", CMeditor, t); + editor = cm.editor; + editor.setOption('lineNumbers', true); + editor.setOption('lineWrapping', true); + editor.setOption('styleActiveLine', true); + editor.setOption('readOnly', false); + } + setTimeout(function () { - editor.setValue(text); - if (cursor) { - if (Sortify(cursor.start) === Sortify(cursor.end)) { - editor.setCursor(cursor.start); - } else { - editor.setSelection(cursor.start, cursor.end); - } + editor.focus(); + if (!(tmp && tmp.editor)) { + editor.setValue(text); + } else { + SFCodeMirror.setValueAndCursor(editor, editor.getValue(), text); } editor.refresh(); editor.save(); editor.focus(); }); - if (APP.common) { + + if (APP.common && !(tmp && tmp.block)) { var markdownTb = APP.common.createMarkdownToolbar(editor, { embed: function (mt) { editor.focus(); @@ -921,26 +932,20 @@ define([ $(markdownTb.toolbar).show(); cm.configureTheme(APP.common, function () {}); } - // 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) - ]); var getContent = function () { return { text: editor.getValue() }; }; - $(saveBlock).click(function () { - $(saveBlock).attr('disabled', 'disabled'); + + if (tmp && tmp.onChange) { + editor.off('change', tmp.onChange); + } + var on = function () { cb(getContent()); - }); + }; + editor.on('change', on); cursorGetter = function () { if (document.activeElement && block.contains(document.activeElement)) { @@ -950,15 +955,16 @@ define([ }; } return { - old: opts, - content: getContent(), - cursor: cursor + cursor: cursor, + block: block, + editor: editor, + onChange: on }; }; return [ block, - h('div.cp-form-edit-save', [cancelBlock, saveBlock]) + cancelBlock ]; }, getCursor: function () { return cursorGetter(); }, @@ -2643,7 +2649,7 @@ define([ Messages.form_preview = "Preview:"; // XXX var previewDiv = h('div.cp-form-preview', Messages.form_preview); - Messages.form_required_answer = "Answer: " + Messages.form_required_answer = "Answer: "; Messages.form_required_on = "required"; Messages.form_required_off = "optional"; // Required radio displayed only for types that have an "isEmpty" function @@ -2759,42 +2765,45 @@ define([ h('span', Messages.form_editBlock) ]); editContainer = h('div'); - var onSave = function (newOpts) { - data.editing = false; - if (!newOpts) { // Cancel edit + var onSave = function (newOpts, close) { + if (close) { // Cancel edit + data.editing = false; $(editContainer).empty(); - $(editButtons).show(); - $(data.tag).show(); + var $oldTag = $(data.tag); + $(edit).show(); $(previewDiv).show(); - $(requiredDiv).show(); - return; - } - $(editContainer).empty(); - block.opts = newOpts; - framework.localChange(); - var $oldTag = $(data.tag); - framework._.cpNfInner.chainpad.onSettle(function () { - $(editButtons).show(); - $(previewDiv).show(); - $(requiredDiv).show(); - UI.log(Messages.saved); + $(requiredDiv).hide(); + + $(editButtons).find('.cp-form-preview-button').remove(); + _answers = getBlockAnswers(APP.answers, uid); - data = model.get(newOpts, _answers, null, evOnChange); + data = model.get(block.opts, _answers, null, evOnChange); if (!data) { data = {}; } $oldTag.before(data.tag).remove(); - }); + return; + } + if (!newOpts) { + // invalid options, nothing to save + return; + } + block.opts = newOpts; + framework.localChange(); }; var onEdit = function (tmp) { data.editing = true; - $(requiredDiv).hide(); + $(requiredDiv).show(); $(previewDiv).hide(); $(data.tag).hide(); $(editContainer).append(data.edit(onSave, tmp, framework)); - $(editButtons).hide(); + + $(editContainer).find('.cp-form-preview-button').prependTo(editButtons); + + $(edit).hide(); }; $(edit).click(function () { onEdit(); }); + $(requiredDiv).hide(); // If we were editing this field, recover our unsaved changes if (temp && temp[uid]) { @@ -2876,9 +2885,9 @@ define([ APP.isEditor && !isStatic ? requiredDiv : undefined, APP.isEditor && !isStatic ? previewDiv : undefined, data.tag, + editContainer, editButtons ]), - editContainer ])); }); @@ -3469,7 +3478,7 @@ define([ UI.alert(content); }; - framework.onReady(function () { + framework.onReady(function (isNew) { var priv = metadataMgr.getPrivateData(); if (APP.isEditor) { @@ -3494,6 +3503,9 @@ define([ } checkIntegrity(); } + if (isNew && content.answers && typeof(content.answers.anonymous) === "undefined") { + content.answers.anonymous = true; + } sframeChan.event('EV_FORM_PIN', {channel: content.answers.channel}); @@ -3633,7 +3645,11 @@ define([ var answers, temp; if (!APP.isEditor) { answers = getFormResults(); } else { temp = getTempFields(); } + + var $main = $('.cp-form-creator-container'); + var sTop = $main.scrollTop(); updateForm(framework, content, APP.isEditor, answers, temp); + $main.scrollTop(sTop); }); framework.setContentGetter(function () {