You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/_socket/main.js

267 lines
10 KiB
JavaScript

9 years ago
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/messages.js',
'/common/crypto.js',
'/_socket/realtime-input.js',
'/common/hyperjson.js',
'/common/hyperscript.js',
9 years ago
'/_socket/toolbar.js',
'/common/cursor.js',
'/common/json-ot.js',
'/_socket/typingTest.js',
9 years ago
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js'
], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest) {
9 years ago
var $ = window.jQuery;
var ifrw = $('#pad-iframe')[0].contentWindow;
var Ckeditor; // to be initialized later...
var DiffDom = window.diffDOM;
var hjsonToDom = function (H) {
return Hyperjson.callOn(H, Hyperscript);
};
9 years ago
var userName = Crypto.rand64(8),
toolbar;
var module = {};
var isNotMagicLine = function (el) {
// factor as:
// return !(el.tagName === 'SPAN' && el.contentEditable === 'false');
var filter = (el.tagName === 'SPAN' && el.contentEditable === 'false');
if (filter) {
console.log("[hyperjson.serializer] prevented an element" +
"from being serialized:", el);
return false;
}
return true;
};
var andThen = function (Ckeditor) {
$(window).on('hashchange', function() {
window.location.reload();
});
if (window.location.href.indexOf('#') === -1) {
window.location.href = window.location.href + '#' + Crypto.genKey();
return;
}
var fixThings = false;
var key = Crypto.parseKey(window.location.hash.substring(1));
var editor = window.editor = Ckeditor.replace('editor1', {
// https://dev.ckeditor.com/ticket/10907
needsBrFiller: fixThings,
needsNbspFiller: fixThings,
removeButtons: 'Source,Maximize',
// magicline plugin inserts html crap into the document which is not part of the
// document itself and causes problems when it's sent across the wire and reflected back
// but we filter it now, so that's ok.
removePlugins: 'resize'
});
editor.on('instanceReady', function (Ckeditor) {
editor.execCommand('maximize');
var documentBody = ifrw.$('iframe')[0].contentDocument.body;
documentBody.innerHTML = Messages.initialState;
var inner = window.inner = documentBody;
var cursor = window.cursor = Cursor(inner);
var setEditable = function (bool) {
// careful about putting attributes onto the DOM
// they get put into the chain, and you can have trouble
// getting rid of them later
//inner.style.backgroundColor = bool? 'white': 'grey';
9 years ago
inner.setAttribute('contenteditable', bool);
};
// don't let the user edit until the pad is ready
setEditable(false);
var diffOptions = {
preDiffApply: function (info) {
/* DiffDOM will filter out magicline plugin elements
9 years ago
in practice this will make it impossible to use it
while someone else is typing, which could be annoying.
9 years ago
we should check when such an element is going to be
removed, and prevent that from happening. */
if (info.node && info.node.tagName === 'SPAN' &&
info.node.contentEditable === "true") {
// it seems to be a magicline plugin element...
if (info.diff.action === 'removeElement') {
// and you're about to remove it...
// this probably isn't what you want
/*
I have never seen this in the console, but the
magic line is still getting removed on remote
edits. This suggests that it's getting removed
by something other than diffDom.
*/
console.log("preventing removal of the magic line!");
// return true to prevent diff application
return true;
}
}
9 years ago
// no use trying to recover the cursor if it doesn't exist
if (!cursor.exists()) { return; }
/* frame is either 0, 1, 2, or 3, depending on which
cursor frames were affected: none, first, last, or both
*/
var frame = info.frame = cursor.inNode(info.node);
if (!frame) { return; }
if (typeof info.diff.oldValue === 'string' && typeof info.diff.newValue === 'string') {
var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue);
if (frame & 1) {
// push cursor start if necessary
if (pushes.commonStart < cursor.Range.start.offset) {
cursor.Range.start.offset += pushes.delta;
}
}
if (frame & 2) {
// push cursor end if necessary
if (pushes.commonStart < cursor.Range.end.offset) {
cursor.Range.end.offset += pushes.delta;
}
}
}
},
postDiffApply: function (info) {
if (info.frame) {
if (info.node) {
if (info.frame & 1) { cursor.fixStart(info.node); }
if (info.frame & 2) { cursor.fixEnd(info.node); }
} else { console.error("info.node did not exist"); }
var sel = cursor.makeSelection();
var range = cursor.makeRange();
cursor.fixSelection(sel, range);
}
}
};
var now = function () { return new Date().getTime() };
9 years ago
var realtimeOptions = {
// configuration :D
doc: inner,
9 years ago
// provide initialstate...
initialState: JSON.stringify(Hyperjson.fromDOM(inner, isNotMagicLine)),
9 years ago
// really basic operational transform
// reject patch if it results in invalid JSON
transformFunction : JsonOT.validate,
websocketURL: Config.websocketURL,
// username
userName: userName,
// communication channel name
channel: key.channel,
// encryption key
cryptKey: key.cryptKey
};
var DD = new DiffDom(diffOptions);
9 years ago
// apply patches, and try not to lose the cursor in the process!
var applyHjson = function (shjson) {
var userDocStateDom = hjsonToDom(JSON.parse(shjson));
9 years ago
userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf
var patch = (DD).diff(inner, userDocStateDom);
(DD).apply(inner, patch);
};
var initializing = true;
var onRemote = realtimeOptions.onRemote = function (info) {
9 years ago
if (initializing) { return; }
var shjson = info.realtime.getUserDoc();
// remember where the cursor is
cursor.update();
// build a dom from HJSON, diff, and patch the editor
applyHjson(shjson);
var shjson2 = JSON.stringify(Hyperjson.fromDOM(inner));
if (shjson2 !== shjson) {
rti.patchText(shjson2);
}
9 years ago
};
var onInit = realtimeOptions.onInit = function (info) {
9 years ago
var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox');
toolbar = info.realtime.toolbar = Toolbar.create($bar, userName, info.realtime);
/* TODO handle disconnects and such*/
};
var onReady = realtimeOptions.onReady = function (info) {
9 years ago
console.log("Unlocking editor");
initializing = false;
setEditable(true);
var shjson = info.realtime.getUserDoc();
applyHjson(shjson);
};
var onAbort = realtimeOptions.onAbort = function (info) {
9 years ago
console.log("Aborting the session!");
// stop the user from continuing to edit
// by setting the editable to false
9 years ago
setEditable(false);
toolbar.failed();
};
var rti = module.realtimeInput = realtimeInput.start(realtimeOptions);
9 years ago
// FIXME Spaghetti code. realtime-input needs access to this variable..
var propogate = window.cryptpad_propogate = function () {
var shjson = JSON.stringify(Hyperjson.fromDOM(inner, isNotMagicLine));
if (!rti.patchText(shjson)) { return; }
9 years ago
rti.onEvent(shjson);
};
var easyTest = window.easyTest = function () {
cursor.update();
var start = cursor.Range.start;
var test = TypingTest.testInput(start.el, start.offset, propogate);
9 years ago
propogate();
return test;
};
editor.on('change', propogate);
});
};
var interval = 100;
var first = function () {
Ckeditor = ifrw.CKEDITOR;
if (Ckeditor) {
andThen(Ckeditor);
} else {
console.log("Ckeditor was not defined. Trying again in %sms",interval);
setTimeout(first, interval);
}
};
$(first);
});