|
|
|
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
|
|
|
define([
|
|
|
|
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
|
|
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
|
|
|
'/bower_components/chainpad-crypto/crypto.js',
|
|
|
|
'/bower_components/textpatcher/TextPatcher.amd.js',
|
|
|
|
'json.sortify',
|
|
|
|
'/form/ula.js',
|
|
|
|
'/bower_components/chainpad-json-validator/json-ot.js',
|
|
|
|
'/bower_components/jquery/dist/jquery.min.js',
|
|
|
|
'/customize/pad.js'
|
|
|
|
], function (Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT) {
|
|
|
|
var $ = window.jQuery;
|
|
|
|
|
|
|
|
var key;
|
|
|
|
var channel = '';
|
|
|
|
var hash = false;
|
|
|
|
if (!/#/.test(window.location.href)) {
|
|
|
|
key = Crypto.genKey();
|
|
|
|
} else {
|
|
|
|
hash = window.location.hash.slice(1);
|
|
|
|
channel = hash.slice(0,32);
|
|
|
|
key = hash.slice(32);
|
|
|
|
}
|
|
|
|
|
|
|
|
var module = window.APP = {
|
|
|
|
TextPatcher: TextPatcher,
|
|
|
|
Sortify: Sortify,
|
|
|
|
Formula: Formula,
|
|
|
|
};
|
|
|
|
var initializing = true;
|
|
|
|
|
|
|
|
var uid = module.uid = Formula.uid;
|
|
|
|
|
|
|
|
var getInputType = Formula.getInputType;
|
|
|
|
var $elements = module.elements = $('input, select, textarea');
|
|
|
|
|
|
|
|
var eventsByType = Formula.eventsByType;
|
|
|
|
|
|
|
|
var Map = module.Map = {};
|
|
|
|
|
|
|
|
var UI = module.UI = {
|
|
|
|
ids: [],
|
|
|
|
each: function (f) {
|
|
|
|
UI.ids.forEach(function (id, i, list) {
|
|
|
|
if (!UI[id]) { return; }
|
|
|
|
f(UI[id], i, list);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
add: function (id, ui) {
|
|
|
|
if (UI.ids.indexOf(id) === -1) {
|
|
|
|
UI.ids.push(id);
|
|
|
|
|
|
|
|
UI[id] = ui;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// it already exists
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
remove: function (id) {
|
|
|
|
delete UI[id];
|
|
|
|
var idx = UI.ids.indexOf(id);
|
|
|
|
if (idx > -1) {
|
|
|
|
UI.ids.splice(idx, 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var cursorTypes = ['textarea', 'password', 'text'];
|
|
|
|
|
|
|
|
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
|
|
|
$elements.each(function (index, element) {
|
|
|
|
var $this = $(this);
|
|
|
|
|
|
|
|
var id = uid();
|
|
|
|
var type = getInputType($this);
|
|
|
|
|
|
|
|
// ignore hidden inputs, submit inputs, and buttons
|
|
|
|
if (['button', 'submit', 'hidden'].indexOf(type) !== -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this // give each element a uid
|
|
|
|
.data('rtform-uid', id)
|
|
|
|
// get its type
|
|
|
|
.data('rt-ui-type', type);
|
|
|
|
|
|
|
|
var component = {
|
|
|
|
id: id,
|
|
|
|
$: $this,
|
|
|
|
element: element,
|
|
|
|
type: type,
|
|
|
|
preserveCursor: cursorTypes.indexOf(type) !== -1,
|
|
|
|
name: $this.prop('name'),
|
|
|
|
};
|
|
|
|
|
|
|
|
UI.add(id, component);
|
|
|
|
|
|
|
|
component.value = (function () {
|
|
|
|
var checker = ['radio', 'checkbox'].indexOf(type) !== -1;
|
|
|
|
|
|
|
|
if (checker) {
|
|
|
|
return function (content) {
|
|
|
|
return typeof content !== 'undefined'?
|
|
|
|
$this.prop('checked', !!content):
|
|
|
|
$this.prop('checked');
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return function (content) {
|
|
|
|
return typeof content !== 'undefined' ?
|
|
|
|
$this.val(content):
|
|
|
|
typeof($this.val()) === 'string'? canonicalize($this.val()): $this.val();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}());
|
|
|
|
|
|
|
|
var update = component.update = function () { Map[id] = component.value(); };
|
|
|
|
update();
|
|
|
|
});
|
|
|
|
|
|
|
|
var config = module.config = {
|
|
|
|
initialState: Sortify(Map) || '{}',
|
|
|
|
websocketURL: Config.websocketURL,
|
|
|
|
userName: Crypto.rand64(8),
|
|
|
|
channel: channel,
|
|
|
|
cryptKey: key,
|
|
|
|
crypto: Crypto,
|
|
|
|
transformFunction: JsonOT.validate
|
|
|
|
};
|
|
|
|
|
|
|
|
var setEditable = module.setEditable = function (bool) {
|
|
|
|
/* (dis)allow editing */
|
|
|
|
$elements.each(function () {
|
|
|
|
$(this).attr('disabled', !bool);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
setEditable(false);
|
|
|
|
|
|
|
|
var onInit = config.onInit = function (info) {
|
|
|
|
var realtime = module.realtime = info.realtime;
|
|
|
|
window.location.hash = info.channel + key;
|
|
|
|
|
|
|
|
// create your patcher
|
|
|
|
module.patchText = TextPatcher.create({
|
|
|
|
realtime: realtime,
|
|
|
|
logging: true,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var readValues = function () {
|
|
|
|
UI.each(function (ui, i, list) {
|
|
|
|
Map[ui.id] = ui.value();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var onLocal = config.onLocal = function () {
|
|
|
|
if (initializing) { return; }
|
|
|
|
/* serialize local changes */
|
|
|
|
readValues();
|
|
|
|
module.patchText(Sortify(Map));
|
|
|
|
};
|
|
|
|
|
|
|
|
var updateValues = function () {
|
|
|
|
var userDoc = module.realtime.getUserDoc();
|
|
|
|
var parsed = JSON.parse(userDoc);
|
|
|
|
|
|
|
|
console.log(userDoc);
|
|
|
|
|
|
|
|
// flush received values to the map
|
|
|
|
// but only if you don't have them locally
|
|
|
|
// this *shouldn't* break cursors
|
|
|
|
Object.keys(parsed).forEach(function (key) {
|
|
|
|
if (UI.ids.indexOf(key) === -1) { Map[key] = parsed[key]; }
|
|
|
|
});
|
|
|
|
|
|
|
|
UI.each(function (ui, i, list) {
|
|
|
|
var newval = parsed[ui.id];
|
|
|
|
var oldval = ui.value();
|
|
|
|
|
|
|
|
if (typeof(newval) === 'undefined') { return; }
|
|
|
|
if (newval === oldval) { return; }
|
|
|
|
|
|
|
|
var op;
|
|
|
|
var selects;
|
|
|
|
var element = ui.element;
|
|
|
|
if (ui.preserveCursor) {
|
|
|
|
op = TextPatcher.diff(oldval, newval);
|
|
|
|
selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
|
|
|
var before = element[attr];
|
|
|
|
var after = TextPatcher.transformCursor(element[attr], op);
|
|
|
|
return after;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.value(newval);
|
|
|
|
ui.update();
|
|
|
|
|
|
|
|
if (op && ui.preserveCursor) {
|
|
|
|
//console.log(selects);
|
|
|
|
element.selectionStart = selects[0];
|
|
|
|
element.selectionEnd = selects[1];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var onRemote = config.onRemote = function (info) {
|
|
|
|
if (initializing) { return; }
|
|
|
|
/* integrate remote changes */
|
|
|
|
updateValues();
|
|
|
|
};
|
|
|
|
|
|
|
|
var onReady = config.onReady = function (info) {
|
|
|
|
updateValues();
|
|
|
|
|
|
|
|
console.log("READY");
|
|
|
|
setEditable(true);
|
|
|
|
initializing = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var onAbort = config.onAbort = function (info) {
|
|
|
|
window.alert("Network Connection Lost");
|
|
|
|
};
|
|
|
|
|
|
|
|
var rt = Realtime.start(config);
|
|
|
|
|
|
|
|
UI.each(function (ui, i, list) {
|
|
|
|
var type = ui.type;
|
|
|
|
var events = eventsByType[type];
|
|
|
|
ui.$.on(events, onLocal);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|