Merge branch 'beta' of github.com:xwiki-labs/cryptpad into beta

pull/1/head
Caleb James DeLisle 9 years ago
commit 3b6fe44378

@ -67,6 +67,7 @@ define([
userName: Crypto.rand64(8), userName: Crypto.rand64(8),
channel: channel, channel: channel,
cryptKey: key, cryptKey: key,
crypto: Crypto,
}; };
var onInit = config.onInit = function (info) { var onInit = config.onInit = function (info) {

@ -4,10 +4,9 @@ define([
var ChainPad = window.ChainPad; var ChainPad = window.ChainPad;
var JsonOT = {}; var JsonOT = {};
/* FIXME
resultOp after transform0() might be null, in which case you should return null
because it is simply a transformation which yields a "do nothing" operation */
var validate = JsonOT.validate = function (text, toTransform, transformBy) { var validate = JsonOT.validate = function (text, toTransform, transformBy) {
var DEBUG = window.REALTIME_DEBUG = window.REALTIME_DEBUG || {};
var resultOp, text2, text3; var resultOp, text2, text3;
try { try {
// text = O (mutual common ancestor) // text = O (mutual common ancestor)
@ -28,7 +27,7 @@ define([
return resultOp; return resultOp;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
var info = window.REALTIME_MODULE.ot_parseError = { var info = DEBUG.ot_parseError = {
type: 'resultParseError', type: 'resultParseError',
resultOp: resultOp, resultOp: resultOp,
@ -40,11 +39,11 @@ define([
text3: text3, text3: text3,
error: e error: e
}; };
console.log('Debugging info available at `window.REALTIME_MODULE.ot_parseError`'); console.log('Debugging info available at `window.REALTIME_DEBUG.ot_parseError`');
} }
} catch (x) { } catch (x) {
console.error(x); console.error(x);
window.REALTIME_MODULE.ot_applyError = { window.DEBUG.ot_applyError = {
type: 'resultParseError', type: 'resultParseError',
resultOp: resultOp, resultOp: resultOp,
@ -56,7 +55,7 @@ define([
text3: text3, text3: text3,
error: x error: x
}; };
console.log('Debugging info available at `window.REALTIME_MODULE.ot_applyError`'); console.log('Debugging info available at `window.REALTIME_DEBUG.ot_applyError`');
} }
// returning **null** breaks out of the loop // returning **null** breaks out of the loop

@ -1,98 +1,124 @@
/*global: WebSocket */ /*global: WebSocket */
define(() => { define(function () {
'use strict'; 'use strict';
const MAX_LAG_BEFORE_PING = 15000;
const MAX_LAG_BEFORE_DISCONNECT = 30000;
const PING_CYCLE = 5000;
const REQUEST_TIMEOUT = 30000;
const now = () => new Date().getTime(); var MAX_LAG_BEFORE_PING = 15000;
var MAX_LAG_BEFORE_DISCONNECT = 30000;
var PING_CYCLE = 5000;
var REQUEST_TIMEOUT = 30000;
const networkSendTo = (ctx, peerId, content) => { var now = function now() {
const seq = ctx.seq++; return new Date().getTime();
};
var networkSendTo = function networkSendTo(ctx, peerId, content) {
var seq = ctx.seq++;
ctx.ws.send(JSON.stringify([seq, 'MSG', peerId, content])); ctx.ws.send(JSON.stringify([seq, 'MSG', peerId, content]));
return new Promise((res, rej) => { return new Promise(function (res, rej) {
ctx.requests[seq] = { reject: rej, resolve: res, time: now() }; ctx.requests[seq] = { reject: rej, resolve: res, time: now() };
}); });
}; };
const channelBcast = (ctx, chanId, content) => { var channelBcast = function channelBcast(ctx, chanId, content) {
const chan = ctx.channels[chanId]; var chan = ctx.channels[chanId];
if (!chan) { throw new Error("no such channel " + chanId); } if (!chan) {
const seq = ctx.seq++; throw new Error("no such channel " + chanId);
}
var seq = ctx.seq++;
ctx.ws.send(JSON.stringify([seq, 'MSG', chanId, content])); ctx.ws.send(JSON.stringify([seq, 'MSG', chanId, content]));
return new Promise((res, rej) => { return new Promise(function (res, rej) {
ctx.requests[seq] = { reject: rej, resolve: res, time: now() }; ctx.requests[seq] = { reject: rej, resolve: res, time: now() };
}); });
}; };
const channelLeave = (ctx, chanId, reason) => { var channelLeave = function channelLeave(ctx, chanId, reason) {
const chan = ctx.channels[chanId]; var chan = ctx.channels[chanId];
if (!chan) { throw new Error("no such channel " + chanId); } if (!chan) {
throw new Error("no such channel " + chanId);
}
delete ctx.channels[chanId]; delete ctx.channels[chanId];
ctx.ws.send(JSON.stringify([ctx.seq++, 'LEAVE', chanId, reason])); ctx.ws.send(JSON.stringify([ctx.seq++, 'LEAVE', chanId, reason]));
}; };
const makeEventHandlers = (ctx, mappings) => { var makeEventHandlers = function makeEventHandlers(ctx, mappings) {
return (name, handler) => { return function (name, handler) {
const handlers = mappings[name]; var handlers = mappings[name];
if (!handlers) { throw new Error("no such event " + name); } if (!handlers) {
throw new Error("no such event " + name);
}
handlers.push(handler); handlers.push(handler);
}; };
}; };
const mkChannel = (ctx, id) => { var mkChannel = function mkChannel(ctx, id) {
const internal = { var internal = {
onMessage: [], onMessage: [],
onJoin: [], onJoin: [],
onLeave: [], onLeave: [],
members: [], members: [],
jSeq: ctx.seq++ jSeq: ctx.seq++
}; };
const chan = { var chan = {
_: internal, _: internal,
time: now(),
id: id, id: id,
members: internal.members, members: internal.members,
bcast: (msg) => channelBcast(ctx, chan.id, msg), bcast: function bcast(msg) {
leave: (reason) => channelLeave(ctx, chan.id, reason), return channelBcast(ctx, chan.id, msg);
on: makeEventHandlers(ctx, { message: },
internal.onMessage, join: internal.onJoin, leave: internal.onLeave }) leave: function leave(reason) {
return channelLeave(ctx, chan.id, reason);
},
on: makeEventHandlers(ctx, { message: internal.onMessage, join: internal.onJoin, leave: internal.onLeave })
}; };
ctx.requests[internal.jSeq] = chan; ctx.requests[internal.jSeq] = chan;
ctx.ws.send(JSON.stringify([internal.jSeq, 'JOIN', id])); ctx.ws.send(JSON.stringify([internal.jSeq, 'JOIN', id]));
return new Promise((res, rej) => { return new Promise(function (res, rej) {
chan._.resolve = res; chan._.resolve = res;
chan._.reject = rej; chan._.reject = rej;
}) });
}; };
const mkNetwork = (ctx) => { var mkNetwork = function mkNetwork(ctx) {
const network = { var network = {
webChannels: ctx.channels, webChannels: ctx.channels,
getLag: () => (ctx.lag), getLag: function getLag() {
sendto: (peerId, content) => (networkSendTo(ctx, peerId, content)), return ctx.lag;
join: (chanId) => (mkChannel(ctx, chanId)), },
sendto: function sendto(peerId, content) {
return networkSendTo(ctx, peerId, content);
},
join: function join(chanId) {
return mkChannel(ctx, chanId);
},
on: makeEventHandlers(ctx, { message: ctx.onMessage, disconnect: ctx.onDisconnect }) on: makeEventHandlers(ctx, { message: ctx.onMessage, disconnect: ctx.onDisconnect })
}; };
network.__defineGetter__("webChannels", () => { network.__defineGetter__("webChannels", function () {
return Object.keys(ctx.channels).map((k) => (ctx.channels[k])); return Object.keys(ctx.channels).map(function (k) {
return ctx.channels[k];
});
}); });
return network; return network;
}; };
const onMessage = (ctx, evt) => { var onMessage = function onMessage(ctx, evt) {
let msg; var msg = void 0;
try { msg = JSON.parse(evt.data); } catch (e) { console.log(e.stack); return; } try {
msg = JSON.parse(evt.data);
} catch (e) {
console.log(e.stack);return;
}
if (msg[0] !== 0) { if (msg[0] !== 0) {
const req = ctx.requests[msg[0]]; var req = ctx.requests[msg[0]];
if (!req) { if (!req) {
console.log("error: " + JSON.stringify(msg)); console.log("error: " + JSON.stringify(msg));
return; return;
} }
delete ctx.requests[msg[0]]; delete ctx.requests[msg[0]];
if (msg[1] === 'ACK') { if (msg[1] === 'ACK') {
if (req.ping) { // ACK of a PING if (req.ping) {
// ACK of a PING
ctx.lag = now() - Number(req.ping); ctx.lag = now() - Number(req.ping);
return; return;
} }
@ -100,7 +126,9 @@ const onMessage = (ctx, evt) => {
} else if (msg[1] === 'JACK') { } else if (msg[1] === 'JACK') {
if (req._) { if (req._) {
// Channel join request... // Channel join request...
if (!msg[2]) { throw new Error("wrong type of ACK for channel join"); } if (!msg[2]) {
throw new Error("wrong type of ACK for channel join");
}
req.id = msg[2]; req.id = msg[2];
ctx.channels[req.id] = req; ctx.channels[req.id] = req;
return; return;
@ -113,14 +141,17 @@ const onMessage = (ctx, evt) => {
} }
return; return;
} }
if (msg[2] === 'IDENT') { if (msg[2] === 'IDENT') {
ctx.uid = msg[3]; ctx.uid = msg[3];
setInterval(() => { setInterval(function () {
if (now() - ctx.timeOfLastMessage < MAX_LAG_BEFORE_PING) { return; } if (now() - ctx.timeOfLastMessage < MAX_LAG_BEFORE_PING) {
let seq = ctx.seq++; return;
let currentDate = now(); }
ctx.requests[seq] = {time: now(), ping: currentDate}; var seq = ctx.seq++;
var currentDate = now();
ctx.requests[seq] = { time: now(), ping: currentDate };
ctx.ws.send(JSON.stringify([seq, 'PING', currentDate])); ctx.ws.send(JSON.stringify([seq, 'PING', currentDate]));
if (now() - ctx.timeOfLastMessage > MAX_LAG_BEFORE_DISCONNECT) { if (now() - ctx.timeOfLastMessage > MAX_LAG_BEFORE_DISCONNECT) {
ctx.ws.close(); ctx.ws.close();
@ -139,57 +170,69 @@ const onMessage = (ctx, evt) => {
} }
if (msg[2] === 'MSG') { if (msg[2] === 'MSG') {
let handlers; var handlers = void 0;
if (msg[3] === ctx.uid) { if (msg[3] === ctx.uid) {
handlers = ctx.onMessage; handlers = ctx.onMessage;
} else { } else {
const chan = ctx.channels[msg[3]]; var chan = ctx.channels[msg[3]];
if (!chan) { if (!chan) {
console.log("message to non-existant chan " + JSON.stringify(msg)); console.log("message to non-existant chan " + JSON.stringify(msg));
return; return;
} }
handlers = chan._.onMessage; handlers = chan._.onMessage;
} }
handlers.forEach((h) => { handlers.forEach(function (h) {
try { h(msg[4], msg[1]); } catch (e) { console.error(e); } try {
h(msg[4], msg[1]);
} catch (e) {
console.error(e);
}
}); });
} }
if (msg[2] === 'LEAVE') { if (msg[2] === 'LEAVE') {
const chan = ctx.channels[msg[3]]; var _chan = ctx.channels[msg[3]];
if (!chan) { if (!_chan) {
console.log("leaving non-existant chan " + JSON.stringify(msg)); console.log("leaving non-existant chan " + JSON.stringify(msg));
return; return;
} }
chan._.onLeave.forEach((h) => { _chan._.onLeave.forEach(function (h) {
try { h(msg[1], msg[4]); } catch (e) { console.log(e.stack); } try {
h(msg[1], msg[4]);
} catch (e) {
console.log(e.stack);
}
}); });
} }
if (msg[2] === 'JOIN') { if (msg[2] === 'JOIN') {
const chan = ctx.channels[msg[3]]; var _chan2 = ctx.channels[msg[3]];
if (!chan) { if (!_chan2) {
console.log("ERROR: join to non-existant chan " + JSON.stringify(msg)); console.log("ERROR: join to non-existant chan " + JSON.stringify(msg));
return; return;
} }
// have we yet fully joined the chan? // have we yet fully joined the chan?
const synced = (chan._.members.indexOf(ctx.uid) !== -1); var synced = _chan2._.members.indexOf(ctx.uid) !== -1;
chan._.members.push(msg[1]); _chan2._.members.push(msg[1]);
if (!synced && msg[1] === ctx.uid) { if (!synced && msg[1] === ctx.uid) {
// sync the channel join event // sync the channel join event
chan.myID = ctx.uid; _chan2.myID = ctx.uid;
chan._.resolve(chan); _chan2._.resolve(_chan2);
} }
if (synced) { if (synced) {
chan._.onJoin.forEach((h) => { _chan2._.onJoin.forEach(function (h) {
try { h(msg[1]); } catch (e) { console.log(e.stack); } try {
h(msg[1]);
} catch (e) {
console.log(e.stack);
}
}); });
} }
} }
}; };
const connect = (websocketURL) => { var connect = function connect(websocketURL) {
let ctx = { var ctx = {
ws: new WebSocket(websocketURL), ws: new WebSocket(websocketURL),
seq: 1, seq: 1,
lag: 0, lag: 0,
@ -200,26 +243,49 @@ const connect = (websocketURL) => {
onDisconnect: [], onDisconnect: [],
requests: {} requests: {}
}; };
setInterval(() => { setInterval(function () {
for (let id in ctx.requests) { for (var id in ctx.requests) {
const req = ctx.requests[id]; var req = ctx.requests[id];
if (now() - req.time > REQUEST_TIMEOUT) { if (now() - req.time > REQUEST_TIMEOUT) {
delete ctx.requests[id]; delete ctx.requests[id];
if(typeof req.reject === "function") { req.reject({ type: 'TIMEOUT', message: 'waited ' + now() - req.time + 'ms' }); } if (typeof req.reject === "function") {
req.reject({ type: 'TIMEOUT', message: 'waited ' + (now() - req.time) + 'ms' });
}
} }
} }
}, 5000); }, 5000);
ctx.network = mkNetwork(ctx); ctx.network = mkNetwork(ctx);
ctx.ws.onmessage = (msg) => (onMessage(ctx, msg)); ctx.ws.onmessage = function (msg) {
ctx.ws.onclose = (evt) => { return onMessage(ctx, msg);
ctx.onDisconnect.forEach((h) => { };
try { h(evt.reason); } catch (e) { console.log(e.stack); } ctx.ws.onclose = function (evt) {
ctx.onDisconnect.forEach(function (h) {
try {
h(evt.reason);
} catch (e) {
console.log(e.stack);
}
}); });
}; };
return new Promise((resolve, reject) => { return new Promise(function (resolve, reject) {
ctx.ws.onopen = () => resolve(ctx.network); ctx.ws.onopen = function () {
var count = 0;
var interval = 100;
var checkIdent = function() {
if(ctx.uid !== null) {
return resolve(ctx.network);
}
else {
if(count * interval > REQUEST_TIMEOUT) {
return reject({ type: 'TIMEOUT', message: 'waited ' + (count * interval) + 'ms' });
}
setTimeout(checkIdent, 100);
}
}
checkIdent();
};
}); });
}; };
return { connect: connect }; return { connect: connect };
}); });

@ -15,16 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
define([ define([
'/common/messages.js',
'/common/netflux-client.js', '/common/netflux-client.js',
'/common/crypto.js',
'/common/es6-promise.min.js', '/common/es6-promise.min.js',
'/common/chainpad.js', '/common/chainpad.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
], function (Messages, Netflux, Crypto) { ], function (Netflux) {
var $ = window.jQuery; var $ = window.jQuery;
var ChainPad = window.ChainPad; var ChainPad = window.ChainPad;
var PARANOIA = true; var PARANOIA = true;
var USE_HISTORY = true;
var module = { exports: {} }; var module = { exports: {} };
/** /**
@ -44,14 +43,14 @@ define([
var websocketUrl = config.websocketURL; var websocketUrl = config.websocketURL;
var userName = config.userName; var userName = config.userName;
var channel = config.channel; var channel = config.channel;
var chanKey = config.cryptKey; var chanKey = config.cryptKey || '';
var Crypto = config.crypto;
var cryptKey = Crypto.parseKey(chanKey).cryptKey; var cryptKey = Crypto.parseKey(chanKey).cryptKey;
var passwd = 'y'; var passwd = 'y';
// make sure configuration is defined // make sure configuration is defined
config = config || {}; config = config || {};
var allMessages = [];
var initializing = true; var initializing = true;
var recoverableErrorCount = 0; // unused var recoverableErrorCount = 0; // unused
var toReturn = {}; var toReturn = {};
@ -99,6 +98,11 @@ define([
}; };
var onReady = function(wc, network) { var onReady = function(wc, network) {
if(config.setMyID) {
config.setMyID({
myID: wc.myID
});
}
// Trigger onJoining with our own Cryptpad username to tell the toolbar that we are synced // Trigger onJoining with our own Cryptpad username to tell the toolbar that we are synced
onJoining(wc.myID); onJoining(wc.myID);
@ -127,11 +131,10 @@ define([
var message = chainpadAdapter.msgIn(peer, msg); var message = chainpadAdapter.msgIn(peer, msg);
verbose(message); verbose(message);
allMessages.push(message);
if (!initializing) { if (!initializing) {
if (toReturn.onLocal) { if (config.onLocal) {
toReturn.onLocal(); config.onLocal();
} }
} }
// pass the message into Chainpad // pass the message into Chainpad
@ -205,11 +208,6 @@ define([
wc.on('join', onJoining); wc.on('join', onJoining);
wc.on('leave', onLeaving); wc.on('leave', onLeaving);
if(config.setMyID) {
config.setMyID({
myID: wc.myID
});
}
// Open a Chainpad session // Open a Chainpad session
realtime = createRealtime(); realtime = createRealtime();
@ -249,14 +247,22 @@ define([
}); });
// Get the channel history // Get the channel history
if(USE_HISTORY) {
var hc; var hc;
wc.members.forEach(function (p) { wc.members.forEach(function (p) {
if (p.length === 16) { hc = p; } if (p.length === 16) { hc = p; }
}); });
wc.history_keeper = hc; wc.history_keeper = hc;
if (hc) { network.sendto(hc, JSON.stringify(['GET_HISTORY', wc.id])); } if (hc) { network.sendto(hc, JSON.stringify(['GET_HISTORY', wc.id])); }
}
realtime.start(); realtime.start();
if(!USE_HISTORY) {
onReady(wc, network);
}
}; };
var findChannelById = function(webChannels, channelId) { var findChannelById = function(webChannels, channelId) {

@ -11,33 +11,59 @@
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
} }
form {
border: 3px solid black;
border-radius: 5px;
padding: 15px;
font-weight: bold !important;
font-size: 18px !important;
}
input[type="text"],
input[type="password"],
input[type="number"],
input[type="range"],
select
{
margin-top: 5px;
margin-bottom: 5px;
width: 80%;
}
textarea {
width: 80%;
height: 40vh;
font-weight: bold;
font-size: 18px;
}
</style> </style>
</head> </head>
<body> <body>
<form> <form>
<input type="text" name="text"><br> <input type="radio" name="radio" value="one" checked>One
<input type="password" name="password"><br> <input type="radio" name="radio" value="two">Two
<input type="radio" name="radio" value="one" checked>One<br>
<input type="radio" name="radio" value="two">Two<br>
<input type="radio" name="radio" value="three">Three<br> <input type="radio" name="radio" value="three">Three<br>
<input type="checkbox" name="checkbox1" value="1">Checkbox One<br> <input type="checkbox" name="checkbox1" value="1">Checkbox One
<input type="checkbox" name="checkbox2" value="2">Checkbox Two<br> <input type="checkbox" name="checkbox2" value="2">Checkbox Two<br>
<input type="number" name="number" min="1" max="5">Number<br> <input type="text" name="text" placeholder="Text Input"><br>
<input type="password" name="password" placeholder="Passwords"><br>
<input type="number" name="number" min="1" max="5" placeholder="Numbers">Number<br>
<input type="range" name="range" min="0" max="10">Ranges<br> <input type="range" name="range" min="0" max="100">Ranges<br>
<select> <select name="select">
<option value="one">One</option> <option value="one">One</option>
<option value="two">Two</option> <option value="two">Two</option>
<option value="three">Three</option> <option value="three">Three</option>
<option value="four">Four</option> <option value="four">Four</option>
</select> Dropdowns<br> </select> Dropdowns<br>
<textarea rows="4" cols="50"> </textarea><br> <textarea name="textarea"></textarea><br>
</form> </form>

@ -1,80 +1,201 @@
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([ define([
'/api/config?cb=' + Math.random().toString(16).substring(2), '/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/RealtimeTextarea.js', '/common/realtime-input.js',
'/common/messages.js',
'/common/crypto.js', '/common/crypto.js',
'/common/TextPatcher.js', '/common/TextPatcher.js',
'json.sortify',
'/form/ula.js',
'/common/json-ot.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js' '/customize/pad.js'
], function (Config, Realtime, Messages, Crypto, TextPatcher) { ], function (Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT) {
var $ = window.jQuery; var $ = window.jQuery;
$(window).on('hashchange', function() {
window.location.reload(); var key;
}); var channel = '';
if (window.location.href.indexOf('#') === -1) { var hash = false;
window.location.href = window.location.href + '#' + Crypto.genKey(); if (!/#/.test(window.location.href)) {
return; key = Crypto.genKey();
} else {
hash = window.location.hash.slice(1);
channel = hash.slice(0,32);
key = hash.slice(32);
} }
var module = window.APP = {}; var module = window.APP = {
var key = Crypto.parseKey(window.location.hash.substring(1)); TextPatcher: TextPatcher,
Sortify: Sortify,
Formula: Formula,
};
var initializing = true; var initializing = true;
/* elements that we need to listen to */ var uid = module.uid = Formula.uid;
/*
* text var getInputType = Formula.getInputType;
* password var $elements = module.elements = $('input, select, textarea')
* radio
* checkbox
* number
* range
* select
* textarea
*/
var $textarea = $('textarea'); var eventsByType = Formula.eventsByType;
var Map = module.Map = {};
var UI = module.UI = {
ids: [],
each: function (f) {
UI.ids.forEach(function (id, i, list) {
f(UI[id], i, list);
});
}
};
var cursorTypes = ['textarea', 'password', 'text'];
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
$elements.each(function (element) {
var $this = $(this);
var id = uid();
var type = getInputType($this);
$this // give each element a uid
.data('rtform-uid', id)
// get its type
.data('rt-ui-type', type);
UI.ids.push(id);
var component = UI[id] = {
id: id,
$: $this,
element: element,
type: type,
preserveCursor: cursorTypes.indexOf(type) !== -1,
name: $this.prop('name'),
};
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):
canonicalize($this.val());
};
}
}());
var update = component.update = function () { Map[id] = component.value(); };
update();
});
var config = module.config = { var config = module.config = {
websocketURL: Config.websocketURL + '_old', initialState: Sortify(Map) || '{}',
websocketURL: Config.websocketURL,
userName: Crypto.rand64(8), userName: Crypto.rand64(8),
channel: key.channel, channel: channel,
cryptKey: key.cryptKey cryptKey: key,
crypto: Crypto,
transformFunction: JsonOT.validate
}; };
var setEditable = function (bool) {/* allow editing */}; var setEditable = module.setEditable = function (bool) {
var canonicalize = function (text) {/* canonicalize all the things */}; /* (dis)allow editing */
$elements.each(function () {
$(this).attr('disabled', !bool);
});
};
setEditable(false); setEditable(false);
var onInit = config.onInit = function (info) { }; var onInit = config.onInit = function (info) {
var realtime = module.realtime = info.realtime;
window.location.hash = info.channel + key;
var onRemote = config.onRemote = function (info) { // create your patcher
if (initializing) { return; } module.patchText = TextPatcher.create({
/* integrate remote changes */ realtime: realtime,
logging: true,
});
}; };
var onLocal = config.onLocal = function () { var onLocal = config.onLocal = function () {
if (initializing) { return; } if (initializing) { return; }
/* serialize local changes */ /* serialize local changes */
readValues();
module.patchText(Sortify(Map));
}; };
var onReady = config.onReady = function (info) { var readValues = function () {
var realtime = module.realtime = info.realtime; UI.each(function (ui, i, list) {
Map[ui.id] = ui.value();
});
};
// create your patcher var updateValues = function () {
module.patchText = TextPatcher.create({ var userDoc = module.realtime.getUserDoc();
realtime: realtime var parsed = JSON.parse(userDoc);
console.log(userDoc);
UI.each(function (ui, i, list) {
var newval = parsed[ui.id];
var oldval = ui.value();
if (newval === oldval) { return; }
var op;
var element = ui.element;
if (ui.preserveCursor) {
op = TextPatcher.diff(oldval, newval);
var 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) {
console.log(selects);
element.selectionStart = selects[0];
element.selectionEnd = selects[1];
}
}); });
};
var onRemote = config.onRemote = function (info) {
if (initializing) { return; }
/* integrate remote changes */
updateValues();
};
// get ready var onReady = config.onReady = function (info) {
updateValues();
console.log("READY");
setEditable(true); setEditable(true);
initializing = false; initializing = false;
}; };
var onAbort = config.onAbort = function (info) {}; var onAbort = config.onAbort = function (info) {
window.alert("Network Connection Lost");
};
var rt = Realtime.start(config); var rt = Realtime.start(config);
// bind to events... UI.each(function (ui, i, list) {
var type = ui.type;
var events = eventsByType[type];
ui.$.on(events, onLocal);
});
}); });

@ -0,0 +1,14 @@
```Javascript
/* elements that we need to listen to */
/*
* text => $(text).val()
* password => $(password).val()
* radio => $(radio).prop('checked')
* checkbox => $(checkbox).prop('checked')
* number => $(number).val() // returns string, no default
* range => $(range).val()
* select => $(select).val()
* textarea => $(textarea).val()
*/
```

@ -0,0 +1,24 @@
define([], function () {
var ula = {};
var uid = ula.uid = (function () {
var i = 0;
var prefix = 'rt_';
return function () { return prefix + i++; };
}());
ula.getInputType = function ($el) { return $el[0].type; };
ula.eventsByType = {
text: 'change keyup',
password: 'change keyup',
radio: 'change click',
checkbox: 'change click',
number: 'change',
range: 'keyup change',
'select-one': 'change',
textarea: 'change keyup',
};
return ula;
});

@ -6,10 +6,6 @@ define([
'/bower_components/jquery/dist/jquery.min.js' '/bower_components/jquery/dist/jquery.min.js'
], function (Config, Realtime, Crypto, TextPatcher) { ], function (Config, Realtime, Crypto, TextPatcher) {
var $ = window.jQuery; var $ = window.jQuery;
/*
$(window).on('hashchange', function() {
window.location.reload();
});*/
var key; var key;
var channel = ''; var channel = '';
@ -44,6 +40,7 @@ define([
userName: userName, userName: userName,
channel: channel, channel: channel,
cryptKey: key, cryptKey: key,
crypto: Crypto,
}; };
var initializing = true; var initializing = true;
@ -54,6 +51,7 @@ define([
var onInit = config.onInit = function (info) { var onInit = config.onInit = function (info) {
window.location.hash = info.channel + key; window.location.hash = info.channel + key;
$(window).on('hashchange', function() { window.location.reload(); });
}; };
var onRemote = config.onRemote = function (info) { var onRemote = config.onRemote = function (info) {

@ -194,9 +194,6 @@ define([
var now = function () { return new Date().getTime(); }; var now = function () { return new Date().getTime(); };
var realtimeOptions = { var realtimeOptions = {
// configuration :D
doc: inner,
// provide initialstate... // provide initialstate...
initialState: stringifyDOM(inner) || '{}', initialState: stringifyDOM(inner) || '{}',
@ -213,7 +210,9 @@ define([
channel: key.channel, channel: key.channel,
// encryption key // encryption key
cryptKey: key.cryptKey cryptKey: key.cryptKey,
crypto: Crypto,
}; };
var DD = new DiffDom(diffOptions); var DD = new DiffDom(diffOptions);

@ -87,7 +87,6 @@ define([
} }
var fixThings = false; var fixThings = false;
// var key = Crypto.parseKey(window.location.hash.substring(1));
var editor = window.editor = Ckeditor.replace('editor1', { var editor = window.editor = Ckeditor.replace('editor1', {
// https://dev.ckeditor.com/ticket/10907 // https://dev.ckeditor.com/ticket/10907
needsBrFiller: fixThings, needsBrFiller: fixThings,
@ -184,7 +183,6 @@ define([
} }
}; };
var now = function () { return new Date().getTime(); };
var initializing = true; var initializing = true;
var userList = {}; // List of pretty name of all users (mapped with their server ID) var userList = {}; // List of pretty name of all users (mapped with their server ID)
@ -250,12 +248,29 @@ define([
// our encryption key // our encryption key
cryptKey: key, cryptKey: key,
// method which allows us to get the id of the user
setMyID: setMyID, setMyID: setMyID,
// Crypto object to avoid loading it twice in Cryptpad
crypto: Crypto,
// really basic operational transform // really basic operational transform
transformFunction : JsonOT.validate transformFunction : JsonOT.validate
}; };
var updateUserList = function(shjson) {
// Extract the user list (metadata) from the hyperjson
var hjson = JSON.parse(shjson);
var peerUserList = hjson[3];
if(peerUserList && peerUserList.metadata) {
var userData = peerUserList.metadata;
// Update the local user data
addToUserList(userData);
hjson.pop();
}
return hjson;
};
var onRemote = realtimeOptions.onRemote = function (info) { var onRemote = realtimeOptions.onRemote = function (info) {
if (initializing) { return; } if (initializing) { return; }
@ -265,18 +280,7 @@ define([
cursor.update(); cursor.update();
// Extract the user list (metadata) from the hyperjson // Extract the user list (metadata) from the hyperjson
var hjson = JSON.parse(shjson); var hjson = updateUserList(shjson);
var peerUserList = hjson[hjson.length-1];
if(peerUserList.metadata) {
var userData = peerUserList.metadata;
// Update the local user data
userList = userData;
// Send the new data to the toolbar
if(toolbarList && typeof toolbarList.onChange === "function") {
toolbarList.onChange(userList);
}
hjson.pop();
}
// build a dom from HJSON, diff, and patch the editor // build a dom from HJSON, diff, and patch the editor
applyHjson(shjson); applyHjson(shjson);
@ -309,7 +313,7 @@ define([
var onReady = realtimeOptions.onReady = function (info) { var onReady = realtimeOptions.onReady = function (info) {
module.patchText = TextPatcher.create({ module.patchText = TextPatcher.create({
realtime: info.realtime, realtime: info.realtime,
logging: false, logging: true,
}); });
module.realtime = info.realtime; module.realtime = info.realtime;
@ -338,7 +342,7 @@ define([
// append the userlist to the hyperjson structure // append the userlist to the hyperjson structure
if(Object.keys(myData).length > 0) { if(Object.keys(myData).length > 0) {
hjson[hjson.length] = {metadata: userList}; hjson[3] = {metadata: userList};
} }
// stringify the json and send it into chainpad // stringify the json and send it into chainpad
var shjson = stringify(hjson); var shjson = stringify(hjson);

@ -206,14 +206,12 @@ define([
// our encryption key // our encryption key
cryptKey: key, cryptKey: key,
// configuration :D
doc: inner,
setMyID: setMyID, setMyID: setMyID,
// really basic operational transform // really basic operational transform
transformFunction : JsonOT.validate transformFunction : JsonOT.validate,
// pass in websocket/netflux object TODO
crypto: Crypto,
}; };
var onRemote = realtimeOptions.onRemote = function (info) { var onRemote = realtimeOptions.onRemote = function (info) {

@ -8,7 +8,6 @@
</head> </head>
<body> <body>
<a id="edit" href="#" target="_blank">Edit this document's style</a> <a id="edit" href="#" target="_blank">Edit this document's style</a>
<textarea id="css" style="display:none !important;"></textarea>
<h1>HTML Ipsum Presents</h1> <h1>HTML Ipsum Presents</h1>

@ -1,31 +1,37 @@
define([ define([
'/api/config?cb=' + Math.random().toString(16).substring(2), '/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/realtime-input.js', '/common/realtime-input.js',
'/common/messages.js',
'/common/crypto.js', '/common/crypto.js',
'/common/TextPatcher.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js' '/customize/pad.js'
], function (Config, Realtime, Messages, Crypto) { ], function (Config, Realtime, Crypto, TextPatcher) {
// TODO consider adding support for less.js // TODO consider adding support for less.js
var $ = window.jQuery; var $ = window.jQuery;
$(window).on('hashchange', function() {
window.location.reload();
});
var userName = Crypto.rand64(8); var $style = $('style').first(),
$edit = $('#edit');
var module = window.APP = {};
if (window.location.href.indexOf('#') === -1) { var key;
window.location.href = window.location.href + '#' + Crypto.genKey(); var channel = '';
return; if (!/#/.test(window.location.href)) {
key = Crypto.genKey();
} else {
var hash = window.location.hash.slice(1);
channel = hash.slice(0, 32);
key = hash.slice(32);
} }
var key = Crypto.parseKey(window.location.hash.slice(1)); var config = {
websocketURL: Config.websocketURL,
var $style = $('style').first(), channel: channel,
$css = $('#css'), cryptKey: key,
$edit = $('#edit'); crypto: Crypto,
};
$edit.attr('href', '/text/'+ window.location.hash); var userName = module.userName = config.userName = Crypto.rand64(8);
var lazyDraw = (function () { var lazyDraw = (function () {
var to, var to,
@ -38,29 +44,45 @@ define([
}; };
}()); }());
var draw = function () { var draw = function (content) { lazyDraw(content); };
lazyDraw($css.val());
var initializing = true;
var onInit = config.onInit = function (info) {
window.location.hash = info.channel + key;
var realtime = module.realtime = info.realtime;
module.patchText = TextPatcher.create({
realtime: realtime,
logging: true,
});
$(window).on('hashchange', function() {
window.location.reload();
});
}; };
$css // set the initial value var onReady = config.onReady = function (info) {
.val($style.text()) var userDoc = module.realtime.getUserDoc();
.on('change', draw); draw(userDoc);
console.log("Ready");
initializing = false;
};
var rts = $('textarea').toArray().map(function (e, i) { var onRemote = config.onRemote = function () {
draw(module.realtime.getUserDoc());
};
var config = { var onAbort = config.onAbort = function (info) {
onRemote: draw, // notify the user of the abort
onInit: draw, window.alert("Network Connection Lost");
onReady: draw, };
textarea: e, var onLocal = config.onLocal = function () {
websocketURL: Config.websocketURL, // nope
userName: userName,
channel: key.channel,
cryptKey: key.cryptKey
}; };
$edit.attr('href', '/text/'+ window.location.hash);
var rt = Realtime.start(config); var rt = Realtime.start(config);
return rt;
});
}); });

@ -1,22 +1,12 @@
define([ define([
'/api/config?cb=' + Math.random().toString(16).substring(2), '/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/realtime-input.js', '/common/realtime-input.js',
'/common/messages.js',
'/common/crypto.js', '/common/crypto.js',
'/common/TextPatcher.js', '/common/TextPatcher.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js' '/customize/pad.js'
], function (Config, Realtime, Messages, Crypto, TextPatcher) { ], function (Config, Realtime, Crypto, TextPatcher) {
var $ = window.jQuery; var $ = window.jQuery;
$(window).on('hashchange', function() {
window.location.reload();
});
/*
if (window.location.href.indexOf('#') === -1) {
window.location.href = window.location.href + '#' + Crypto.genKey();
return;
}*/
var key; var key;
var channel = ''; var channel = '';
@ -42,6 +32,7 @@ define([
userName: userName, userName: userName,
channel: channel, channel: channel,
cryptKey: key, cryptKey: key,
crypto: Crypto,
}; };
var setEditable = function (bool) { $textarea.attr('disabled', !bool); }; var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
@ -51,6 +42,9 @@ define([
var onInit = config.onInit = function (info) { var onInit = config.onInit = function (info) {
window.location.hash = info.channel + key; window.location.hash = info.channel + key;
$(window).on('hashchange', function() {
window.location.reload();
});
}; };
var onRemote = config.onRemote = function (info) { var onRemote = config.onRemote = function (info) {

Loading…
Cancel
Save