diff --git a/www/poll/test/index.html b/www/poll/test/index.html new file mode 100644 index 000000000..a053052d4 --- /dev/null +++ b/www/poll/test/index.html @@ -0,0 +1,50 @@ + + + + + + Zero Knowledge Date Picker + + + + + + +
+ + +
diff --git a/www/poll/test/main.js b/www/poll/test/main.js new file mode 100644 index 000000000..654dd8a00 --- /dev/null +++ b/www/poll/test/main.js @@ -0,0 +1,187 @@ +define([ + '/api/config?cb=' + Math.random().toString(16).substring(2), + '/bower_components/textpatcher/TextPatcher.js', + '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/chainpad-crypto/crypto.js', + '/common/cryptpad-common.js', + '/bower_components/hyperjson/hyperjson.js', + '/poll/test/render.js', + '/common/toolbar.js', + '/common/visible.js', + '/common/notify.js', + '/bower_components/file-saver/FileSaver.min.js', + '/bower_components/jquery/dist/jquery.min.js', + //'/customize/pad.js' +], function (Config, TextPatcher, Listmap, Crypto, Cryptpad, Hyperjson, Render, Toolbar) { + var $ = window.jQuery; + var APP = window.APP = { + Toolbar: Toolbar, + Hyperjson: Hyperjson, + Render: Render, + //$bar: $('#toolbar').css({ border: '1px solid white', background: 'grey', 'margin-bottom': '1vh', }), + }; + + var change = function (o, n, path) { + if (path && path.join) { + console.log("Change from [%s] to [%s] at [%s]", + o, n, path.join(', ')); + } + + var table = APP.$table[0]; + Render.updateTable(table, APP.proxy); + + /* 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 + */ + window.setTimeout(function () { + Render.updateTable(table, APP.proxy); + }); + }; + + var getRealtimeId = function (input) { + return input.getAttribute && input.getAttribute('data-rt-id'); + }; + + var handleInput = function (input) { + var type = input.type.toLowerCase(); + var id = getRealtimeId(input); + + switch (type) { + case 'text': + console.log("text[rt-id='%s'] [%s]", id, input.value); + Render.setValue(APP.proxy, id, input.value); + break; + case 'checkbox': + console.log("checkbox[tr-id='%s'] %s", id, input.checked); + Render.setValue(APP.proxy, id, input.checked); + break; + default: + console.log("Input[type='%s']", type); + break; + } + }; + + var handleSpan = function (span) { + var id = span.getAttribute('data-rt-id'); + var type = Render.typeofId(id); + if (type === 'row') { + Render.removeRow(APP.proxy, id, function () { + change(); + }); + } else if (type === 'col') { + Render.removeColumn(APP.proxy, id, function () { + change(); + }); + } + }; + + var handleClick = function (e) { + if (!APP.ready) { return; } + var target = e && e.target; + + if (!target) { return void console.log("NO TARGET"); } + + var nodeName = target && target.nodeName; + + switch (nodeName) { + case 'INPUT': + handleInput(target); + break; + case 'SPAN': + handleSpan(target); + break; + case undefined: + //console.error(new Error("C'est pas possible!")); + break; + default: + console.log(target, nodeName); + break; + } + }; + + var prepareProxy = function (proxy, schema) { + if (proxy && proxy.version === 1) { return; } + console.log("Configuring proxy schema..."); + + proxy.info = schema.info; + proxy.table = schema.table; + proxy.version = 1; + }; + + var ready = function (info) { + console.log("READY"); + + var proxy = APP.proxy; + + prepareProxy(proxy, Render.Example); + + var $table = APP.$table = $(Render.asHTML(proxy)); + var $createRow = APP.$createRow = $('#create-option').click(function () { + Render.createRow(proxy, function () { + change(); + }); + }); + + var $createCol = APP.$createCol = $('#create-user').click(function () { + Render.createColumn(proxy, function () { + change(); + }); + }); + + $('.realtime').append($table); + + $table + .click(handleClick) + .on('keyup', handleClick); + + proxy + .on('change', [], change) + .on('remove', [], change); + + APP.ready = true; + }; + + var secret = Cryptpad.getSecrets(); + + var create = function (info) { + var realtime = APP.realtime = info.realtime; + + var editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); + + APP.patchText = TextPatcher.create({ + realtime: realtime, + logging: true, + }); + + Cryptpad.replaceHash(editHash); + }; + + var disconnect = function () { + //setEditable(false); // TODO + //Cryptpad.alert(Messages.common_connectionLost); // TODO + }; + + var config = { + websocketURL: Cryptpad.getWebsocketURL(), + channel: secret.channel, + data: {}, + // our public key + validateKey: secret.keys.validateKey || undefined, + //readOnly: readOnly, + crypto: Crypto.createEncryptor(secret.keys), + }; + + // don't initialize until the store is ready. + Cryptpad.ready(function () { + var rt = window.rt = APP.rt = Listmap.create(config); + APP.proxy = rt.proxy; + rt.proxy + .on('create', create) + .on('ready', ready) + .on('disconnect', disconnect); + }); +}); + diff --git a/www/poll/test/render.js b/www/poll/test/render.js new file mode 100644 index 000000000..5f984703a --- /dev/null +++ b/www/poll/test/render.js @@ -0,0 +1,325 @@ +define([ + '/common/cryptpad-common.js', + '/bower_components/hyperjson/hyperjson.js', + '/bower_components/diff-dom/diffDOM.js', +], function (Cryptpad, Hyperjson) { + var DiffDOM = window.diffDOM; + + var Example = { + info: { + title: 'my title', + description: 'my description', + }, + 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 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; + }; + + 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) { + return Cryptpad.find(obj, ['table', 'cells'].concat([cellId])); + }; + + 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; + }; + + var createColumn = 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); } + }; + + var removeColumn = 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")); + } + + order.splice(idx, 1); + if (parent[id]) { delete parent[id]; } + if (typeof(cb) === 'function') { + cb(); + } + }; + + var createRow = 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); } + }; + + var removeRow = 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(); } + }; + + var setValue = 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!"); + } + }; + + var getValue = 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) { + if (typeof(obj) !== 'object') { + throw new Error('expected realtime-proxy object'); + } + + var cells = getCells(obj); + rows = rows || getRowIds(obj); + cols = cols || getColIds(obj); + + return [null].concat(rows).map(function (row, i) { + if (i === 0) { + return [null].concat(cols.map(function (col) { + return { + 'data-rt-id': col, + type: 'text', + value: getColumnValue(obj, col) || "", + }; + })); + } + + return [{ + 'data-rt-id': row, + value: getRowValue(obj, row), + type: 'text', + }].concat(cols.map(function (col) { + var id = [col, rows[i-1]].join('_'); + var val = cells[id] || false; + + var result = { + 'data-rt-id': id, + type: 'checkbox', + autocomplete: 'nope' + }; + + if (val) { result.checked = true; } + return result; + })); + }); + }; + + var makeRemoveElement = Render.makeRemoveElement = function (id) { + return ['SPAN', { 'data-rt-id': id, }, ['x']]; + }; + + var makeHeadingCell = Render.makeHeadingCell = function (cell) { + if (!cell) { return ['TD', {}, []]; } + if (cell.type === 'text') { + return ['TD', {}, [ + ['INPUT', cell, []], + makeRemoveElement(cell['data-rt-id']) + ]]; + } + return ['TD', cell, []]; + }; + + var makeBodyCell = Render.makeBodyCell = function (cell) { + if (cell.type === 'text') { + return ['TD', {}, [ + ['INPUT', cell, []], + makeRemoveElement(cell['data-rt-id']) + ]]; + } + + if (cell.type === 'checkbox') { + return ['TD', {}, [ ['INPUT', cell, []] ]]; + } + return ['TD', cell, []]; + }; + + var makeBodyRow = Render.makeBodyRow = function (row) { + return ['TR', {}, row.map(makeBodyCell)]; + }; + + var toHyperjson = Render.toHyperjson = function (matrix) { + if (!matrix || !matrix.length) { return; } + var head = ['THEAD', {}, [ ['TR', {}, matrix[0].map(makeHeadingCell)] ]]; + var body = ['TBODY', {}, matrix.slice(1).map(makeBodyRow)]; + return ['TABLE', {}, [head, body]]; + }; + + var asHTML = Render.asHTML = function (obj) { + return Hyperjson.toDOM(toHyperjson(cellMatrix(obj))); + }; + + 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 diffOptions = { + preDiffApply: function (info) { + if (!diffIsInput(info)) { return; } + switch (getInputType(info)) { + case 'checkbox': + //console.log('checkbox'); + //console.log("[preDiffApply]", info); +/* + ['modifyAttribute', + 'addAttribute', + 'removeAttribute' + ].some(function (x) { + //if (x === info.diff.action) { } + });*/ + break; + case 'text': + break; + default: break; + } + }, + postDiffApply: function (info) { + /* + if (!diffIsInput(info)) { return; } + switch (getInputType(info)) { + case 'checkbox': + console.log("[postDiffApply]", info); + break; + case 'text': break; + default: break; + }*/ + } + }; + + var updateTable = Render.updateTable = function (table, obj, conf) { + var DD = new DiffDOM(diffOptions); + + var matrix = cellMatrix(obj); + + var hj = toHyperjson(matrix); + + if (!hj) { throw new Error("Expected Hyperjson!"); } + + var table2 = Hyperjson.toDOM(hj); + var patch = DD.diff(table, table2); + DD.apply(table, patch); + }; + + return Render; +});