diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 55945ccd2..5fac0939b 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -570,15 +570,12 @@ define([ }; var appToolbar = function () { - return h('div#toolbar.cryptpad-toolbar'); - }; - var appToolbar3 = function () { return h('div#cp-toolbar.cp-toolbar-container'); }; Pages['/whiteboard/'] = Pages['/whiteboard/index.html'] = function () { return [ - appToolbar3(), + appToolbar(), h('div#cp-app-whiteboard-canvas-area', h('canvas#cp-app-whiteboard-canvas', { width: 600, height: 600 @@ -639,47 +636,49 @@ define([ Pages['/poll/'] = Pages['/poll/index.html'] = function () { return [ appToolbar(), - h('div#content', [ - h('div#poll', [ - h('div#howItWorks', [ + h('div#cp-app-poll-content', [ + h('div#cp-app-poll-form', [ + h('div#cp-app-poll-help', [ h('h1', 'CryptPoll'), setHTML(h('h2'), Msg.poll_subtitle), h('p', Msg.poll_p_save), h('p', Msg.poll_p_encryption) ]), - h('div.upper', [ - h('button#publish.btn.btn-success', { - style: { display: 'none' } - }, Msg.poll_publish_button), - h('button#admin.btn.btn-primary', { - style: { display: 'none' }, - title: Msg.poll_admin_button - }, Msg.poll_admin_button), - h('button#help.btn.btn-secondary', { - title: Msg.poll_show_help_button - }, Msg.poll_show_help_button) - ]), - h('div.realtime', [ + h('div.cp-app-poll-realtime', [ h('br'), - h('center', [ - h('textarea#description', { + h('div', [ + h('textarea#cp-app-poll-description', { rows: "5", cols: "50", + placeholder: Msg.poll_descriptionHint, disabled: true }), + h('div#cp-app-poll-description-published'), h('br') ]), - h('div#tableContainer', [ - h('div#tableScroll'), - h('button#create-user.btn.btn-secondary', { + h('div#cp-app-poll-table-container', [ + h('div#cp-app-poll-table-scroll'), + h('button#cp-app-poll-create-user.btn.btn-secondary', { title: Msg.poll_create_user }, h('span.fa.fa-plus')), - h('button#create-option.btn.btn-secondary', { + h('button#cp-app-poll-create-option.btn.btn-secondary', { title: Msg.poll_create_option }, h('span.fa.fa-plus')), - h('button#commit.btn.btn-secondary', { - title: Msg.poll_commit - }, h('span.fa.fa-check')) + ]), + h('div#cp-app-poll-comments', [ + h('h2#cp-app-poll-comments-add-title', Msg.poll_comment_add), + h('div#cp-app-poll-comments-add', [ + h('input.cp-app-poll-comments-add-name', { + type: 'text' + }), + h('textarea.cp-app-poll-comments-add-msg'), + h('button.cp-app-poll-comments-add-submit.btn.btn-secondary', + Msg.poll_comment_submit), + h('button.cp-app-poll-comments-add-cancel.btn.btn-secondary', + Msg.cancel) + ]), + h('h2#cp-app-poll-comments-list-title', Msg.poll_comment_list), + h('div#cp-app-poll-comments-list') ]) ]) ]) diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index 8f53ecab5..812da47a8 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -29,6 +29,6 @@ body.cp-app-slide { @import "../../../slide/app-slide.less"; } body.cp-app-file { @import "../../../file/app-file.less"; } body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; } body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; } -//body.cp-app-poll { @import "../../../poll/app-poll.less"; } +body.cp-app-poll { @import "../../../poll/app-poll.less"; } body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; } diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 118eb6f7b..91bba1fbf 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -246,7 +246,9 @@ define(function () { out.poll_removeUser = "Êtes-vous sûr de vouloir supprimer cet utilisateur ?"; out.poll_titleHint = "Titre"; - out.poll_descriptionHint = "Description"; + out.poll_descriptionHint = "Décrivez votre sondage puis cliquer sur le bouton ✓ (Publier).\n" + + "La description peut contenir de la syntaxe markdown, et vous pouvez y ajouter des images stockées dans votre CryptDrive.\n" + + "Toutes les personnes possédant le lien d'édition de ce sondage peuvent modifier la description, bien que ce soit déconseillé."; out.poll_remove = "Supprimer"; out.poll_edit = "Modifier"; @@ -256,6 +258,15 @@ define(function () { out.poll_show_help_button = "Afficher l'aide"; out.poll_hide_help_button = "Cacher l'aide"; + out.poll_bookmark_col = "Marquer cette colonne comme favorite pour qu'elle soit toujours déverouillée et affichée en première position."; + out.poll_bookmarked_col = "Voici votre colonne favorite; elle sera toujours dévérouillée et affichée en première position."; + out.poll_total = 'TOTAL'; + + out.poll_comment_list = "Commentaires"; + out.poll_comment_add = "Ajouter un commentaire"; + out.poll_comment_submit = "Envoyer"; + out.poll_comment_remove = "Supprimer ce commentaire"; + // Canvas out.canvas_clear = "Nettoyer"; out.canvas_delete = "Supprimer la sélection"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index b4aadd799..f7bb3ffca 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -250,7 +250,9 @@ define(function () { out.poll_removeUser = "Are you sure you'd like to remove this user?"; out.poll_titleHint = "Title"; - out.poll_descriptionHint = "Describe your poll, and use the 'publish' button when you're done. Anyone with the link can change the description, but this is discouraged."; + out.poll_descriptionHint = "Describe your poll, and use the ✓ (publish) button when you're done.\n" + + "The description can be written using markdown syntax and you can embed media elements from your CryptDrive.\n" + + "Anyone with the link can change the description, but this is discouraged."; out.poll_remove = "Remove"; out.poll_edit = "Edit"; @@ -260,6 +262,15 @@ define(function () { out.poll_show_help_button = "Show help"; out.poll_hide_help_button = "Hide help"; + out.poll_bookmark_col = 'Bookmark this column so that it is always unlocked and displayed at the beginning for you'; + out.poll_bookmarked_col = 'This is your bookmarked column. It will always be unlocked and displayed at the beginning for you.'; + out.poll_total = 'TOTAL'; + + out.poll_comment_list = "Comments"; + out.poll_comment_add = "Add a comment"; + out.poll_comment_submit = "Send"; + out.poll_comment_remove = "Delete this comment"; + // Canvas out.canvas_clear = "Clear"; out.canvas_delete = "Delete selection"; diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 6686a9a55..098972161 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -9,6 +9,58 @@ define([ var saveAs = window.saveAs; var module = {}; + var cursorToPos = function(cursor, oldText) { + var cLine = cursor.line; + var cCh = cursor.ch; + var pos = 0; + var textLines = oldText.split("\n"); + for (var line = 0; line <= cLine; line++) { + if(line < cLine) { + pos += textLines[line].length+1; + } + else if(line === cLine) { + pos += cCh; + } + } + return pos; + }; + + var posToCursor = function(position, newText) { + var cursor = { + line: 0, + ch: 0 + }; + var textLines = newText.substr(0, position).split("\n"); + cursor.line = textLines.length - 1; + cursor.ch = textLines[cursor.line].length; + return cursor; + }; + + module.setValueAndCursor = function (editor, oldDoc, remoteDoc, TextPatcher) { + var scroll = editor.getScrollInfo(); + //get old cursor here + var oldCursor = {}; + oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc); + oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc); + + editor.setValue(remoteDoc); + editor.save(); + + var op = TextPatcher.diff(oldDoc, remoteDoc); + var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { + return TextPatcher.transformCursor(oldCursor[attr], op); + }); + + if(selects[0] === selects[1]) { + editor.setCursor(posToCursor(selects[0], remoteDoc)); + } + else { + editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc)); + } + + editor.scrollTo(scroll.left, scroll.top); + }; + module.create = function (Common, defaultMode, CMeditor) { var exp = {}; var Messages = Cryptpad.Messages; @@ -253,56 +305,8 @@ define([ onLocal(); }; - var cursorToPos = function(cursor, oldText) { - var cLine = cursor.line; - var cCh = cursor.ch; - var pos = 0; - var textLines = oldText.split("\n"); - for (var line = 0; line <= cLine; line++) { - if(line < cLine) { - pos += textLines[line].length+1; - } - else if(line === cLine) { - pos += cCh; - } - } - return pos; - }; - - var posToCursor = function(position, newText) { - var cursor = { - line: 0, - ch: 0 - }; - var textLines = newText.substr(0, position).split("\n"); - cursor.line = textLines.length - 1; - cursor.ch = textLines[cursor.line].length; - return cursor; - }; - exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) { - var scroll = editor.getScrollInfo(); - //get old cursor here - var oldCursor = {}; - oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc); - oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc); - - editor.setValue(remoteDoc); - editor.save(); - - var op = TextPatcher.diff(oldDoc, remoteDoc); - var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { - return TextPatcher.transformCursor(oldCursor[attr], op); - }); - - if(selects[0] === selects[1]) { - editor.setCursor(posToCursor(selects[0], remoteDoc)); - } - else { - editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc)); - } - - editor.scrollTo(scroll.left, scroll.top); + return module.setValueAndCursor(editor, oldDoc, remoteDoc, TextPatcher); }; return exp; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e145fc4a9..6913f7ef5 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -65,6 +65,7 @@ define([ Cryptpad.ready(waitFor()); })); }).nThen(function (waitFor) { + $('#sbox-iframe').focus(); sframeChan.on('EV_CACHE_PUT', function (x) { Object.keys(x).forEach(function (k) { diff --git a/www/common/sframe-common-title.js b/www/common/sframe-common-title.js index f07da43fa..6e0712011 100644 --- a/www/common/sframe-common-title.js +++ b/www/common/sframe-common-title.js @@ -17,6 +17,7 @@ define(['jquery'], function ($) { var $title; exp.setToolbar = function (toolbar) { $title = toolbar && (toolbar.title || toolbar.pageTitle); + console.log('SET TOOLBAR'); }; exp.getTitle = function () { return exp.title; }; diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 3218f40f4..6433906d6 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -228,7 +228,7 @@ define([ $span.append($rightCol); } else { Common.displayAvatar($span, data.avatar, name, function ($img) { - if (data.avatar && $img) { + if (data.avatar && $img.length) { avatars[data.avatar] = $img[0].outerHTML; } $span.append($rightCol); @@ -448,7 +448,7 @@ define([ var $content = $('
'); $('', {'style':'display:none;'}).appendTo($content); $('

').text(Messages.viewEmbedTitle).appendTo($content); - var $tag = $('

').text(Messages.fileEmbedTag).appendTo($content); + var $tag = $('

').text(Messages.viewEmbedTag).appendTo($content); $('
').appendTo($tag); var iframeId = uid(); var iframeEmbed = ''; diff --git a/www/drive/inner.js b/www/drive/inner.js index a16c0d8f2..4b673a997 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1,6 +1,5 @@ define([ 'jquery', - '/bower_components/chainpad-crypto/crypto.js', '/bower_components/textpatcher/TextPatcher.js', '/common/toolbar3.js', 'json.sortify', @@ -12,7 +11,6 @@ define([ '/common/common-realtime.js', '/common/userObject.js', '/customize/application_config.js', - '/common/mergeDrive.js', '/common/sframe-chainpad-listmap.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -20,7 +18,6 @@ define([ 'less!/customize/src/less2/main.less', ], function ( $, - Crypto, TextPatcher, Toolbar, JSONSortify, @@ -32,7 +29,6 @@ define([ CommonRealtime, FO, AppConfig, - Merge, Listmap) { var Messages = Cryptpad.Messages; diff --git a/www/oldpoll/index.html b/www/oldpoll/index.html new file mode 100644 index 000000000..3f4b53dbd --- /dev/null +++ b/www/oldpoll/index.html @@ -0,0 +1,11 @@ + + + + + + Zero Knowledge Date Picker + + + + diff --git a/www/oldpoll/main.js b/www/oldpoll/main.js new file mode 100644 index 000000000..eaadb2114 --- /dev/null +++ b/www/oldpoll/main.js @@ -0,0 +1,806 @@ +define([ + 'jquery', + '/bower_components/textpatcher/TextPatcher.js', + '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/chainpad-crypto/crypto.js', + '/common/cryptpad-common.js', + '/common/cryptget.js', + '/bower_components/hyperjson/hyperjson.js', + 'render.js', + '/common/toolbar2.js', + '/bower_components/file-saver/FileSaver.min.js', + + 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', + 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', + 'less!/customize/src/less/toolbar.less', + 'less!/customize/src/less/cryptpad.less', + 'less!/poll/poll.less', +], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar) { + + var Messages = Cryptpad.Messages; + + $(function () { + + var HIDE_INTRODUCTION_TEXT = "hide-text"; + var defaultName; + + var secret = Cryptpad.getSecrets(); + var readOnly = secret.keys && !secret.keys.editKeyStr; + // DEPRECATE_F + if (!secret.keys) { + secret.keys = secret.key; + } + + var DEBUG = false; + var debug = console.log; + if (!DEBUG) { + debug = function() {}; + } + + Cryptpad.addLoadingScreen(); + var onConnectError = function () { + Cryptpad.errorLoadingScreen(Messages.websocketError); + }; + + var Render = Renderer(Cryptpad); + + var APP = window.APP = { + Toolbar: Toolbar, + Hyperjson: Hyperjson, + Render: Render, + $bar: $('#toolbar'), + editable: { + row: [], + col: [] + }, + locked: false + }; + + var sortColumns = function (order, firstcol) { + var colsOrder = order.slice(); + colsOrder.sort(function (a, b) { + return (a === firstcol) ? -1 : + ((b === firstcol) ? 1 : 0); + }); + return colsOrder; + }; + + var isOwnColumnCommitted = function () { + return APP.proxy && APP.proxy.table.colsOrder.indexOf(APP.userid) !== -1; + }; + + var mergeUncommitted = function (proxy, uncommitted, commit) { + var newObj; + if (commit) { + newObj = proxy; + } else { + newObj = $.extend(true, {}, proxy); + } + // We have uncommitted data only if the user's column is not in the proxy + // If it is already is the proxy, nothing to merge + if (isOwnColumnCommitted()) { + return newObj; + } + // Merge uncommitted into the proxy + uncommitted.table.colsOrder.forEach(function (x) { + if (newObj.table.colsOrder.indexOf(x) !== -1) { return; } + newObj.table.colsOrder.push(x); + }); + for (var k in uncommitted.table.cols) { + if (!newObj.table.cols[k]) { + newObj.table.cols[k] = uncommitted.table.cols[k]; + } + } + for (var l in uncommitted.table.cells) { + if (!newObj.table.cells[l]) { + newObj.table.cells[l] = uncommitted.table.cells[l]; + } + } + return newObj; + }; + + var styleUncommittedColumn = function () { + var id = APP.userid; + + // Enable the checkboxes for the user's column (committed or not) + $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); + $('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled'); + $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked); + + if (isOwnColumnCommitted()) { return; } + $('[data-rt-id^="' + id + '"]').closest('td').addClass("uncommitted"); + $('td.uncommitted .cover').addClass("uncommitted"); + $('.uncommitted input[type="text"]').attr("placeholder", Messages.poll_userPlaceholder); + }; + + var unlockElements = function () { + APP.editable.row.forEach(function (id) { + var $input = $('input[type="text"][disabled="disabled"][data-rt-id="' + id + '"]').removeAttr('disabled'); + $input.parent().parent().addClass('editing'); + $('span.edit[data-rt-id="' + id + '"]').css('visibility', 'hidden'); + }); + APP.editable.col.forEach(function (id) { + var $input = $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); + $input.parent().addClass('editing'); + $('input[type="number"][data-rt-id^="' + id + '"]').addClass('enabled'); + $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked); + }); + }; + + var updateTableButtons = function () { + if (!isOwnColumnCommitted()) { + $('#commit').show(); + } + + var $createOption = APP.$table.find('tfoot tr td:first-child'); + var $commitCell = APP.$table.find('tfoot tr td:nth-child(2)'); + $createOption.append(APP.$createRow); + $commitCell.append(APP.$commit); + $('#create-user, #create-option').css('display', 'inline-flex'); + if (!APP.proxy || !APP.proxy.table.rowsOrder || APP.proxy.table.rowsOrder.length === 0) { $('#create-user').hide(); } + var width = $('#table').outerWidth(); + if (width) { + //$('#create-user').css('left', width + 30 + 'px'); + } + }; + + var setTablePublished = function (bool) { + if (bool) { + if (APP.$publish) { APP.$publish.hide(); } + if (APP.$admin) { APP.$admin.show(); } + $('#create-option').hide(); + $('.remove[data-rt-id^="y"], .edit[data-rt-id^="y"]').hide(); + } else { + if (APP.$publish) { APP.$publish.show(); } + if (APP.$admin) { APP.$admin.hide(); } + $('#create-option').show(); + $('.remove[data-rt-id^="y"], .edit[data-rt-id^="y"]').show(); + } + }; + + var updateDisplayedTable = function () { + styleUncommittedColumn(); + unlockElements(); + updateTableButtons(); + setTablePublished(APP.proxy.published); + + /* + APP.proxy.table.rowsOrder.forEach(function (rowId) { + $('[data-rt-id="' + rowId +'"]').val(APP.proxy.table.rows[rowId] || ''); + });*/ + }; + + var unlockColumn = function (id, cb) { + if (APP.editable.col.indexOf(id) === -1) { + APP.editable.col.push(id); + } + if (typeof(cb) === "function") { + cb(); + } + }; + var unlockRow = function (id, cb) { + if (APP.editable.row.indexOf(id) === -1) { + APP.editable.row.push(id); + } + if (typeof(cb) === "function") { + cb(); + } + }; + + /* Any time the realtime object changes, call this function */ + var change = function (o, n, path, throttle, cb) { + if (path && !Cryptpad.isArray(path)) { + return; + } + if (path && path.join) { + debug("Change from [%s] to [%s] at [%s]", + o, n, path.join(', ')); + } + + var table = APP.$table[0]; + + var displayedObj = mergeUncommitted(APP.proxy, APP.uncommitted); + + var colsOrder = sortColumns(displayedObj.table.colsOrder, APP.userid); + var conf = { + cols: colsOrder, + readOnly: readOnly + }; + + //Render.updateTable(table, displayedObj, conf); + + /* FIXME browser autocomplete fills in new fields sometimes + calling updateTable twice removes the autofilled in values + setting autocomplete="off" is not reliable + + https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion + */ + Cryptpad.notify(); + + var getFocus = function () { + var active = document.activeElement; + if (!active) { return; } + return { + el: active, + start: active.selectionStart, + end: active.selectionEnd + }; + }; + var setFocus = function (obj) { + if (obj.el) { obj.el.focus(); } + else { return; } + if (obj.start) { obj.el.selectionStart = obj.start; } + if (obj.end) { obj.el.selectionEnd = obj.end; } + }; + + var updateTable = function () { + var displayedObj2 = mergeUncommitted(APP.proxy, APP.uncommitted); + var f = getFocus(); + Render.updateTable(table, displayedObj2, conf); + APP.proxy.table.rowsOrder.forEach(function (rowId) { + $('input[data-rt-id="' + rowId +'"]').val(APP.proxy.table.rows[rowId] || ''); + }); + updateDisplayedTable(); + setFocus(f); + if (typeof(cb) === "function") { + cb(); + } + }; + + if (throttle) { + if (APP.throttled) { window.clearTimeout(APP.throttled); } + updateTable(); + APP.throttled = window.setTimeout(function () { + updateDisplayedTable(); + }, throttle); + return; + } + + window.setTimeout(updateTable); + }; + + var getRealtimeId = function (input) { + return input.getAttribute && input.getAttribute('data-rt-id'); + }; + + /* Called whenever an event is fired on an input element */ + var handleInput = function (input) { + var type = input.type.toLowerCase(); + var id = getRealtimeId(input); + + debug(input); + + var object = APP.proxy; + + var x = Render.getCoordinates(id)[0]; + if (type !== "row" && x === APP.userid && APP.proxy.table.colsOrder.indexOf(x) === -1) { + object = APP.uncommitted; + } + + switch (type) { + case 'text': + debug("text[rt-id='%s'] [%s]", id, input.value); + Render.setValue(object, id, input.value); + change(null, null, null, 50); + break; + case 'number': + debug("checkbox[tr-id='%s'] %s", id, input.value); + if (APP.editable.col.indexOf(x) >= 0 || x === APP.userid) { + var value = parseInt(input.value); + + if (isNaN(value)) { + console.error("Got NaN?!"); + break; + } + + Render.setValue(object, id, value); + change(); + } else { + debug('checkbox locked'); + } + break; + default: + debug("Input[type='%s']", type); + break; + } + }; + + var hideInputs = function (target, isKeyup) { + if (APP.locked) { return; } + if (!isKeyup && $(target).is('[type="text"]')) { + return; + } + $('.lock[data-rt-id!="' + APP.userid + '"]').addClass('fa-lock').removeClass('fa-unlock').attr('title', Messages.poll_locked); + var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td'); + $cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true); + $cells.removeClass('editing'); + $('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible'); + APP.editable.col = [APP.userid]; + APP.editable.row = []; + }; + + /* Called whenever an event is fired on a span */ + var handleSpan = function (span) { + var id = span.getAttribute('data-rt-id'); + var type = Render.typeofId(id); + var isRemove = span.className && span.className.split(' ').indexOf('remove') !== -1; + var isEdit = span.className && span.className.split(' ').indexOf('edit') !== -1; + var isLock = span.className && span.className.split(' ').indexOf('lock') !== -1; + var isLocked = span.className && span.className.split(' ').indexOf('fa-lock') !== -1; + if (type === 'row') { + if (isRemove) { + Cryptpad.confirm(Messages.poll_removeOption, function (res) { + if (!res) { return; } + Render.removeRow(APP.proxy, id, function () { + change(); + }); + }); + } else if (isEdit) { + hideInputs(span); + unlockRow(id, function () { + change(null, null, null, null, function() { + $('input[data-rt-id="' + id + '"]').focus(); + }); + }); + } + } else if (type === 'col') { + if (isRemove) { + Cryptpad.confirm(Messages.poll_removeUser, function (res) { + if (!res) { return; } + Render.removeColumn(APP.proxy, id, function () { + change(); + }); + }); + } else if (isLock && isLocked) { + hideInputs(span); + unlockColumn(id, function () { + change(null, null, null, null, function() { + $('input[data-rt-id="' + id + '"]').focus(); + }); + }); + } + } else if (type === 'cell') { + change(); + } else { + debug("UNHANDLED"); + } + }; + + var handleClick = function (e, isKeyup) { + if (APP.locked) { return; } + + e.stopPropagation(); + + if (!APP.ready) { return; } + if (!isKeyup && e.which !== 1) { return; } // only allow left clicks + + var target = e && e.target; + + if (!target) { return void debug("NO TARGET"); } + + var nodeName = target && target.nodeName; + var shouldLock = $(target).hasClass('fa-unlock'); + + if ((!$(target).parents('#table tbody').length && $(target).hasClass('lock'))) { + hideInputs(e); + } + + switch (nodeName) { + case 'INPUT': + if (isKeyup && (e.keyCode === 13 || e.keyCode === 27)) { + hideInputs(target, isKeyup); + break; + } + if ($(target).is('input[type="number"]')) { console.error("number input focused?"); break; } + + handleInput(target); + break; + case 'LABEL': + var input = $('input[type="number"][id=' + $(target).attr('for') + ']'); + var value = parseInt(input.val()); + + input.val((value + 1) % 4); + + handleInput(input[0]); + break; + case 'SPAN': + if (shouldLock) { + break; + } + handleSpan(target); + break; + case undefined: + //console.error(new Error("C'est pas possible!")); + break; + default: + debug(target, nodeName); + break; + } + }; + + /* + Make sure that the realtime data structure has all the required fields + */ + var prepareProxy = function (proxy, schema) { + if (proxy && proxy.version === 1) { return; } + debug("Configuring proxy schema..."); + + proxy.info = proxy.info || schema.info; + Object.keys(schema.info).forEach(function (k) { + if (!proxy.info[k]) { proxy.info[k] = schema.info[k]; } + }); + + proxy.table = proxy.table || schema.table; + Object.keys(schema.table).forEach(function (k) { + if (!proxy.table[k]) { proxy.table[k] = schema.table[k]; } + }); + + proxy.version = 1; + proxy.type = 'poll'; + }; + + /* + + */ + var publish = APP.publish = function (bool) { + if (!APP.ready) { return; } + if (APP.proxy.published !== bool) { + APP.proxy.published = bool; + } + setTablePublished(bool); + ['textarea'].forEach(function (sel) { + $(sel).attr('disabled', bool); + }); + }; + + var showHelp = function(help) { + if (typeof help === 'undefined') { help = !$('#howItWorks').is(':visible'); } + + var msg = (help ? Messages.poll_hide_help_button : Messages.poll_show_help_button); + + $('#howItWorks').toggle(help); + $('#help').text(msg); + }; + + var Title; + var UserList; + + var copyObject = function (obj) { + return JSON.parse(JSON.stringify(obj)); + }; + + // special UI elements + var $description = $('#description').attr('placeholder', Messages.poll_descriptionHint || 'description'); + +var ready = function (info, userid, readOnly) { + debug("READY"); + debug('userid: %s', userid); + + var proxy = APP.proxy; + + var isNew = false; + var userDoc = JSON.stringify(proxy); + if (userDoc === "" || userDoc === "{}") { isNew = true; } + + if (!isNew && typeof(proxy.type) !== 'undefined' && proxy.type !== 'poll') { + var errorText = Messages.typeError; + Cryptpad.errorLoadingScreen(errorText); + throw new Error(errorText); + } + + if (typeof(proxy.type) === 'undefined') { + proxy.type = 'poll'; + } + + var uncommitted = APP.uncommitted = {}; + prepareProxy(proxy, copyObject(Render.Example)); + prepareProxy(uncommitted, copyObject(Render.Example)); + if (!readOnly && proxy.table.colsOrder.indexOf(userid) === -1 && + uncommitted.table.colsOrder.indexOf(userid) === -1) { + uncommitted.table.colsOrder.unshift(userid); + } + + var displayedObj = mergeUncommitted(proxy, uncommitted, false); + + var colsOrder = sortColumns(displayedObj.table.colsOrder, userid); + + var $table = APP.$table = $(Render.asHTML(displayedObj, null, colsOrder, readOnly)); + APP.$createRow = $('#create-option').click(function () { + Render.createRow(proxy, function (empty, id) { + change(null, null, null, null, function() { + handleSpan($('.edit[data-rt-id="' + id + '"]')[0]); + }); + }); + }); + + APP.$createCol = $('#create-user').click(function () { + Render.createColumn(proxy, function (empty, id) { + change(null, null, null, null, function() { + handleSpan($('.lock[data-rt-id="' + id + '"]')[0]); + }); + }); + }); + + // Commit button + APP.$commit = $('#commit').click(function () { + var uncommittedCopy = JSON.parse(JSON.stringify(APP.uncommitted)); + APP.uncommitted = {}; + prepareProxy(APP.uncommitted, copyObject(Render.Example)); + mergeUncommitted(proxy, uncommittedCopy, true); + APP.$commit.hide(); + change(); + }); + + // #publish button is removed in readonly + APP.$publish = $('#publish') + .click(function () { + publish(true); + }); + + APP.$admin = $('#admin') + .click(function () { + publish(false); + }); + + APP.$help = $('#help') + .click(function () { + showHelp(); + }); + + // Title + if (APP.proxy.info.defaultTitle) { + Title.updateDefaultTitle(APP.proxy.info.defaultTitle); + } else { + APP.proxy.info.defaultTitle = Title.defaultTitle; + } + + var andThen = function () { + if (readOnly) { return; } + Cryptpad.setPadAttribute('userid', userid, function (e) { + if (e) { console.error(e); } + }); + }; + + if (Cryptpad.initialName && !APP.proxy.info.title) { + APP.proxy.info.title = Cryptpad.initialName; + Title.updateTitle(Cryptpad.initialName, null, andThen); + } else { + Title.updateTitle(APP.proxy.info.title || Title.defaultTitle, null, andThen); + } + + // Description + var resize = function () { + var lineCount = $description.val().split('\n').length; + $description.css('height', lineCount + 'rem'); + }; + $description.on('change keyup', function () { + var val = $description.val(); + proxy.info.description = val; + resize(); + }); + resize(); + if (typeof(proxy.info.description) !== 'undefined') { + $description.val(proxy.info.description); + } + + $('#tableScroll').html('').prepend($table); + updateDisplayedTable(); + + $table + .click(handleClick) + .on('keyup', function (e) { handleClick(e, true); }); + + $(window).click(function(e) { + if (e.which !== 1) { return; } + hideInputs(); + }); + + proxy + .on('change', ['info'], function (o, n, p) { + if (p[1] === 'title') { + Title.updateTitle(n); + Cryptpad.notify(); + } else if (p[1] === "userData") { + UserList.addToUserData(APP.proxy.info.userData); + } else if (p[1] === 'description') { + var op = TextPatcher.diff(o, n); + var el = $description[0]; + + var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { + return TextPatcher.transformCursor(el[attr], op); + }); + $description.val(n); + if (op) { + el.selectionStart = selects[0]; + el.selectionEnd = selects[1]; + } + Cryptpad.notify(); + } + + debug("change: (%s, %s, [%s])", o, n, p.join(', ')); + }) + .on('change', ['table'], change) + .on('remove', [], change); + + var userInput = $('.uncommitted > input'); + if (userInput.val() === '') + { + userInput.val(Cryptpad.getProxy()[Cryptpad.displayNameKey]); + } + + UserList.addToUserData(APP.proxy.info.userData); + + APP.ready = true; + if (!proxy.published) { + publish(false); + } else { + publish(true); + } + + Cryptpad.removeLoadingScreen(); + + if (readOnly) { return; } + UserList.getLastName(APP.toolbar.$userNameButton, isNew); +}; + +var setEditable = function (editable) { + APP.locked = !editable; + + if (editable === false) { + // disable all the things + $('.realtime input, .realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked); + $('span.edit, span.remove').hide(); + $('span.lock').addClass('fa-lock').removeClass('fa-unlock') + .attr('title', Messages.poll_locked) + .css({'cursor': 'default'}); + } else { + // enable + $('span.edit, span.remove').show(); + $('span.lock').css({'cursor': ''}); + $('.realtime button, .upper button, .realtime textarea').attr('disabled', APP.locked); + unlockElements(); + } +}; + +var disconnect = function () { + setEditable(false); + APP.toolbar.failed(); + Cryptpad.alert(Messages.common_connectionLost, undefined, true); +}; + +var reconnect = function (info) { + setEditable(true); + APP.toolbar.reconnecting(info.myId); + Cryptpad.findOKButton().click(); +}; + +var create = function (info) { + APP.myID = info.myID; + + var editHash; + if (!readOnly) { + editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); + } + + if (APP.realtime !== info.realtime) { + APP.realtime = info.realtime; + APP.patchText = TextPatcher.create({ + realtime: info.realtime, + logging: true, + }); + } + + var onLocal = function () { + APP.proxy.info.userData = UserList.userData; + }; + UserList = Cryptpad.createUserList(info, onLocal, Cryptget, Cryptpad); + + var onLocalTitle = function () { + APP.proxy.info.title = Title.isDefaultTitle() ? "" : Title.title; + }; + Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad); + + var configTb = { + displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'], + userList: UserList.getToolbarConfig(), + share: { + secret: secret, + channel: info.channel + }, + title: Title.getTitleConfig(), + common: Cryptpad, + readOnly: readOnly, + ifrw: window, + realtime: info.realtime, + network: info.network, + $container: APP.$bar, + $contentContainer: $('#content') + }; + APP.toolbar = Toolbar.create(configTb); + + Title.setToolbar(APP.toolbar); + + var $rightside = APP.toolbar.$rightside; + + /* add a forget button */ + var forgetCb = function (err) { + if (err) { return; } + setEditable(false); + }; + var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); + $rightside.append($forgetPad); + + // set the hash + if (!readOnly) { Cryptpad.replaceHash(editHash); } + + /* save as template */ + if (!Cryptpad.isTemplate(window.location.href)) { + var templateObj = { + rt: info.realtime, + Crypt: Cryptget, + getTitle: function () { return document.title; } + }; + var $templateButton = Cryptpad.createButton('template', true, templateObj); + $rightside.append($templateButton); + } +}; + + // don't initialize until the store is ready. + Cryptpad.ready(function () { + Cryptpad.reportAppUsage(); + var config = { + websocketURL: Cryptpad.getWebsocketURL(), + channel: secret.channel, + readOnly: readOnly, + data: {}, + // our public key + validateKey: secret.keys.validateKey || undefined, + //readOnly: readOnly, + crypto: Crypto.createEncryptor(secret.keys), + userName: 'poll', + network: Cryptpad.getNetwork() + }; + + if (readOnly) { + $('#commit, #create-user, #create-option, #publish, #admin').remove(); + } + + var parsedHash = Cryptpad.parsePadUrl(window.location.href); + defaultName = Cryptpad.getDefaultName(parsedHash); + var rt = window.rt = APP.rt = Listmap.create(config); + APP.proxy = rt.proxy; + rt.proxy + .on('create', create) + .on('ready', function (info) { + Cryptpad.getPadAttribute('userid', function (e, userid) { + if (e) { console.error(e); } + if (!userid) { userid = Render.coluid(); } + APP.userid = userid; + ready(info, userid, readOnly); + }); + }) + .on('disconnect', disconnect) + .on('reconnect', reconnect); + + Cryptpad.getAttribute(['poll', HIDE_INTRODUCTION_TEXT], function (e, value) { + if (e) { console.error(e); } + if (!value) { + Cryptpad.setAttribute(['poll', HIDE_INTRODUCTION_TEXT], "1", function (e) { + if (e) { console.error(e); } + }); + showHelp(true); + } else { + showHelp(false); + } + }); + + Cryptpad.onLogout(function () { setEditable(false); }); + }); + Cryptpad.onError(function (info) { + if (info) { + onConnectError(); + } + }); + + }); +}); diff --git a/www/poll/poll.less b/www/oldpoll/poll.less similarity index 100% rename from www/poll/poll.less rename to www/oldpoll/poll.less diff --git a/www/oldpoll/render.js b/www/oldpoll/render.js new file mode 100644 index 000000000..f84fdc964 --- /dev/null +++ b/www/oldpoll/render.js @@ -0,0 +1,453 @@ +define([ + //'/common/cryptpad-common.js', + '/bower_components/hyperjson/hyperjson.js', + '/bower_components/textpatcher/TextPatcher.js', + '/bower_components/diff-dom/diffDOM.js', +], function (Hyperjson, TextPatcher) { + var DiffDOM = window.diffDOM; + + var Example = { + info: { + title: '', + description: '', + userData: {} + }, + table: { +/* TODO + +deprecate the practice of storing cells, cols, and rows separately. + +Instead, keep everything in one map, and iterate over columns and rows +by maintaining indexes in rowsOrder and colsOrder + +*/ + cells: {}, + cols: {}, + colsOrder: [], + rows: {}, + rowsOrder: [] + } + }; + +var Renderer = function (Cryptpad) { + + var Render = { + Example: Example + }; + + var Uid = Render.Uid = function (prefix, f) { + f = f || function () { + return Number(Math.random() * Number.MAX_SAFE_INTEGER) + .toString(32).replace(/\./g, ''); + }; + return function () { return prefix + '-' + f(); }; + }; + + var coluid = Render.coluid = Uid('x'); + var rowuid = Render.rowuid = Uid('y'); + + var isRow = Render.isRow = function (id) { return /^y\-[^_]*$/.test(id); }; + var isColumn = Render.isColumn = function (id) { return /^x\-[^_]*$/.test(id); }; + var isCell = Render.isCell = function (id) { return /^x\-[^_]*_y\-.*$/.test(id); }; + + var typeofId = Render.typeofId = function (id) { + if (isRow(id)) { return 'row'; } + if (isColumn(id)) { return 'col'; } + if (isCell(id)) { return 'cell'; } + return null; + }; + + Render.getCoordinates = function (id) { + return id.split('_'); + }; + + var getColumnValue = Render.getColumnValue = function (obj, colId) { + return Cryptpad.find(obj, ['table', 'cols'].concat([colId])); + }; + + var getRowValue = Render.getRowValue = function (obj, rowId) { + return Cryptpad.find(obj, ['table', 'rows'].concat([rowId])); + }; + + var getCellValue = Render.getCellValue = function (obj, cellId) { + var value = Cryptpad.find(obj, ['table', 'cells'].concat([cellId])); + if (typeof value === 'boolean') { + return (value === true ? 1 : 0); + } else { + return value; + } + }; + + var setRowValue = Render.setRowValue = function (obj, rowId, value) { + var parent = Cryptpad.find(obj, ['table', 'rows']); + if (typeof(parent) === 'object') { return (parent[rowId] = value); } + return null; + }; + + var setColumnValue = Render.setColumnValue = function (obj, colId, value) { + var parent = Cryptpad.find(obj, ['table', 'cols']); + if (typeof(parent) === 'object') { return (parent[colId] = value); } + return null; + }; + + var setCellValue = Render.setCellValue = function (obj, cellId, value) { + var parent = Cryptpad.find(obj, ['table', 'cells']); + if (typeof(parent) === 'object') { return (parent[cellId] = value); } + return null; + }; + + Render.createColumn = function (obj, cb, id, value) { + var order = Cryptpad.find(obj, ['table', 'colsOrder']); + if (!order) { throw new Error("Uninitialized realtime object!"); } + id = id || coluid(); + value = value || ""; + setColumnValue(obj, id, value); + order.push(id); + if (typeof(cb) === 'function') { cb(void 0, id); } + }; + + Render.removeColumn = function (obj, id, cb) { + var order = Cryptpad.find(obj, ['table', 'colsOrder']); + var parent = Cryptpad.find(obj, ['table', 'cols']); + + if (!(order && parent)) { throw new Error("Uninitialized realtime object!"); } + + var idx = order.indexOf(id); + if (idx === -1) { + return void console + .error(new Error("Attempted to remove id which does not exist")); + } + + Object.keys(obj.table.cells).forEach(function (key) { + if (key.indexOf(id) === 0) { + delete obj.table.cells[key]; + } + }); + + order.splice(idx, 1); + if (parent[id]) { delete parent[id]; } + if (typeof(cb) === 'function') { + cb(); + } + }; + + Render.createRow = function (obj, cb, id, value) { + var order = Cryptpad.find(obj, ['table', 'rowsOrder']); + if (!order) { throw new Error("Uninitialized realtime object!"); } + id = id || rowuid(); + value = value || ""; + setRowValue(obj, id, value); + order.push(id); + if (typeof(cb) === 'function') { cb(void 0, id); } + }; + + Render.removeRow = function (obj, id, cb) { + var order = Cryptpad.find(obj, ['table', 'rowsOrder']); + var parent = Cryptpad.find(obj, ['table', 'rows']); + + if (!(order && parent)) { throw new Error("Uninitialized realtime object!"); } + + var idx = order.indexOf(id); + if (idx === -1) { + return void console + .error(new Error("Attempted to remove id which does not exist")); + } + + order.splice(idx, 1); + if (parent[id]) { delete parent[id]; } + if (typeof(cb) === 'function') { cb(); } + }; + + Render.setValue = function (obj, id, value) { + var type = typeofId(id); + + switch (type) { + case 'row': return setRowValue(obj, id, value); + case 'col': return setColumnValue(obj, id, value); + case 'cell': return setCellValue(obj, id, value); + case null: break; + default: + console.log("[%s] has type [%s]", id, type); + throw new Error("Unexpected type!"); + } + }; + + Render.getValue = function (obj, id) { + switch (typeofId(id)) { + case 'row': return getRowValue(obj, id); + case 'col': return getColumnValue(obj, id); + case 'cell': return getCellValue(obj, id); + case null: break; + default: throw new Error("Unexpected type!"); + } + }; + + var getRowIds = Render.getRowIds = function (obj) { + return Cryptpad.find(obj, ['table', 'rowsOrder']); + }; + + var getColIds = Render.getColIds = function (obj) { + return Cryptpad.find(obj, ['table', 'colsOrder']); + }; + + var getCells = Render.getCells = function (obj) { + return Cryptpad.find(obj, ['table', 'cells']); + }; + + /* cellMatrix takes a proxy object, and optionally an alternate ordering + of row/column keys (as an array). + + it returns an array of arrays containing the relevant data for each + cell in table we wish to construct. + */ + var cellMatrix = Render.cellMatrix = function (obj, rows, cols, readOnly) { + if (typeof(obj) !== 'object') { + throw new Error('expected realtime-proxy object'); + } + + var cells = getCells(obj); + rows = rows || getRowIds(obj); + rows.push(''); + cols = cols || getColIds(obj); + + return [null].concat(rows).map(function (row, i) { + if (i === 0) { + return [null].concat(cols.map(function (col) { + var result = { + 'data-rt-id': col, + type: 'text', + value: getColumnValue(obj, col) || "", + placeholder: Cryptpad.Messages.poll_userPlaceholder, + disabled: 'disabled' + }; + return result; + })); + } + if (i === rows.length) { + return [null].concat(cols.map(function () { + return { + 'class': 'lastRow', + }; + })); + } + + return [{ + 'data-rt-id': row, + value: getRowValue(obj, row), + type: 'text', + placeholder: Cryptpad.Messages.poll_optionPlaceholder, + disabled: 'disabled' + }].concat(cols.map(function (col) { + var id = [col, rows[i-1]].join('_'); + var val = cells[id]; + var result = { + 'data-rt-id': id, + type: 'number', + autocomplete: 'nope', + value: '3', + }; + if (readOnly) { + result.disabled = "disabled"; + } + if (typeof val !== 'undefined') { + if (typeof val === 'boolean') { val = (val ? '1' : '0'); } + result.value = val; + } + return result; + })); + }); + }; + + var makeRemoveElement = Render.makeRemoveElement = function (id) { + return ['SPAN', { + 'data-rt-id': id, + 'title': Cryptpad.Messages.poll_remove, + class: 'remove', + }, ['✖']]; + }; + + var makeEditElement = Render.makeEditElement = function (id) { + return ['SPAN', { + 'data-rt-id': id, + 'title': Cryptpad.Messages.poll_edit, + class: 'edit', + }, ['✐']]; + }; + + var makeLockElement = Render.makeLockElement = function (id) { + return ['SPAN', { + 'data-rt-id': id, + 'title': Cryptpad.Messages.poll_locked, + class: 'lock fa fa-lock', + }, []]; + }; + + var makeHeadingCell = Render.makeHeadingCell = function (cell, readOnly) { + if (!cell) { return ['TD', {}, []]; } + if (cell.type === 'text') { + var elements = [['INPUT', cell, []]]; + if (!readOnly) { + elements.unshift(makeRemoveElement(cell['data-rt-id'])); + elements.unshift(makeLockElement(cell['data-rt-id'])); + } + return ['TD', {}, elements]; + } + return ['TD', cell, []]; + }; + + var clone = function (o) { + return JSON.parse(JSON.stringify(o)); + }; + + var makeCheckbox = Render.makeCheckbox = function (cell) { + var attrs = clone(cell); + + // FIXME + attrs.id = cell['data-rt-id']; + + var labelClass = 'cover'; + + // TODO implement Yes/No/Maybe/Undecided + return ['TD', {class:"checkbox-cell"}, [ + ['DIV', {class: 'checkbox-contain'}, [ + ['INPUT', attrs, []], + ['SPAN', {class: labelClass}, []], + ['LABEL', { + for: attrs.id, + 'data-rt-id': attrs.id, + }, []] + ]] + ]]; + }; + + var makeBodyCell = Render.makeBodyCell = function (cell, readOnly) { + if (cell && cell.type === 'text') { + var elements = [['INPUT', cell, []]]; + if (!readOnly) { + elements.push(makeRemoveElement(cell['data-rt-id'])); + elements.push(makeEditElement(cell['data-rt-id'])); + } + return ['TD', {}, [ + ['DIV', {class: 'text-cell'}, elements] + ]]; + } + + if (cell && cell.type === 'number') { + return makeCheckbox(cell); + } + return ['TD', cell, []]; + }; + + var makeBodyRow = Render.makeBodyRow = function (row, readOnly) { + return ['TR', {}, row.map(function (cell) { + return makeBodyCell(cell, readOnly); + })]; + }; + + var toHyperjson = Render.toHyperjson = function (matrix, readOnly) { + if (!matrix || !matrix.length) { return; } + var head = ['THEAD', {}, [ ['TR', {}, matrix[0].map(function (cell) { + return makeHeadingCell(cell, readOnly); + })] ]]; + var foot = ['TFOOT', {}, matrix.slice(-1).map(function (row) { + return makeBodyRow(row, readOnly); + })]; + var body = ['TBODY', {}, matrix.slice(1, -1).map(function (row) { + return makeBodyRow(row, readOnly); + })]; + return ['TABLE', {id:'table'}, [head, foot, body]]; + }; + + Render.asHTML = function (obj, rows, cols, readOnly) { + return Hyperjson.toDOM(toHyperjson(cellMatrix(obj, rows, cols, readOnly), readOnly)); + }; + + var diffIsInput = Render.diffIsInput = function (info) { + var nodeName = Cryptpad.find(info, ['node', 'nodeName']); + if (nodeName !== 'INPUT') { return; } + return true; + }; + + var getInputType = Render.getInputType = function (info) { + return Cryptpad.find(info, ['node', 'type']); + }; + + var preserveCursor = Render.preserveCursor = function (info) { + if (['modifyValue', 'modifyAttribute'].indexOf(info.diff.action) !== -1) { + var element = info.node; + + if (typeof(element.selectionStart) !== 'number') { return; } + + var o = info.oldValue || ''; + var n = info.newValue || ''; + var op = TextPatcher.diff(o, n); + + info.selection = ['selectionStart', 'selectionEnd'].map(function (attr) { + return TextPatcher.transformCursor(element[attr], op); + }); + } + }; + + var recoverCursor = Render.recoverCursor = function (info) { + try { + if (info.selection && info.node) { + info.node.selectionStart = info.selection[0]; + info.node.selectionEnd = info.selection[1]; + } + } catch (err) { + // FIXME LOL empty try-catch? + //console.log(info.node); + //console.error(err); + } + }; + + var diffOptions = { + preDiffApply: function (info) { + if (!diffIsInput(info)) { return; } + switch (getInputType(info)) { + case 'number': + //console.log('checkbox'); + //console.log("[preDiffApply]", info); + break; + case 'text': + preserveCursor(info); + break; + default: break; + } + }, + postDiffApply: function (info) { + if (info.selection) { recoverCursor(info); } + /* + if (!diffIsInput(info)) { return; } + switch (getInputType(info)) { + case 'checkbox': + console.log("[postDiffApply]", info); + break; + case 'text': break; + default: break; + }*/ + } + }; + + Render.updateTable = function (table, obj, conf) { + var DD = new DiffDOM(diffOptions); + + var rows = conf ? conf.rows : null; + var cols = conf ? conf.cols : null; + var readOnly = conf ? conf.readOnly : false; + var matrix = cellMatrix(obj, rows, cols, readOnly); + + var hj = toHyperjson(matrix, readOnly); + + if (!hj) { throw new Error("Expected Hyperjson!"); } + + var table2 = Hyperjson.toDOM(hj); + var patch = DD.diff(table, table2); + DD.apply(table, patch); + }; + + return Render; +}; + + return Renderer; +}); diff --git a/www/poll/app-poll.less b/www/poll/app-poll.less new file mode 100644 index 000000000..1b58841e3 --- /dev/null +++ b/www/poll/app-poll.less @@ -0,0 +1,565 @@ +@import (once) "../../customize/src/less2/include/browser.less"; +@import (once) "../../customize/src/less2/include/toolbar.less"; +@import (once) "../../customize/src/less2/include/markdown.less"; +@import (once) '../../customize/src/less2/include/fileupload.less'; +@import (once) '../../customize/src/less2/include/alertify.less'; +@import (once) '../../customize/src/less2/include/tokenfield.less'; +@import (once) '../../customize/src/less2/include/tools.less'; +@import (once) '../../customize/src/less2/include/avatar.less'; + +.toolbar_main(); +.fileupload_main(); +.alertify_main(); +.tokenfield_main(); + +@poll-fore: #555; + +@poll-th-bg: #005bef; +@poll-th-fg: #fff; +@poll-th-user-bg: darken(@poll-th-bg, 10%); +@poll-editing: lighten(@poll-th-bg, 10%); +@poll-winner: darken(@poll-th-bg, 15%); +@poll-td-bg: @poll-th-bg; +@poll-td-fg: @poll-th-fg; + +@poll-help-bg: #bbffbb; // lightgreen + +@poll-uncommitted-cell: #eee; +@poll-uncommitted-bg: #ddd; //lighten(@poll-th-bg, 50%); +@poll-uncommitted-text: black; + +@poll-placeholder: #666; +@poll-border-color: #555; +@poll-cover-color: #000; +@poll-fg: #000; +@poll-option-yellow: #ff5; +@poll-option-gray: #ccc; + +.bottom-left(@s: 5px) { border-bottom-left-radius: @s; } +.top-left(@s: 5px) { border-top-left-radius: @s; } + +display: flex; +flex-flow: column; +overflow-x: hidden; + +#cp-app-poll-content { + display: flex; + flex: 1; + min-height: 0; + #cp-app-poll-form { + flex: 1; + overflow-y: auto; + &.cp-app-poll-published { + #cp-app-poll-create-option { + display: none; + } + .cp-app-poll-table-remove[data-rt-id^="y"], .cp-app-poll-table-edit[data-rt-id^="y"] { + display: none; + } + tr.cp-app-poll-table-uncommitted { + display: none; + } + } + } +} + +.cp-app-poll-table-text-cell input[type="text"] { + width: 400px; +} + +input[type="text"], textarea { + background-color: white; + color: black; + border: 0; +} + +input[type="text"][disabled], textarea[disabled] { + background-color: transparent; + border: 0px; +} + +// The placeholder color only seems to effect Safari when not set + +input[type="text"][disabled]::placeholder { + //color: @poll-placeholder; + opacity: 1; +} + +table#cp-app-poll-table { + margin: 0px; +} +#cp-app-poll-table-container { + position: relative; + padding: 30px 0; + width: ~"calc(100% - 30px)"; +} +#cp-app-poll-table-container button { + //display: none; + border-radius: 0; + border: 0; +} +#cp-app-poll-create-user { + display: inline-flex; + height: 24px; + padding: 0; + width: 50px; + overflow: hidden; +} +#cp-app-poll-create-option { + display: inline-flex; + width: 50px; + height: 24px; + padding: 0; +} +#cp-app-poll-table-scroll { + overflow-y: hidden; + overflow-x: auto; + margin-left: ~"calc(25% + 30px)"; + max-width: ~"calc(75% - 30px - 100px - 100px)"; + width: auto; + display: inline-block; +} +#cp-app-poll-description { + &~ .CodeMirror { + margin: auto; + min-width: 80%; + width: 80%; + min-height: 200px; + border: 1px solid black; + .CodeMirror-placeholder { + color: #777; + } + } +} +#cp-app-poll-description-published { + display: none; + padding: 15px; + margin: auto; + + min-width: 80%; + width: 80%; + min-height: 7em; + color: #000; + border: 1px solid transparent; + background-color: #eeeeee; + font: @colortheme_app-font; + text-align: left; + media-tag > * { + max-width: 100%; + max-height: 20em; + } +} +.cp-app-poll-published { + #cp-app-poll-description { + display: none; + &~ .CodeMirror { + display: none; + } + } + #cp-app-poll-description-published { + display: block; + &:empty { + display: none; + } + } +} + +#cp-app-poll-help { + width: 100%; + margin: auto; + padding: 20px 10%; + background: @poll-help-bg; +} + +// from cryptpad.less + +table { + border-collapse: collapse; + border-spacing: 0; + margin: 20px; +} +tbody { + //border: 1px solid @poll-border-color; + * { + box-sizing: border-box; + } + tr { + text-align: center; + } + + td { + .tools_unselectable(); + border-right: 1px solid @poll-border-color; + padding: 12px; + padding-top: 0px; + padding-bottom: 0px; + &:last-child { + border-right: none; + } + } +} + +div.cp-app-poll-realtime { + display: block; + max-height: 100%; + max-width: 100%; + + input { + &[type="text"] { + height: 1em; + margin: 0px; + } + } + > textarea { + width: 50%; + height: 15vh; + } + + padding: 0px; + margin: 0px; + + table { + border-collapse: collapse; + width: ~"calc(100% - 1px)"; + .cp-app-poll-table-editing { + background-color: @poll-editing; + } + .cp-app-poll-table-uncommitted { + .cp-app-poll-table-cover { + background-color: @poll-uncommitted-cell !important; + } + div.cp-app-poll-table-text-cell { + background-color: @poll-uncommitted-bg !important; + color: @poll-uncommitted-text !important; + input { + width: ~"calc(100% - 80px)" !important; + vertical-align: middle; + } + } + text-align: center; + background-color: @poll-uncommitted-bg !important; + color: @poll-uncommitted-text !important; + } + tr { + height: 28px; + /* Options */ + td:first-child { + position:absolute; + left: 30px; + top: auto; + width: 25%; + } + /* Uncommitted column */ + td:nth-last-child(2) { + position: absolute; + top: auto; + width: 100px; + min-width: unset !important; + height: auto !important; + } + /* Results */ + td:last-child { + color: @poll-th-fg; + position:absolute; + top: auto; + margin-left: 100px; + width: 100px; + min-width: unset !important; + background-color: @poll-th-bg; + } + td { + padding: 0px; + margin: 0px; + + div.cp-app-poll-table-text-cell { + padding: 0px; + margin: 0px; + height: 100%; + + input { + width: 80%; + width: 90%; + height: 100%; + border: 0px; + margin: 2px; + &[disabled] { + background-color: transparent; + color: @poll-td-fg; + //font-weight: bold; + } + } + } + + &.cp-app-poll-table-checkbox-cell { + margin: 0px; + padding: 0px; + height: 100%; + min-width: 100px; + + div.cp-app-poll-table-checkbox-contain { + display: inline-block; + height: 100%; + width: 100%; + position: relative; + + label { + background-color: transparent; + display: block; + position: absolute; + top: 0px; + left: 0px; + height: 100%; + width: 100%; + } + + input { + &[type="number"] { + &:not(.editable) { + display: none; + + ~ .cp-app-poll-table-cover { + line-height: 28px; + display: block; + font-weight: bold; + height: 100%; + display: block; + + color: @poll-cover-color; + + &:after { + height: 100%; + } + + } + } + } + } + + input[type="number"][value="0"] { + ~ .cp-app-poll-table-cover { + background-color: @colortheme_cp-red; + &:after { content: "✖"; } + } + } + input[type="number"][value="1"] { + ~ .cp-app-poll-table-cover { + background-color: @colortheme_cp-green; + &:after { content: "✔"; } + } + } + input[type="number"][value="2"] { + ~ .cp-app-poll-table-cover { + background-color: @poll-option-yellow; + &:after { content: "~"; } + } + } + input[type="number"][value="3"] { + ~ .cp-app-poll-table-cover { + background-color: @poll-option-gray; + &:after { content: "?"; } + } + } + } + } + } + } + + input { + &[type="text"] { + height: auto; + width: 80%; + } + } + span { + .tools_unselectable(); + } + thead { + height: 52px; + td { + padding: 0px 5px; + background: @poll-th-bg; + color: @poll-th-fg; + &:not(:last-child) { + border-right: 1px solid rgba(255,255,255,0.2); + } + &:nth-last-child(2) { + border-right: 1px solid @poll-border-color; + } + //text-align: center; + &.cp-app-poll-table-own { + background: @poll-th-user-bg; + .cp-app-poll-table-lock { + cursor: default; + } + } + .cp-app-poll-table-buttons { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + align-items: center; + span { + cursor: pointer; + width: 1em; + text-align: center; + } + .cp-app-poll-table-bookmark { + color: darken(@poll-th-fg, 30%); + &.cp-app-poll-table-bookmark-full { + color: @poll-th-fg; + } + } + } + input { + &[type="text"] { + overflow: hidden; + text-overflow: ellipsis; + break-after: always; + width: ~"calc(100% - 2px)"; // borders... + box-sizing: border-box; + padding: 1px 5px; + margin: 1px; + &[disabled] { + color: @poll-th-fg; + } + } + } + } + } + + tbody { + td:first-child { + background: @poll-td-bg; + color: @poll-td-fg; + } + td.cp-app-poll-table-winner { + background-color: @poll-winner; + &:last-child { font-weight: bold; } + } + .cp-app-poll-table-text-cell { + input[type="text"] { + width: ~"calc(100% - 50px)"; + padding: 0 0.5em; + } + .cp-app-poll-table-edit { + float:right; + margin: 2px 10px 0 0; + } + .cp-app-poll-table-remove { + float: left; + margin: 2px 0 0 10px; + } + } + tr:not(:first-child) { + td:not(:first-child) { + label { + border-top: 1px solid @poll-border-color; + } + } + } + } + .cp-app-poll-table-edit { + //color: @poll-cover-color; + cursor: pointer; + float: left; + margin-left: 10px; + } + + thead { + tr { + th { + input[type="text"][disabled] { + background-color: transparent; + color: @poll-fore; + font-weight: bold; + } + .cp-app-poll-table-remove { + cursor: pointer; + font-size: 20px; + } + } + } + } + tbody { + tr { + td { + + } + } + } + tfoot { + display: none; + } + } + #cp-app-poll-comments { + width: 50%; + margin: 20px auto; + min-width: 400px; + padding-bottom: 5px; + button { + border-radius: 0; + } + #cp-app-poll-comments-add { + input, textarea { + border: 1px solid black; + width: 90%; + margin: 5px 5%; + } + input { + padding: 5px; + height: 26px; + &[disabled] { + background: #eee; + } + } + textarea { + padding: 5px; + height: 8em; + line-height: 1.5em; + } + button { + padding: 10px; + } + text-align: center; + } + #cp-app-poll-comments-list { + .cp-app-poll-comments-list-el { + width: 90%; + margin: 5px 5%; + } + .cp-app-poll-comments-list-msg { + display: flex; + background: #eee; + padding: 5px 10px; + .cp-app-poll-comments-list-msg-text { + flex: 1; + white-space: pre; + } + .cp-app-poll-comments-list-msg-actions { + button { + padding: 0; + width: 25px; + line-height: 20px; + } + } + } + .cp-app-poll-comments-list-data { + background: #ddd; + padding: 5px 10px; + display: flex; + align-items: center; + .cp-app-poll-comments-list-data-name { + margin-left: 10px; + flex: 1; + } + .cp-app-poll-comments-list-data-avatar { .avatar_main(30px); } + } + } + } +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; +} + diff --git a/www/poll/index.html b/www/poll/index.html index 3f4b53dbd..e3f7eacc4 100644 --- a/www/poll/index.html +++ b/www/poll/index.html @@ -1,11 +1,38 @@ - + - + CryptPad - Zero Knowledge Date Picker - + + + + - +