manual merge, still wip
commit
368a6b2406
@ -0,0 +1,748 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'json.sortify',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
], function (Realtime, JsonOT, Sortify, TextPatcher) {
|
||||
var api = {};
|
||||
// "Proxy" is undefined in Safari : we need to use an normal object and check if there are local
|
||||
// changes regurlarly.
|
||||
var isFakeProxy = typeof window.Proxy === "undefined";
|
||||
|
||||
var DeepProxy = api.DeepProxy = (function () {
|
||||
var deepProxy = {};
|
||||
|
||||
var isArray = deepProxy.isArray = Array.isArray || function (obj) {
|
||||
return Object.toString(obj) === '[object Array]';
|
||||
};
|
||||
|
||||
/* Arrays and nulls both register as 'object' when using native typeof
|
||||
we need to distinguish them as their own types, so use this instead. */
|
||||
var type = deepProxy.type = function (dat) {
|
||||
return dat === null? 'null': isArray(dat)?'array': typeof(dat);
|
||||
};
|
||||
|
||||
/* Check if an (sub-)element in an object or an array and should be a proxy.
|
||||
If the browser doesn't support Proxy, return false */
|
||||
var isProxyable = deepProxy.isProxyable = function (obj, forceCheck) {
|
||||
if (typeof forceCheck === "undefined" && isFakeProxy) { return false; }
|
||||
return ['object', 'array'].indexOf(type(obj)) !== -1;
|
||||
};
|
||||
|
||||
/* Any time you set a value, check its type.
|
||||
If that type is proxyable, make a new proxy. */
|
||||
var setter = deepProxy.set = function (cb) {
|
||||
return function (obj, prop, value) {
|
||||
if (prop === 'on') {
|
||||
throw new Error("'on' is a reserved attribute name for realtime lists and maps");
|
||||
}
|
||||
if (isProxyable(value)) {
|
||||
obj[prop] = deepProxy.create(value, cb);
|
||||
} else {
|
||||
obj[prop] = value;
|
||||
}
|
||||
|
||||
cb();
|
||||
return obj[prop] || true; // always return truthey or you have problems
|
||||
};
|
||||
};
|
||||
|
||||
var pathMatches = deepProxy.pathMatches = function (path, pattern) {
|
||||
return !pattern.some(function (x, i) {
|
||||
return x !== path[i];
|
||||
});
|
||||
};
|
||||
|
||||
var lengthDescending = function (a, b) { return b.pattern.length - a.pattern.length; };
|
||||
|
||||
/* TODO implement 'off' as well.
|
||||
change 'setter' to warn users when they attempt to set 'off'
|
||||
*/
|
||||
var on = function(events) {
|
||||
return function (evt, pattern, f) {
|
||||
switch (evt) {
|
||||
case 'change':
|
||||
// pattern needs to be an array
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.change.push({
|
||||
cb: function (oldval, newval, path, root) {
|
||||
if (pathMatches(path, pattern)) {
|
||||
return f(oldval, newval, path, root);
|
||||
}
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
// sort into descending order so we evaluate in order of specificity
|
||||
events.change.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'remove':
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.remove.push({
|
||||
cb: function (oldval, path, root) {
|
||||
if (pathMatches(path, pattern)) { return f(oldval, path, root); }
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
|
||||
events.remove.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'ready':
|
||||
events.ready.push({
|
||||
// on('ready' has a different signature than
|
||||
// change and delete, so use 'pattern', not 'f'
|
||||
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'disconnect':
|
||||
events.disconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'reconnect':
|
||||
events.reconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'create':
|
||||
events.create.push({
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
};
|
||||
|
||||
var getter = deepProxy.get = function (/* cb */) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
|
||||
return function (obj, prop) {
|
||||
if (prop === 'on') {
|
||||
return on(events);
|
||||
} else if (prop === '_isProxy') {
|
||||
return true;
|
||||
} else if (prop === '_events') {
|
||||
return events;
|
||||
}
|
||||
return obj[prop];
|
||||
};
|
||||
};
|
||||
|
||||
var deleter = deepProxy.delete = function (cb) {
|
||||
return function (obj, prop) {
|
||||
if (typeof(obj[prop]) === 'undefined') { return true; }
|
||||
delete obj[prop];
|
||||
cb();
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
var handlers = deepProxy.handlers = function (cb, isRoot) {
|
||||
if (!isRoot) {
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: function (obj, prop) {
|
||||
if (prop === '_isProxy') {
|
||||
return true;
|
||||
}
|
||||
return obj[prop];
|
||||
},
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
}
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: getter(cb),
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
};
|
||||
|
||||
var remoteChangeFlag = deepProxy.remoteChangeFlag = false;
|
||||
|
||||
var stringifyFakeProxy = deepProxy.stringifyFakeProxy = function (proxy) {
|
||||
var copy = JSON.parse(Sortify(proxy));
|
||||
delete copy._events;
|
||||
delete copy._isProxy;
|
||||
return Sortify(copy);
|
||||
};
|
||||
|
||||
deepProxy.checkLocalChange = function (obj, cb) {
|
||||
if (!isFakeProxy) { return; }
|
||||
var oldObj = stringifyFakeProxy(obj);
|
||||
window.setInterval(function() {
|
||||
var newObj = stringifyFakeProxy(obj);
|
||||
if (newObj !== oldObj) {
|
||||
oldObj = newObj;
|
||||
if (remoteChangeFlag) {
|
||||
remoteChangeFlag = false;
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
},300);
|
||||
};
|
||||
|
||||
var create = deepProxy.create = function (obj, opt, isRoot) {
|
||||
/* recursively create proxies in case users do:
|
||||
`x.a = {b: {c: 5}};
|
||||
|
||||
otherwise the inner object is not a proxy, which leads to incorrect
|
||||
behaviour on the client that initiated the object (but not for
|
||||
clients that receive the objects) */
|
||||
|
||||
// if the user supplied a callback, use it to create handlers
|
||||
// this saves a bit of work in recursion
|
||||
var methods = type(opt) === 'function'? handlers(opt, isRoot) : opt;
|
||||
switch (type(obj)) {
|
||||
case 'object':
|
||||
var keys = Object.keys(obj);
|
||||
keys.forEach(function (k) {
|
||||
if (isProxyable(obj[k]) && !obj[k]._isProxy) {
|
||||
obj[k] = create(obj[k], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'array':
|
||||
obj.forEach(function (o, i) {
|
||||
if (isProxyable(o) && !o._isProxy) {
|
||||
obj[i] = create(obj[i], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// if it's not an array or object, you don't need to proxy it
|
||||
throw new Error('attempted to make a proxy of an unproxyable object');
|
||||
}
|
||||
if (!isFakeProxy) {
|
||||
if (obj._isProxy) {
|
||||
return obj;
|
||||
}
|
||||
return new window.Proxy(obj, methods);
|
||||
}
|
||||
|
||||
var proxy = JSON.parse(JSON.stringify(obj));
|
||||
|
||||
if (isRoot) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
proxy.on = on(events);
|
||||
proxy._events = events;
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
var onChange = function (path, key, root, oldval, newval) {
|
||||
var P = path.slice(0);
|
||||
P.push(key);
|
||||
|
||||
/* returning false in your callback terminates 'bubbling up'
|
||||
we can accomplish this with Array.some because we've presorted
|
||||
listeners by the specificity of their path
|
||||
*/
|
||||
root._events.change.some(function (handler) {
|
||||
return handler.cb(oldval, newval, P, root) === false;
|
||||
});
|
||||
};
|
||||
|
||||
var find = deepProxy.find = function (map, path) {
|
||||
/* safely search for nested values in an object via a path */
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof p[n] !== 'undefined' && p[n];
|
||||
}, map)) || undefined;
|
||||
};
|
||||
|
||||
var onRemove = function (path, key, root, old, top) {
|
||||
var newpath = path.concat(key);
|
||||
var X = find(root, newpath);
|
||||
|
||||
var t_X = type(X);
|
||||
|
||||
/* TODO 'find' is correct but unnecessarily expensive.
|
||||
optimize it. */
|
||||
|
||||
switch (t_X) {
|
||||
case 'array':
|
||||
|
||||
if (top) {
|
||||
// the top of an onremove should emit an onchange instead
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
}
|
||||
// remove all of the array's children
|
||||
X.forEach(function (x, i) {
|
||||
onRemove(newpath, i, root);
|
||||
});
|
||||
|
||||
break;
|
||||
case 'object':
|
||||
if (top) {
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root, old, false);
|
||||
});
|
||||
}
|
||||
// remove all of the object's children
|
||||
Object.keys(X).forEach(function (key) {
|
||||
onRemove(newpath, key, root, X[key], false);
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* compare a new object 'B' against an existing proxy object 'A'
|
||||
provide a unary function 'f' for the purpose of constructing new
|
||||
deep proxies from regular objects and arrays.
|
||||
|
||||
Supply the path as you recurse, for the purpose of emitting events
|
||||
attached to particular paths within the complete structure.
|
||||
|
||||
Operates entirely via side effects on 'A'
|
||||
*/
|
||||
var objects = deepProxy.objects = function (A, B, f, path, root) {
|
||||
var Akeys = Object.keys(A);
|
||||
var Bkeys = Object.keys(B);
|
||||
|
||||
/* iterating over the keys in B will tell you if a new key exists
|
||||
it will not tell you if a key has been removed.
|
||||
to accomplish that you will need to iterate over A's keys
|
||||
*/
|
||||
|
||||
/* TODO return a truthy or falsey value (in 'objects' and 'arrays')
|
||||
so that we have some measure of whether an object or array changed
|
||||
(from the higher level in the tree, rather than doing everything
|
||||
at the leaf level).
|
||||
|
||||
bonus points if you can defer events until the complete diff has
|
||||
finished (collect them into an array or something, and simplify
|
||||
the event if possible)
|
||||
*/
|
||||
|
||||
Bkeys.forEach(function (b) {
|
||||
var t_b = type(B[b]);
|
||||
var old = A[b];
|
||||
|
||||
if (Akeys.indexOf(b) === -1) {
|
||||
// there was an insertion
|
||||
|
||||
// mind the fallthrough behaviour
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
// umm. this should never happen?
|
||||
throw new Error("undefined type has key. this shouldn't happen?");
|
||||
case 'array':
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
break;
|
||||
default:
|
||||
A[b] = B[b];
|
||||
}
|
||||
|
||||
// insertions are a change
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// else the key already existed
|
||||
var t_a = type(A[b]);
|
||||
if (t_a !== t_b) {
|
||||
// its type changed!
|
||||
console.log("type changed from [%s] to [%s]", t_a, t_b);
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error("first pass should never reveal undefined keys");
|
||||
case 'array':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
default:
|
||||
// all other datatypes just require assignment.
|
||||
A[b] = B[b];
|
||||
break;
|
||||
}
|
||||
|
||||
// type changes always mean a change happened
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// values might have changed, if not types
|
||||
if (['array', 'object'].indexOf(t_a) === -1) {
|
||||
// it's not an array or object, so we can do deep equality
|
||||
if (A[b] !== B[b]) {
|
||||
// not equal, so assign
|
||||
A[b] = B[b];
|
||||
|
||||
onChange(path, b, root, old, B[b]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// else it's an array or object
|
||||
var nextPath = path.slice(0).concat(b);
|
||||
if (t_a === 'object') {
|
||||
// it's an object
|
||||
objects.call(root, A[b], B[b], f, nextPath, root);
|
||||
} else {
|
||||
// it's an array
|
||||
deepProxy.arrays.call(root, A[b], B[b], f, nextPath, root);
|
||||
}
|
||||
});
|
||||
Akeys.forEach(function (a) {
|
||||
var old = A[a];
|
||||
|
||||
if (a === "on" || a === "_events") { return; }
|
||||
|
||||
// the key was deleted
|
||||
if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') {
|
||||
onRemove(path, a, root, old, true);
|
||||
delete A[a];
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
var arrays = deepProxy.arrays = function (A, B, f, path, root) {
|
||||
var l_A = A.length;
|
||||
var l_B = B.length;
|
||||
|
||||
if (l_A !== l_B) {
|
||||
// B is longer than Aj
|
||||
// there has been an insertion
|
||||
|
||||
// OR
|
||||
|
||||
// A is longer than B
|
||||
// there has been a deletion
|
||||
|
||||
B.forEach(function (b, i) {
|
||||
var t_a = type(A[i]);
|
||||
var t_b = type(b);
|
||||
|
||||
var old = A[i];
|
||||
|
||||
if (t_a !== t_b) {
|
||||
// type changes are always destructive
|
||||
// that's good news because destructive is easy
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('this should never happen');
|
||||
case 'object':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
case 'array':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
default:
|
||||
A[i] = b;
|
||||
break;
|
||||
}
|
||||
|
||||
// path, key, root object, oldvalue, newvalue
|
||||
onChange(path, i, root, old, b);
|
||||
} else {
|
||||
// same type
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
switch (t_b) {
|
||||
case 'object':
|
||||
objects.call(root, A[i], b, f, nextPath, root);
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], b, f, nextPath, root)) {
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (b !== A[i]) {
|
||||
A[i] = b;
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (l_A > l_B) {
|
||||
// A was longer than B, so there have been deletions
|
||||
var i = l_B;
|
||||
//var t_a;
|
||||
var old;
|
||||
|
||||
for (; i <= l_B; i++) {
|
||||
// recursively delete
|
||||
old = A[i];
|
||||
|
||||
onRemove(path, i, root, old, true);
|
||||
}
|
||||
// cool
|
||||
}
|
||||
|
||||
A.length = l_B;
|
||||
return;
|
||||
}
|
||||
|
||||
// else they are the same length, iterate over their values
|
||||
A.forEach(function (a, i) {
|
||||
var t_a = type(a);
|
||||
var t_b = type(B[i]);
|
||||
|
||||
var old = a;
|
||||
|
||||
// they have different types
|
||||
if (t_a !== t_b) {
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
onRemove(path, i, root, old, true);
|
||||
break;
|
||||
|
||||
// watch out for fallthrough behaviour
|
||||
// if it's an object or array, create a proxy
|
||||
case 'object':
|
||||
case 'array':
|
||||
A[i] = f(B[i]);
|
||||
break;
|
||||
default:
|
||||
A[i] = B[i];
|
||||
break;
|
||||
}
|
||||
|
||||
onChange(path, i, root, old, B[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
// they are the same type, clone the paths array and push to it
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
// same type
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('existing key had type `undefined`. this should never happen');
|
||||
case 'object':
|
||||
if (objects.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (A[i] !== B[i]) {
|
||||
A[i] = B[i];
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
deepProxy.update = function (A, B, cb) {
|
||||
var t_A = type(A);
|
||||
var t_B = type(B);
|
||||
|
||||
if (t_A !== t_B) {
|
||||
throw new Error("Proxy updates can't result in type changes");
|
||||
}
|
||||
|
||||
switch (t_B) {
|
||||
/* use .call so you can supply a different `this` value */
|
||||
case 'array':
|
||||
arrays.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
case 'object':
|
||||
// arrays.call(this, A , B , f, path , root)
|
||||
objects.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
default:
|
||||
throw new Error("unsupported realtime datatype:" + t_B);
|
||||
}
|
||||
};
|
||||
|
||||
return deepProxy;
|
||||
}());
|
||||
|
||||
api.create = function (cfg) {
|
||||
/* validate your inputs before proceeding */
|
||||
|
||||
if (!DeepProxy.isProxyable(cfg.data, true)) {
|
||||
throw new Error('unsupported datatype: '+ DeepProxy.type(cfg.data));
|
||||
}
|
||||
|
||||
var realtimeOptions = {
|
||||
userName: cfg.userName,
|
||||
initialState: Sortify(cfg.data),
|
||||
readOnly: cfg.readOnly,
|
||||
transformFunction: JsonOT.transform || JsonOT.validate,
|
||||
logLevel: typeof(cfg.logLevel) === 'undefined'? 0: cfg.logLevel,
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse, rejecting patch");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var setterCb = function () {
|
||||
if (!DeepProxy.remoteChangeFlag) { realtimeOptions.onLocal(); }
|
||||
};
|
||||
|
||||
var rt = {};
|
||||
var realtime;
|
||||
|
||||
var proxy;
|
||||
var patchText;
|
||||
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
// TODO maybe implement history support here
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.remoteChangeFlag = true;
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
DeepProxy.remoteChangeFlag = false;
|
||||
};
|
||||
|
||||
var onLocal = realtimeOptions.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
var strung = isFakeProxy? DeepProxy.stringifyFakeProxy(proxy): Sortify(proxy);
|
||||
patchText(strung);
|
||||
|
||||
// try harder
|
||||
if (realtime.getUserDoc() !== strung) { patchText(strung); }
|
||||
|
||||
// onLocal
|
||||
if (cfg.onLocal) { cfg.onLocal(); }
|
||||
};
|
||||
|
||||
proxy = DeepProxy.create(cfg.data, setterCb, true);
|
||||
console.log(proxy);
|
||||
|
||||
realtimeOptions.onInit = function (info) {
|
||||
proxy._events.create.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onReady = function (info) {
|
||||
// create your patcher
|
||||
if (realtime !== info.realtime) {
|
||||
realtime = rt.realtime = info.realtime;
|
||||
patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
logging: cfg.logging || false,
|
||||
});
|
||||
} else {
|
||||
console.error(realtime);
|
||||
}
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
|
||||
proxy._events.ready.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
|
||||
DeepProxy.checkLocalChange(proxy, onLocal);
|
||||
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
realtimeOptions.onAbort = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onConnectionChange = function (info) {
|
||||
if (info.state) { // reconnect
|
||||
initializing = true;
|
||||
proxy._events.reconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// disconnected
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onError = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtime = rt.cpCnInner = cfg.common.startRealtime(realtimeOptions);
|
||||
rt.proxy = proxy;
|
||||
rt.realtime = realtime;
|
||||
|
||||
return rt;
|
||||
};
|
||||
|
||||
return api;
|
||||
});
|
Binary file not shown.
@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp pad">
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<link rel="icon" type="image/png"
|
||||
href="/customize/main-favicon.png"
|
||||
data-main-favicon="/customize/main-favicon.png"
|
||||
data-alt-favicon="/customize/alt-favicon.png"
|
||||
id="favicon" />
|
||||
<link rel="stylesheet" href="/customize/main.css" />
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
<div id="loading">
|
||||
<div class="loadingContainer">
|
||||
<img class="cryptofist" src="/customize/cryptofist_small.png" />
|
||||
<div class="spinnerContainer">
|
||||
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
|
||||
</div>
|
||||
<p data-localization="loading"></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
margin-bottom: 1px;
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
media-tag * {
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
media-tag *:not(button) {
|
||||
height: 100%;
|
||||
}
|
||||
media-tag video {
|
||||
min-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="toolbar" class="toolbar-container"></div>
|
||||
<media-tag id="encryptedFile"></media-tag>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,139 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/cryptpad-common.js',
|
||||
//'/common/visible.js',
|
||||
//'/common/notify.js',
|
||||
//'pdfjs-dist/build/pdf',
|
||||
//'pdfjs-dist/build/pdf.worker',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
|
||||
//var Messages = Cryptpad.Messages;
|
||||
//var saveAs = window.saveAs;
|
||||
//window.Nacl = window.nacl;
|
||||
$(function () {
|
||||
|
||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var andThen = function () {
|
||||
var $bar = $iframe.find('.toolbar-container');
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
|
||||
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var fileId = secret.channel;
|
||||
var hexFileName = Cryptpad.base64ToHex(fileId);
|
||||
// var type = "image/png";
|
||||
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsed);
|
||||
|
||||
var getTitle = function () {
|
||||
var pad = Cryptpad.getRelativeHref(window.location.href);
|
||||
var fo = Cryptpad.getStore().getProxy().fo;
|
||||
var data = fo.getFileData(pad);
|
||||
return data ? data.title : undefined;
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
var title = document.title = newTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(title);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(title);
|
||||
};
|
||||
|
||||
var suggestName = function () {
|
||||
return document.title || getTitle() || '';
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
var $mt = $iframe.find('#encryptedFile');
|
||||
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
|
||||
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||
// $mt.attr('data-type', type);
|
||||
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
var metadata = decrypted.metadata;
|
||||
|
||||
if (decrypted.callback) { decrypted.callback(); }
|
||||
//console.log(metadata);
|
||||
//console.log(defaultName);
|
||||
if (!metadata || metadata.name !== defaultName) { return; }
|
||||
var title = document.title = metadata.name;
|
||||
updateTitle(title || defaultName);
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
Cryptpad.alert(error.message);
|
||||
})
|
||||
.on('decryptionProgress', function (e) {
|
||||
var progress = e.originalEvent;
|
||||
console.log(progress.percent);
|
||||
});
|
||||
|
||||
require(['/common/media-tag.js'], function (MediaTag) {
|
||||
var configTb = {
|
||||
displayed: ['useradmin', 'share', 'newpad'],
|
||||
ifrw: ifrw,
|
||||
common: Cryptpad,
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: hexFileName
|
||||
}
|
||||
};
|
||||
Toolbar.create($bar, null, null, null, null, configTb);
|
||||
|
||||
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||
|
||||
/**
|
||||
* Allowed mime types that have to be set for a rendering after a decryption.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
var allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
'application/dash+xml',
|
||||
'download'
|
||||
];
|
||||
|
||||
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
@import "/customize/src/less/variables.less";
|
||||
@import "/customize/src/less/mixins.less";
|
||||
@import "/common/markdown.less";
|
||||
@import "/common/file-dialog.less";
|
||||
|
||||
html, body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
@slideTime: 500ms;
|
||||
.CodeMirror {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
&.transition {
|
||||
transition: width @slideTime, min-width @slideTime, max-width @slideTime;
|
||||
}
|
||||
min-width: 20%;
|
||||
max-width: 80%;
|
||||
resize: horizontal;
|
||||
font-size: initial;
|
||||
}
|
||||
.CodeMirror.fullPage {
|
||||
//min-width: 100%;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
flex: 1;
|
||||
}
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
#editorContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#previewContainer {
|
||||
flex: 1;
|
||||
padding: 5px 20px;
|
||||
overflow: auto;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
border-left: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
font-family: Calibri,Ubuntu,sans-serif;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
media-tag {
|
||||
* {
|
||||
max-width:100%;
|
||||
}
|
||||
iframe[type="application/pdf"] {
|
||||
max-height:50vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#preview {
|
||||
max-width: 40vw;
|
||||
margin: 1em auto;
|
||||
|
||||
.markdown_preformatted-code;
|
||||
.markdown_gfm-table(black);
|
||||
}
|
||||
|
||||
.cp-splitter {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 8px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
@media (max-width: @media-medium-screen) {
|
||||
.CodeMirror {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
#previewContainer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp code">
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
#iframe-container {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* We use !important here to override the 96% set to the element in DecorateToolbar.js
|
||||
when we enter fullscreen mode. It allows us to avoid changing the iframe's size in JS */
|
||||
#pad-iframe.fullscreen {
|
||||
top: 0px;
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="iframe-container">
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
</div>
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 100%;">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<script async data-bootload="inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style> .loading-hidden { display: none; } </style>
|
||||
</head>
|
||||
<body class="loading-hidden">
|
||||
<div id="cme_toolbox" class="toolbar-container"></div>
|
||||
<div id="editorContainer">
|
||||
<textarea id="editor1" name="editor1"></textarea>
|
||||
<div id="previewContainer"><div id="preview"></div></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,40 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
|
||||
'cm/lib/codemirror',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/code/code.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
|
||||
'css!cm/lib/codemirror.css',
|
||||
'css!cm/addon/dialog/dialog.css',
|
||||
'css!cm/addon/fold/foldgutter.css',
|
||||
|
||||
'cm/mode/markdown/markdown',
|
||||
'cm/addon/mode/loadmode',
|
||||
'cm/mode/meta',
|
||||
'cm/addon/mode/overlay',
|
||||
'cm/addon/mode/multiplex',
|
||||
'cm/addon/mode/simple',
|
||||
'cm/addon/edit/closebrackets',
|
||||
'cm/addon/edit/matchbrackets',
|
||||
'cm/addon/edit/trailingspace',
|
||||
'cm/addon/selection/active-line',
|
||||
'cm/addon/search/search',
|
||||
'cm/addon/search/match-highlighter',
|
||||
'cm/addon/search/searchcursor',
|
||||
'cm/addon/dialog/dialog',
|
||||
'cm/addon/fold/foldcode',
|
||||
'cm/addon/fold/foldgutter',
|
||||
'cm/addon/fold/brace-fold',
|
||||
'cm/addon/fold/xml-fold',
|
||||
'cm/addon/fold/markdown-fold',
|
||||
'cm/addon/fold/comment-fold',
|
||||
'cm/addon/display/placeholder',
|
||||
], function ($, CMeditor) {
|
||||
window.CodeMirror = CMeditor;
|
||||
$('.loading-hidden').removeClass('loading-hidden');
|
||||
});
|
@ -1,559 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/toolbar2.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/diffMarked.js',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less/cryptpad.less'
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
|
||||
Cryptget, DiffMd) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var ifrw = APP.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var toolbar;
|
||||
var editor;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
var andThen = function (CMeditor) {
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
var $contentContainer = $iframe.find('#editorContainer');
|
||||
var $previewContainer = $iframe.find('#previewContainer');
|
||||
var $preview = $iframe.find('#preview');
|
||||
$preview.click(function (e) {
|
||||
if (!e.target) { return; }
|
||||
var $t = $(e.target);
|
||||
if ($t.is('a') || $t.parents('a').length) {
|
||||
e.preventDefault();
|
||||
var $a = $t.is('a') ? $t : $t.parents('a').first();
|
||||
var href = $a.attr('href');
|
||||
window.open(href);
|
||||
}
|
||||
});
|
||||
|
||||
var CodeMirror = Cryptpad.createCodemirror(ifrw, Cryptpad, null, CMeditor);
|
||||
$iframe.find('.CodeMirror').addClass('fullPage');
|
||||
editor = CodeMirror.editor;
|
||||
|
||||
var setIndentation = APP.setIndentation = function (units, useTabs) {
|
||||
if (typeof(units) !== 'number') { return; }
|
||||
editor.setOption('indentUnit', units);
|
||||
editor.setOption('tabSize', units);
|
||||
editor.setOption('indentWithTabs', useTabs);
|
||||
};
|
||||
|
||||
var indentKey = 'indentUnit';
|
||||
var useTabsKey = 'indentWithTabs';
|
||||
|
||||
var proxy = Cryptpad.getProxy();
|
||||
|
||||
var updateIndentSettings = APP.updateIndentSettings = function () {
|
||||
var indentUnit = proxy.settings[indentKey];
|
||||
var useTabs = proxy.settings[useTabsKey];
|
||||
setIndentation(
|
||||
typeof(indentUnit) === 'number'? indentUnit: 2,
|
||||
typeof(useTabs) === 'boolean'? useTabs: false);
|
||||
};
|
||||
|
||||
proxy.on('change', ['settings', indentKey], updateIndentSettings);
|
||||
proxy.on('change', ['settings', useTabsKey], updateIndentSettings);
|
||||
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
|
||||
var isHistoryMode = false;
|
||||
|
||||
var setEditable = APP.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
channel: secret.channel,
|
||||
// our public key
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
network: Cryptpad.getNetwork(),
|
||||
transformFunction: JsonOT.validate,
|
||||
};
|
||||
|
||||
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
|
||||
|
||||
var setHistory = function (bool, update) {
|
||||
isHistoryMode = bool;
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
config.onRemote();
|
||||
}
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
defaultTitle: Title.defaultTitle
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// set mode too...
|
||||
obj.highlightMode = CodeMirror.highlightMode;
|
||||
|
||||
// stringify the json and send it into chainpad
|
||||
return stringify(obj);
|
||||
};
|
||||
|
||||
var forceDrawPreview = function () {
|
||||
try {
|
||||
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
var drawPreview = Cryptpad.throttle(function () {
|
||||
if (CodeMirror.highlightMode !== 'markdown') { return; }
|
||||
if (!$previewContainer.is(':visible')) { return; }
|
||||
forceDrawPreview();
|
||||
}, 150);
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
editor.save();
|
||||
|
||||
drawPreview();
|
||||
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
APP.patchText(shjson);
|
||||
|
||||
if (APP.realtime.getUserDoc() !== shjson) {
|
||||
console.error("realtime.getUserDoc() !== shjson");
|
||||
}
|
||||
};
|
||||
|
||||
var mediaTagModes = [
|
||||
'markdown',
|
||||
'html',
|
||||
'htmlembedded',
|
||||
'htmlmixed',
|
||||
'index.html',
|
||||
'php',
|
||||
'velocity',
|
||||
'xml',
|
||||
];
|
||||
|
||||
var onModeChanged = function (mode) {
|
||||
var $codeMirror = $iframe.find('.CodeMirror');
|
||||
window.clearTimeout(APP.previewTo);
|
||||
$codeMirror.addClass('transition');
|
||||
APP.previewTo = window.setTimeout(function () {
|
||||
$codeMirror.removeClass('transition');
|
||||
}, 500);
|
||||
if (mediaTagModes.indexOf(mode) !== -1) {
|
||||
APP.$mediaTagButton.show();
|
||||
} else { APP.$mediaTagButton.hide(); }
|
||||
|
||||
if (mode === "markdown") {
|
||||
APP.$previewButton.show();
|
||||
Cryptpad.getPadAttribute('previewMode', function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (data !== false) {
|
||||
$previewContainer.show();
|
||||
APP.$previewButton.addClass('active');
|
||||
$codeMirror.removeClass('fullPage');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
APP.$previewButton.hide();
|
||||
$previewContainer.hide();
|
||||
APP.$previewButton.removeClass('active');
|
||||
$codeMirror.addClass('fullPage');
|
||||
if (typeof(APP.updateIndentSettings) === 'function') {
|
||||
APP.updateIndentSettings();
|
||||
}
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, null, 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: ifrw,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar,
|
||||
$contentContainer: $contentContainer
|
||||
};
|
||||
toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
var $drawer = toolbar.$drawer;
|
||||
|
||||
var editHash;
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal,
|
||||
onRemote: config.onRemote,
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$drawer.append($hist);
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: Title.getTitle
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
|
||||
$drawer.append($export);
|
||||
|
||||
if (!readOnly) {
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
|
||||
$drawer.append($import);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
};
|
||||
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
var fileDialogCfg = {
|
||||
$body: $iframe.find('body'),
|
||||
onSelect: function (href) {
|
||||
var parsed = Cryptpad.parsePadUrl(href);
|
||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
|
||||
editor.replaceSelection(mt);
|
||||
},
|
||||
data: APP
|
||||
};
|
||||
APP.$mediaTagButton = $('<button>', {
|
||||
title: Messages.filePickerButton,
|
||||
'class': 'rightside-button fa fa-picture-o',
|
||||
style: 'font-size: 17px'
|
||||
}).click(function () {
|
||||
Cryptpad.createFileDialog(fileDialogCfg);
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
|
||||
$previewButton.removeClass('fa-question').addClass('fa-eye');
|
||||
$previewButton.attr('title', Messages.previewButtonTitle);
|
||||
$previewButton.click(function () {
|
||||
var $codeMirror = $iframe.find('.CodeMirror');
|
||||
window.clearTimeout(APP.previewTo);
|
||||
$codeMirror.addClass('transition');
|
||||
APP.previewTo = window.setTimeout(function () {
|
||||
$codeMirror.removeClass('transition');
|
||||
}, 500);
|
||||
if (CodeMirror.highlightMode !== 'markdown') {
|
||||
$previewContainer.show();
|
||||
}
|
||||
$previewContainer.toggle();
|
||||
if ($previewContainer.is(':visible')) {
|
||||
forceDrawPreview();
|
||||
$codeMirror.removeClass('fullPage');
|
||||
Cryptpad.setPadAttribute('previewMode', true, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
$previewButton.addClass('active');
|
||||
} else {
|
||||
$codeMirror.addClass('fullPage');
|
||||
$previewButton.removeClass('active');
|
||||
Cryptpad.setPadAttribute('previewMode', false, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
}
|
||||
});
|
||||
$rightside.append($previewButton);
|
||||
|
||||
if (!readOnly) {
|
||||
CodeMirror.configureTheme(function () {
|
||||
CodeMirror.configureLanguage(null, onModeChanged);
|
||||
});
|
||||
}
|
||||
else {
|
||||
CodeMirror.configureTheme();
|
||||
}
|
||||
|
||||
|
||||
// set the hash
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (APP.realtime !== info.realtime) {
|
||||
var realtime = APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
//logging: true
|
||||
});
|
||||
}
|
||||
|
||||
var userDoc = APP.realtime.getUserDoc();
|
||||
|
||||
var isNew = false;
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
|
||||
var newDoc = "";
|
||||
if(userDoc !== "") {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
|
||||
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'code')) {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
|
||||
newDoc = hjson.content;
|
||||
|
||||
if (hjson.highlightMode) {
|
||||
CodeMirror.setMode(hjson.highlightMode, onModeChanged);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode('markdown', onModeChanged);
|
||||
console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
|
||||
}
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
Metadata.update(userDoc);
|
||||
|
||||
if (newDoc) {
|
||||
editor.setValue(newDoc);
|
||||
}
|
||||
|
||||
if (Cryptpad.initialName && Title.isDefaultTitle()) {
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
}
|
||||
|
||||
Cryptpad.getPadAttribute('previewMode', function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (data === false && APP.$previewButton) {
|
||||
APP.$previewButton.click();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
// add the splitter
|
||||
if (!$iframe.has('.cp-splitter').length) {
|
||||
var $preview = $iframe.find('#previewContainer');
|
||||
var splitter = $('<div>', {
|
||||
'class': 'cp-splitter'
|
||||
}).appendTo($preview);
|
||||
|
||||
$preview.on('scroll', function() {
|
||||
splitter.css('top', $preview.scrollTop() + 'px');
|
||||
});
|
||||
|
||||
var $target = $iframe.find('.CodeMirror');
|
||||
|
||||
splitter.on('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
var x = e.pageX;
|
||||
var w = $target.width();
|
||||
|
||||
$iframe.on('mouseup mousemove', function handler(evt) {
|
||||
if (evt.type === 'mouseup') {
|
||||
$iframe.off('mouseup mousemove', handler);
|
||||
return;
|
||||
}
|
||||
$target.css('width', (w - x + evt.pageX) + 'px');
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
|
||||
onLocal(); // push local state to avoid parse errors later.
|
||||
|
||||
if (readOnly) {
|
||||
config.onRemote();
|
||||
return;
|
||||
}
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
var fmConfig = {
|
||||
dropArea: $iframe.find('.CodeMirror'),
|
||||
body: $iframe.find('body'),
|
||||
onUploaded: function (ev, data) {
|
||||
//var cursor = editor.getCursor();
|
||||
//var cleanName = data.name.replace(/[\[\]]/g, '');
|
||||
//var text = '!['+cleanName+']('+data.url+')';
|
||||
var parsed = Cryptpad.parsePadUrl(data.url);
|
||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
|
||||
editor.replaceSelection(mt);
|
||||
}
|
||||
};
|
||||
APP.FM = Cryptpad.createFileManager(fmConfig);
|
||||
};
|
||||
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = APP.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== APP.highlightMode) {
|
||||
CodeMirror.setMode(highlightMode, onModeChanged);
|
||||
}
|
||||
|
||||
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
drawPreview();
|
||||
|
||||
if (!readOnly) {
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson2 = stringifyInner(textValue);
|
||||
if (shjson2 !== shjson) {
|
||||
console.error("shjson2 !== shjson");
|
||||
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
|
||||
APP.patchText(shjson2);
|
||||
}
|
||||
}
|
||||
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
config.onError = onConnectError;
|
||||
|
||||
APP.realtime = Realtime.start(config);
|
||||
|
||||
editor.on('change', onLocal);
|
||||
|
||||
Cryptpad.onLogout(function () { setEditable(false); });
|
||||
};
|
||||
|
||||
var interval = 100;
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var first = function () {
|
||||
if (ifrw.CodeMirror) {
|
||||
second(ifrw.CodeMirror);
|
||||
} else {
|
||||
console.log("CodeMirror was not defined. Trying again in %sms", interval);
|
||||
setTimeout(first, interval);
|
||||
}
|
||||
};
|
||||
|
||||
first();
|
||||
});
|
||||
});
|
@ -1,40 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp slide">
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
#iframe-container {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body #pad-iframe.fullscreen {
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="iframe-container">
|
||||
<iframe id="pad-iframe", name="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
</div>
|
||||
|
@ -1,33 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<script async data-bootload="inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>.loading-hidden { display: none; } </style>
|
||||
</head>
|
||||
<body class="loading-hidden">
|
||||
<div id="bar"></div>
|
||||
<!-- <textarea></textarea>-->
|
||||
<div id="cme_toolbox" class="toolbar-container"></div>
|
||||
|
||||
<div id="editorContainer">
|
||||
<textarea id="editor1" name="editor1"></textarea>
|
||||
<div class="cp-app-slide-viewer slide" tabindex="2">
|
||||
<div id="modal">
|
||||
<div id="button_exit" class="button"><span class="fa fa-times"></span></div>
|
||||
<div id="button_left" class="button"><span class="fa fa-chevron-left"></span></div>
|
||||
<div id="button_right" class="button"><span class="fa fa-chevron-right"></span></div>
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
<div id="print"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nope"></div>
|
||||
<div id="colorPicker_check"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,41 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
|
||||
'cm/lib/codemirror',
|
||||
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
'less!/slide/slide.less',
|
||||
|
||||
'css!cm/lib/codemirror.css',
|
||||
'css!cm/addon/dialog/dialog.css',
|
||||
'css!cm/addon/fold/foldgutter.css',
|
||||
|
||||
'cm/mode/markdown/markdown',
|
||||
'cm/addon/mode/loadmode',
|
||||
'cm/mode/meta',
|
||||
'cm/addon/mode/overlay',
|
||||
'cm/addon/mode/multiplex',
|
||||
'cm/addon/mode/simple',
|
||||
'cm/addon/edit/closebrackets',
|
||||
'cm/addon/edit/matchbrackets',
|
||||
'cm/addon/edit/trailingspace',
|
||||
'cm/addon/selection/active-line',
|
||||
'cm/addon/search/search',
|
||||
'cm/addon/search/match-highlighter',
|
||||
'cm/addon/search/searchcursor',
|
||||
'cm/addon/dialog/dialog',
|
||||
'cm/addon/fold/foldcode',
|
||||
'cm/addon/fold/foldgutter',
|
||||
'cm/addon/fold/brace-fold',
|
||||
'cm/addon/fold/xml-fold',
|
||||
'cm/addon/fold/markdown-fold',
|
||||
'cm/addon/fold/comment-fold',
|
||||
'cm/addon/display/placeholder',
|
||||
|
||||
], function ($, CMeditor) {
|
||||
window.CodeMirror = CMeditor;
|
||||
$('.loading-hidden').removeClass('loading-hidden');
|
||||
});
|
@ -1,680 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/toolbar2.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/slide/slide.js',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
TextPatcher: TextPatcher,
|
||||
Slide: Slide,
|
||||
};
|
||||
var APP = window.APP;
|
||||
|
||||
var SLIDE_BACKCOLOR_ID = "cryptpad-backcolor";
|
||||
var SLIDE_COLOR_ID = "cryptpad-color";
|
||||
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var toolbar;
|
||||
var editor;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
Slide.readOnly = readOnly;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var presentMode = Slide.isPresentURL();
|
||||
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
var andThen = function (CMeditor) {
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
var $contentContainer = $iframe.find('#editorContainer');
|
||||
var CodeMirror = Cryptpad.createCodemirror(ifrw, Cryptpad, null, CMeditor);
|
||||
editor = CodeMirror.editor;
|
||||
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
var $pad = $('#pad-iframe');
|
||||
|
||||
var isHistoryMode = false;
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var setTabTitle = function (title) {
|
||||
var slideNumber = '';
|
||||
if (Slide.shown) { //Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
|
||||
}
|
||||
document.title = title + slideNumber;
|
||||
};
|
||||
|
||||
var initialState = Messages.slideInitialState;
|
||||
|
||||
var $modal = $pad.contents().find('#modal');
|
||||
var $content = $pad.contents().find('#content');
|
||||
var $print = $pad.contents().find('#print');
|
||||
var slideOptions = {};
|
||||
|
||||
$content.click(function (e) {
|
||||
if (!e.target) { return; }
|
||||
var $t = $(e.target);
|
||||
if ($t.is('a') || $t.parents('a').length) {
|
||||
e.preventDefault();
|
||||
var $a = $t.is('a') ? $t : $t.parents('a').first();
|
||||
var href = $a.attr('href');
|
||||
window.open(href);
|
||||
}
|
||||
});
|
||||
|
||||
Slide.setModal($modal, $content, $pad, ifrw, slideOptions, initialState);
|
||||
|
||||
var enterPresentationMode = function (shouldLog) {
|
||||
Slide.show(true, editor.getValue());
|
||||
if (shouldLog) {
|
||||
Cryptpad.log(Messages.presentSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
if (presentMode) {
|
||||
enterPresentationMode(true);
|
||||
}
|
||||
|
||||
var textColor;
|
||||
var backColor;
|
||||
|
||||
var config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
channel: secret.channel,
|
||||
// our public key
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
transformFunction: JsonOT.validate,
|
||||
network: Cryptpad.getNetwork()
|
||||
};
|
||||
|
||||
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
|
||||
|
||||
var setHistory = function (bool, update) {
|
||||
isHistoryMode = bool;
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
config.onRemote();
|
||||
}
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
slideOptions: slideOptions
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
if (textColor) {
|
||||
obj.metadata.color = textColor;
|
||||
}
|
||||
if (backColor) {
|
||||
obj.metadata.backColor = backColor;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return stringify(obj);
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
Slide.update(textValue);
|
||||
|
||||
if (module.realtime.getUserDoc() !== shjson) {
|
||||
console.error("realtime.getUserDoc() !== shjson");
|
||||
}
|
||||
};
|
||||
|
||||
var metadataCfg = {
|
||||
slideOptions: function (newOpt) {
|
||||
if (stringify(newOpt) !== stringify(slideOptions)) {
|
||||
$.extend(slideOptions, newOpt);
|
||||
// TODO: manage realtime + cursor in the "options" modal ??
|
||||
Slide.updateOptions();
|
||||
}
|
||||
}
|
||||
};
|
||||
var updateColors = metadataCfg.slideColors = function (text, back) {
|
||||
if (text) {
|
||||
textColor = text;
|
||||
$modal.css('color', text);
|
||||
$modal.css('border-color', text);
|
||||
$pad.contents().find('#' + SLIDE_COLOR_ID).css('color', text);
|
||||
}
|
||||
if (back) {
|
||||
backColor = back;
|
||||
$modal.css('background-color', back);
|
||||
//$pad.contents().find('#' + SLIDE_COLOR_ID).css('background', back);
|
||||
$pad.contents().find('#' + SLIDE_BACKCOLOR_ID).css('color', back);
|
||||
}
|
||||
};
|
||||
|
||||
var createPrintDialog = function () {
|
||||
var slideOptionsTmp = {
|
||||
title: false,
|
||||
slide: false,
|
||||
date: false,
|
||||
transition: true,
|
||||
style: ''
|
||||
};
|
||||
|
||||
$.extend(slideOptionsTmp, slideOptions);
|
||||
var $container = $('<div class="alertify">');
|
||||
var $container2 = $('<div class="dialog">').appendTo($container);
|
||||
var $div = $('<div id="printOptions">').appendTo($container2);
|
||||
var $p = $('<p>', {'class': 'msg'}).appendTo($div);
|
||||
$('<b>').text(Messages.printOptions).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// Slide number
|
||||
$('<input>', {type: 'checkbox', id: 'checkNumber', checked: slideOptionsTmp.slide}).on('change', function () {
|
||||
var c = this.checked;
|
||||
slideOptionsTmp.slide = c;
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkNumber'}).text(Messages.printSlideNumber).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// Date
|
||||
$('<input>', {type: 'checkbox', id: 'checkDate', checked: slideOptionsTmp.date}).on('change', function () {
|
||||
var c = this.checked;
|
||||
slideOptionsTmp.date = c;
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkDate'}).text(Messages.printDate).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// Title
|
||||
$('<input>', {type: 'checkbox', id: 'checkTitle', checked: slideOptionsTmp.title}).on('change', function () {
|
||||
var c = this.checked;
|
||||
slideOptionsTmp.title = c;
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkTitle'}).text(Messages.printTitle).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// Transition
|
||||
$('<input>', {type: 'checkbox', id: 'checkTransition', checked: slideOptionsTmp.transition}).on('change', function () {
|
||||
var c = this.checked;
|
||||
slideOptionsTmp.transition = c;
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkTransition'}).text(Messages.printTransition).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// CSS
|
||||
$('<label>', {'for': 'cssPrint'}).text(Messages.printCSS).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
var $textarea = $('<textarea>', {'id':'cssPrint'}).css({'width':'100%', 'height':'100px'}).appendTo($p)
|
||||
.on('keydown keyup', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$textarea.val(slideOptionsTmp.style);
|
||||
window.setTimeout(function () { $textarea.focus(); }, 0);
|
||||
|
||||
var h;
|
||||
|
||||
var todo = function () {
|
||||
$.extend(slideOptions, slideOptionsTmp);
|
||||
slideOptions.style = $textarea.val();
|
||||
onLocal();
|
||||
$container.remove();
|
||||
Cryptpad.stopListening(h);
|
||||
};
|
||||
var todoCancel = function () {
|
||||
$container.remove();
|
||||
Cryptpad.stopListening(h);
|
||||
};
|
||||
|
||||
h = Cryptpad.listenForKeys(todo, todoCancel);
|
||||
|
||||
var $nav = $('<nav>').appendTo($div);
|
||||
$('<button>', {'class': 'cancel'}).text(Messages.cancelButton).appendTo($nav).click(todoCancel);
|
||||
$('<button>', {'class': 'ok'}).text(Messages.settings_save).appendTo($nav).click(todo);
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var titleCfg = {
|
||||
updateLocalTitle: setTabTitle,
|
||||
getHeadingText: CodeMirror.getHeadingText
|
||||
};
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
Slide.setTitle(Title);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, 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: ifrw,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar,
|
||||
$contentContainer: $contentContainer
|
||||
};
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
var $drawer = toolbar.$drawer;
|
||||
|
||||
var editHash;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal,
|
||||
onRemote: config.onRemote,
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$drawer.append($hist);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
|
||||
$drawer.append($export);
|
||||
|
||||
if (!readOnly) {
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
|
||||
$drawer.append($import);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
};
|
||||
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
var fileDialogCfg = {
|
||||
$body: $iframe.find('body'),
|
||||
onSelect: function (href) {
|
||||
var parsed = Cryptpad.parsePadUrl(href);
|
||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
|
||||
editor.replaceSelection(mt);
|
||||
},
|
||||
data: APP
|
||||
};
|
||||
$('<button>', {
|
||||
title: Messages.filePickerButton,
|
||||
'class': 'rightside-button fa fa-picture-o',
|
||||
style: 'font-size: 17px'
|
||||
}).click(function () {
|
||||
Cryptpad.createFileDialog(fileDialogCfg);
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
|
||||
$previewButton.removeClass('fa-question').addClass('fa-eye');
|
||||
$previewButton.attr('title', Messages.previewButtonTitle);
|
||||
$previewButton.click(function () {
|
||||
var $c = $iframe.find('#editorContainer');
|
||||
if ($c.hasClass('preview')) {
|
||||
Cryptpad.setPadAttribute('previewMode', false, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
$previewButton.removeClass('active');
|
||||
return void $c.removeClass('preview');
|
||||
}
|
||||
Cryptpad.setPadAttribute('previewMode', true, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
$c.addClass('preview');
|
||||
$previewButton.addClass('active');
|
||||
Slide.updateFontSize();
|
||||
});
|
||||
$rightside.append($previewButton);
|
||||
|
||||
var $printButton = $('<button>', {
|
||||
title: Messages.printButtonTitle,
|
||||
'class': 'rightside-button fa fa-print',
|
||||
style: 'font-size: 17px'
|
||||
}).click(function () {
|
||||
Slide.update(editor.getValue(), true);
|
||||
$print.html($content.html());
|
||||
Cryptpad.confirm("Are you sure you want to print?", function (yes) {
|
||||
if (yes) {
|
||||
window.frames["pad-iframe"].focus();
|
||||
window.frames["pad-iframe"].print();
|
||||
}
|
||||
}, {ok: Messages.printButton});
|
||||
Cryptpad.feedback('PRINT_SLIDES');
|
||||
//$('body').append(createPrintDialog());
|
||||
}).append($('<span>', {'class': 'drawer'}).text(Messages.printText));
|
||||
|
||||
$drawer.append($printButton);
|
||||
|
||||
var $slideOptions = $('<button>', {
|
||||
title: Messages.slideOptionsTitle,
|
||||
'class': 'rightside-button fa fa-cog',
|
||||
style: 'font-size: 17px'
|
||||
}).click(function () {
|
||||
$('body').append(createPrintDialog());
|
||||
}).append($('<span>', {'class': 'drawer'}).text(Messages.slideOptionsText));
|
||||
$drawer.append($slideOptions);
|
||||
|
||||
var $present = Cryptpad.createButton('present', true)
|
||||
.click(function () {
|
||||
enterPresentationMode(true);
|
||||
});
|
||||
if (presentMode) {
|
||||
$present.hide();
|
||||
}
|
||||
$rightside.append($present);
|
||||
|
||||
var configureColors = function () {
|
||||
var $back = $('<button>', {
|
||||
id: SLIDE_BACKCOLOR_ID,
|
||||
'class': 'fa fa-square rightside-button',
|
||||
'style': 'font-family: FontAwesome; color: #000;',
|
||||
title: Messages.backgroundButtonTitle
|
||||
});
|
||||
var $text = $('<button>', {
|
||||
id: SLIDE_COLOR_ID,
|
||||
'class': 'fa fa-i-cursor rightside-button',
|
||||
'style': 'font-family: FontAwesome; font-weight: bold; color: #fff;',
|
||||
title: Messages.colorButtonTitle
|
||||
});
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
var $check = $pad.contents().find("#colorPicker_check");
|
||||
if ($testColor.attr('type') !== "color" || $testColor.val() === '!') { return; }
|
||||
$back.on('click', function() {
|
||||
var $picker = $('<input>', { type: 'color', value: backColor })
|
||||
.css({ display: 'none', })
|
||||
.on('change', function() {
|
||||
updateColors(undefined, this.value);
|
||||
onLocal();
|
||||
});
|
||||
$check.append($picker);
|
||||
setTimeout(function() {
|
||||
$picker.click();
|
||||
}, 0);
|
||||
});
|
||||
$text.on('click', function() {
|
||||
var $picker = $('<input>', { type: 'color', value: textColor })
|
||||
.css({ display: 'none', })
|
||||
.on('change', function() {
|
||||
updateColors(this.value, undefined);
|
||||
onLocal();
|
||||
$check.html('');
|
||||
});
|
||||
$check.append($picker);
|
||||
setTimeout(function() {
|
||||
$picker.click();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$rightside.append($back).append($text);
|
||||
};
|
||||
|
||||
configureColors();
|
||||
CodeMirror.configureTheme();
|
||||
|
||||
if (presentMode) {
|
||||
$('#top-bar').hide();
|
||||
}
|
||||
|
||||
// set the hash
|
||||
if (!window.location.hash || window.location.hash === '#') {
|
||||
Cryptpad.replaceHash(editHash);
|
||||
}
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
//logging: true
|
||||
});
|
||||
}
|
||||
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
var isNew = false;
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
|
||||
var newDoc = "";
|
||||
if(userDoc !== "") {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
newDoc = hjson.content;
|
||||
|
||||
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'slide')) {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
|
||||
if (hjson.highlightMode) {
|
||||
CodeMirror.setMode(hjson.highlightMode);
|
||||
}
|
||||
}
|
||||
if (!CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode('markdown');
|
||||
}
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
Metadata.update(userDoc);
|
||||
editor.setValue(newDoc || initialState);
|
||||
|
||||
if (Cryptpad.initialName && Title.isDefaultTitle()) {
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
}
|
||||
|
||||
Cryptpad.getPadAttribute('previewMode', function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if ([true, undefined].indexOf(data) !== -1 && APP.$previewButton) {
|
||||
APP.$previewButton.click();
|
||||
}
|
||||
});
|
||||
|
||||
Slide.onChange(function (o, n, l) {
|
||||
var slideNumber = '';
|
||||
if (n !== null) {
|
||||
if (Slide.shown) { //Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + (++n) + '/' + l + ')';
|
||||
}
|
||||
}
|
||||
document.title = Title.title + slideNumber;
|
||||
});
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
|
||||
onLocal(); // push local state to avoid parse errors later.
|
||||
Slide.update(editor.getValue());
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
|
||||
var fmConfig = {
|
||||
dropArea: $iframe.find('.CodeMirror'),
|
||||
body: $iframe.find('body'),
|
||||
onUploaded: function (ev, data) {
|
||||
//var cursor = editor.getCursor();
|
||||
//var cleanName = data.name.replace(/[\[\]]/g, '');
|
||||
//var text = '!['+cleanName+']('+data.url+')';
|
||||
var parsed = Cryptpad.parsePadUrl(data.url);
|
||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
|
||||
editor.replaceSelection(mt);
|
||||
}
|
||||
};
|
||||
APP.FM = Cryptpad.createFileManager(fmConfig);
|
||||
};
|
||||
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode(highlightMode);
|
||||
}
|
||||
|
||||
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
|
||||
if (!readOnly) {
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson2 = stringifyInner(textValue);
|
||||
if (shjson2 !== shjson) {
|
||||
console.error("shjson2 !== shjson");
|
||||
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
|
||||
module.patchText(shjson2);
|
||||
}
|
||||
}
|
||||
Slide.update(remoteDoc);
|
||||
|
||||
if (oldDoc !== remoteDoc) {
|
||||
Cryptpad.notify();
|
||||
}
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
if (!Slide.isPresentURL()) {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
config.onError = onConnectError;
|
||||
|
||||
module.realtime = Realtime.start(config);
|
||||
|
||||
editor.on('change', onLocal);
|
||||
|
||||
Cryptpad.onLogout(function () { setEditable(false); });
|
||||
};
|
||||
|
||||
var interval = 100;
|
||||
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var first = function () {
|
||||
if (ifrw.CodeMirror) {
|
||||
// it exists, call your continuation
|
||||
second(ifrw.CodeMirror);
|
||||
} else {
|
||||
console.log("CodeMirror was not defined. Trying again in %sms", interval);
|
||||
// try again in 'interval' ms
|
||||
setTimeout(first, interval);
|
||||
}
|
||||
};
|
||||
|
||||
first();
|
||||
});
|
||||
});
|
@ -1,428 +0,0 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 37px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 34px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 31px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 27px;
|
||||
}
|
||||
h6 {
|
||||
font-size: 24px;
|
||||
}
|
||||
body .CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
body .CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
#colorPicker_check {
|
||||
display: block;
|
||||
}
|
||||
@media print {
|
||||
@page {
|
||||
margin: 0;
|
||||
size: landscape;
|
||||
}
|
||||
body {
|
||||
display: block;
|
||||
}
|
||||
body .CodeMirror,
|
||||
body #cme_toolbox {
|
||||
display: none;
|
||||
}
|
||||
body * {
|
||||
visibility: hidden;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
html .cp #print {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
html .cp #print * {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
#cme_toolbox {
|
||||
z-index: 10000;
|
||||
}
|
||||
#editorContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#editorContainer .CodeMirror {
|
||||
resize: none;
|
||||
width: 100vw;
|
||||
}
|
||||
#editorContainer.preview .CodeMirror {
|
||||
width: 50vw;
|
||||
}
|
||||
.preview .cp {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.preview .cp div#modal:not(.shown) {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
width: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
.preview .cp div#modal:not(.shown) #content .slide-container {
|
||||
width: 100%;
|
||||
}
|
||||
.preview .cp div#modal:not(.shown) #content .slide-frame {
|
||||
width: 50vw;
|
||||
height: 28.125vw;
|
||||
max-height: 100vh;
|
||||
max-width: 177.78vh;
|
||||
}
|
||||
.cp {
|
||||
/* Slide position (print mode) */
|
||||
/* Slide position (present mode) */
|
||||
/* Slide content */
|
||||
}
|
||||
.cp #print {
|
||||
position: relative;
|
||||
display: none;
|
||||
font-size: 10.125vw;
|
||||
/*.slide-frame:first-child {
|
||||
margin-top: ~"calc(((100vh - 56.25vw)/2))";
|
||||
}*/
|
||||
}
|
||||
.cp #print .slide-frame {
|
||||
display: block !important;
|
||||
padding: 0.5em;
|
||||
margin: auto;
|
||||
border: 1px solid black;
|
||||
height: 50.625vw;
|
||||
width: 90vw;
|
||||
page-break-after: always;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cp #print .slide-frame li {
|
||||
min-width: 45vw;
|
||||
}
|
||||
.cp #print .slide-frame h1 {
|
||||
padding-top: 0;
|
||||
}
|
||||
.cp #print .slide-container {
|
||||
width: 90vw;
|
||||
height: 100vh;
|
||||
margin: 0vh 5vw;
|
||||
display: flex;
|
||||
}
|
||||
.cp #print .slide-container:last-child {
|
||||
height: calc(100vh - 2px);
|
||||
}
|
||||
.cp div.modal,
|
||||
.cp div#modal {
|
||||
background-color: black;
|
||||
color: white;
|
||||
/* Navigation buttons */
|
||||
box-sizing: border-box;
|
||||
z-index: 9001;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: none;
|
||||
background-color: #000;
|
||||
}
|
||||
.cp div.modal .button,
|
||||
.cp div#modal .button {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
font-size: 30px;
|
||||
opacity: 0.6;
|
||||
display: none;
|
||||
z-index: 9001;
|
||||
}
|
||||
.cp div.modal .button:hover,
|
||||
.cp div#modal .button:hover {
|
||||
opacity: 1;
|
||||
display: block !important;
|
||||
}
|
||||
.cp div.modal #button_exit,
|
||||
.cp div#modal #button_exit {
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
.cp div.modal #button_left,
|
||||
.cp div#modal #button_left {
|
||||
left: 6vw;
|
||||
bottom: 10vh;
|
||||
}
|
||||
.cp div.modal #button_right,
|
||||
.cp div#modal #button_right {
|
||||
right: 6vw;
|
||||
bottom: 10vh;
|
||||
}
|
||||
.cp div.modal.shown,
|
||||
.cp div#modal.shown {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 100000;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
.cp div.modal #content,
|
||||
.cp div#modal #content {
|
||||
font-size: 20vh;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cp div.modal #content .slide-frame,
|
||||
.cp div#modal #content .slide-frame {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid;
|
||||
white-space: normal;
|
||||
vertical-align: middle;
|
||||
/* center things as much as possible
|
||||
|
||||
margin-top: 50vh;
|
||||
margin-bottom: 50vh;
|
||||
transform: translateY(-50%);
|
||||
|
||||
*/
|
||||
padding: 0.5em;
|
||||
width: 100vw;
|
||||
height: 56.25vw;
|
||||
max-height: 100vh;
|
||||
max-width: 177.78vh;
|
||||
margin: auto;
|
||||
}
|
||||
.cp div.modal #content .slide-container,
|
||||
.cp div#modal #content .slide-container {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp div.modal #content.transition .slide-container,
|
||||
.cp div#modal #content.transition .slide-container {
|
||||
transition: margin-left 1s;
|
||||
}
|
||||
.cp div.modal .center,
|
||||
.cp div#modal .center {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: auto;
|
||||
border: 1px solid #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
.cp div.modal.shown,
|
||||
.cp div#modal.shown {
|
||||
display: block;
|
||||
}
|
||||
.cp div#modal #content .slide-frame,
|
||||
.cp #print .slide-frame {
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
.cp div#modal #content .slide-frame *,
|
||||
.cp #print .slide-frame * {
|
||||
font-size: 27.5%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame * *,
|
||||
.cp #print .slide-frame * * {
|
||||
font-size: 100%;
|
||||
line-height: 1em;
|
||||
}
|
||||
.cp div#modal #content .slide-frame ul ul,
|
||||
.cp #print .slide-frame ul ul,
|
||||
.cp div#modal #content .slide-frame ol ul,
|
||||
.cp #print .slide-frame ol ul,
|
||||
.cp div#modal #content .slide-frame ul ol,
|
||||
.cp #print .slide-frame ul ol,
|
||||
.cp div#modal #content .slide-frame ol ol,
|
||||
.cp #print .slide-frame ol ol {
|
||||
margin: 0;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h1,
|
||||
.cp #print .slide-frame h1 {
|
||||
font-size: 50%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h2,
|
||||
.cp #print .slide-frame h2 {
|
||||
font-size: 42%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h3,
|
||||
.cp #print .slide-frame h3 {
|
||||
font-size: 36%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h4,
|
||||
.cp #print .slide-frame h4 {
|
||||
font-size: 30%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h5,
|
||||
.cp #print .slide-frame h5 {
|
||||
font-size: 22%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h6,
|
||||
.cp #print .slide-frame h6 {
|
||||
font-size: 16%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame h1,
|
||||
.cp #print .slide-frame h1,
|
||||
.cp div#modal #content .slide-frame h2,
|
||||
.cp #print .slide-frame h2,
|
||||
.cp div#modal #content .slide-frame h3,
|
||||
.cp #print .slide-frame h3,
|
||||
.cp div#modal #content .slide-frame h4,
|
||||
.cp #print .slide-frame h4,
|
||||
.cp div#modal #content .slide-frame h5,
|
||||
.cp #print .slide-frame h5,
|
||||
.cp div#modal #content .slide-frame h6,
|
||||
.cp #print .slide-frame h6 {
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
padding-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.cp div#modal #content .slide-frame pre > code,
|
||||
.cp #print .slide-frame pre > code {
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 1px solid #333;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
padding-left: .25vw;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.cp div#modal #content .slide-frame ul,
|
||||
.cp #print .slide-frame ul,
|
||||
.cp div#modal #content .slide-frame ol,
|
||||
.cp #print .slide-frame ol {
|
||||
min-width: 50%;
|
||||
max-width: 100%;
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
.cp div#modal #content .slide-frame img,
|
||||
.cp #print .slide-frame img {
|
||||
position: relative;
|
||||
min-width: 1%;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
.cp div#modal #content .slide-frame .slideNumber,
|
||||
.cp #print .slide-frame .slideNumber {
|
||||
position: absolute;
|
||||
right: 5vh;
|
||||
bottom: 5vh;
|
||||
font-size: 10%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame .slideDate,
|
||||
.cp #print .slide-frame .slideDate {
|
||||
position: absolute;
|
||||
left: 5vh;
|
||||
bottom: 5vh;
|
||||
font-size: 10%;
|
||||
line-height: 110%;
|
||||
}
|
||||
.cp div#modal #content .slide-frame .slideTitle,
|
||||
.cp #print .slide-frame .slideTitle {
|
||||
position: absolute;
|
||||
bottom: 5vh;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
text-align: center;
|
||||
font-size: 10%;
|
||||
line-height: 110%;
|
||||
}
|
||||
#fileDialog {
|
||||
position: absolute;
|
||||
background-color: rgba(200, 200, 200, 0.8);
|
||||
top: 15vh;
|
||||
bottom: 15vh;
|
||||
left: 10vw;
|
||||
right: 10vw;
|
||||
border: 1px solid black;
|
||||
z-index: 10;
|
||||
overflow: auto;
|
||||
display: none;
|
||||
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
#fileDialog .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#fileDialog .element {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid #ccc;
|
||||
margin: 5px;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
#fileDialog .element span {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
@ -1,321 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/diffMarked.js',
|
||||
'/common/cryptpad-common.js',
|
||||
],function ($, DiffMd, Cryptpad) {
|
||||
|
||||
var Slide = {
|
||||
index: 0,
|
||||
lastIndex: 0,
|
||||
content: [],
|
||||
changeHandlers: [],
|
||||
};
|
||||
var ifrw;
|
||||
var $modal;
|
||||
var $content;
|
||||
var $pad;
|
||||
var placeholder;
|
||||
var options;
|
||||
var separator = '<hr data-pewpew="pezpez">';
|
||||
var separatorReg = /<hr data\-pewpew="pezpez">/g;
|
||||
var slideClass = 'slide-frame';
|
||||
var Title;
|
||||
|
||||
Slide.onChange = function (f) {
|
||||
if (typeof(f) === 'function') {
|
||||
Slide.changeHandlers.push(f);
|
||||
}
|
||||
};
|
||||
|
||||
var getNumberOfSlides = Slide.getNumberOfSlides = function () {
|
||||
return $content.find('.' + slideClass).length;
|
||||
};
|
||||
|
||||
var change = function (oldIndex, newIndex) {
|
||||
if (Slide.changeHandlers.length) {
|
||||
Slide.changeHandlers.some(function (f) {
|
||||
f(oldIndex, newIndex, getNumberOfSlides());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var updateFontSize = Slide.updateFontSize = function () {
|
||||
// 20vh
|
||||
// 20 * 16 / 9vw
|
||||
var wbase = 20;
|
||||
var vh = 20;
|
||||
var $elem = $(window);
|
||||
if (!Slide.shown) {
|
||||
wbase = 10;
|
||||
vh *= $content.height()/$(window).height();
|
||||
$elem = $content;
|
||||
}
|
||||
if ($elem.width() > 16/9*$elem.height()) {
|
||||
$content.css('font-size', vh+'vh');
|
||||
// $print.css('font-size', '20vh');
|
||||
return;
|
||||
}
|
||||
$content.css('font-size', (wbase*9/16)+'vw');
|
||||
// $print.css('font-size', (20*9/16)+'vw');
|
||||
};
|
||||
|
||||
var fixCSS = function (css) {
|
||||
var append = '.cp #print .slide-frame ';
|
||||
var append2 = '.cp div#modal #content .slide-frame ';
|
||||
return css.replace(/(\n*)([^\n}]+)\s*\{/g, '$1' + append + '$2,' + append2 + '$2 {');
|
||||
};
|
||||
|
||||
var goTo = Slide.goTo = function (i) {
|
||||
i = i || 0;
|
||||
Slide.index = i;
|
||||
$content.find('.slide-container').first().css('margin-left', -(i*100)+'%');
|
||||
updateFontSize();
|
||||
change(Slide.lastIndex, Slide.index);
|
||||
$modal.find('#button_left > span').css({
|
||||
opacity: Slide.index === 0? 0: 1
|
||||
});
|
||||
$modal.find('#button_right > span').css({
|
||||
opacity: Slide.index === (getNumberOfSlides() -1)? 0: 1
|
||||
});
|
||||
};
|
||||
var draw = Slide.draw = function (i) {
|
||||
if (typeof(Slide.content) !== 'string') { return; }
|
||||
|
||||
var c = Slide.content;
|
||||
var m = '<span class="slide-container"><span class="'+slideClass+'">'+DiffMd.render(c).replace(separatorReg, '</span></span><span class="slide-container"><span class="'+slideClass+'">')+'</span></span>';
|
||||
|
||||
DiffMd.apply(m, $content);
|
||||
|
||||
var length = getNumberOfSlides();
|
||||
$modal.find('style.slideStyle').remove();
|
||||
if (options.style && Slide.shown) {
|
||||
$modal.prepend($('<style>', {'class': 'slideStyle'}).text(fixCSS(options.style)));
|
||||
}
|
||||
$content.find('.slide-frame').each(function (i, el) {
|
||||
if (options.slide) {
|
||||
$('<div>', {'class': 'slideNumber'}).text((i+1)+'/'+length).appendTo($(el));
|
||||
}
|
||||
if (options.date) {
|
||||
$('<div>', {'class': 'slideDate'}).text(new Date().toLocaleDateString()).appendTo($(el));
|
||||
}
|
||||
if (options.title) {
|
||||
$('<div>', {'class': 'slideTitle'}).text(Title.title).appendTo($(el));
|
||||
}
|
||||
});
|
||||
$content.removeClass('transition');
|
||||
if (options.transition || typeof(options.transition) === "undefined") {
|
||||
$content.addClass('transition');
|
||||
}
|
||||
//$content.find('.' + slideClass).hide();
|
||||
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
|
||||
//$content.css('margin-left', -(i*100)+'vw');
|
||||
goTo(Math.min(i, getNumberOfSlides() - 1));
|
||||
};
|
||||
|
||||
Slide.updateOptions = function () {
|
||||
draw(Slide.index);
|
||||
};
|
||||
|
||||
var isPresentURL = Slide.isPresentURL = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return parsed && parsed.hashData && parsed.hashData.present;
|
||||
};
|
||||
|
||||
var show = Slide.show = function (bool, content) {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
var hashData = parsed.hashData || {};
|
||||
Slide.shown = bool;
|
||||
if (bool) {
|
||||
Slide.update(content);
|
||||
Slide.draw(Slide.index);
|
||||
$modal.addClass('shown');
|
||||
$(ifrw).focus();
|
||||
change(null, Slide.index);
|
||||
if (!isPresentURL()) {
|
||||
window.location += parsed.getUrl({present: true, embed: hashData.embed});
|
||||
}
|
||||
$pad.contents().find('.cryptpad-present-button').hide();
|
||||
$pad.contents().find('.cryptpad-source-button').show();
|
||||
$pad.addClass('fullscreen');
|
||||
$('#iframe-container').addClass('fullscreen');
|
||||
$('.top-bar').hide();
|
||||
updateFontSize();
|
||||
return;
|
||||
}
|
||||
window.location = parsed.getUrl({embed: hashData.embed});
|
||||
change(Slide.index, null);
|
||||
$pad.contents().find('.cryptpad-present-button').show();
|
||||
$pad.contents().find('.cryptpad-source-button').hide();
|
||||
$pad.removeClass('fullscreen');
|
||||
$('#iframe-container').removeClass('fullscreen');
|
||||
$('.top-bar').show();
|
||||
$modal.removeClass('shown');
|
||||
updateFontSize();
|
||||
};
|
||||
|
||||
Slide.update = function (content) {
|
||||
updateFontSize();
|
||||
//if (!init) { return; }
|
||||
if (!content) { content = ''; }
|
||||
var old = Slide.content;
|
||||
Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
|
||||
if (old !== Slide.content) {
|
||||
draw(Slide.index);
|
||||
return;
|
||||
}
|
||||
change(Slide.lastIndex, Slide.index);
|
||||
};
|
||||
|
||||
Slide.left = function () {
|
||||
console.log('left');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
var i = Slide.index = Math.max(0, Slide.index - 1);
|
||||
Slide.goTo(i);
|
||||
};
|
||||
|
||||
Slide.right = function () {
|
||||
console.log('right');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
var i = Slide.index = Math.min(getNumberOfSlides() -1, Slide.index + 1);
|
||||
Slide.goTo(i);
|
||||
};
|
||||
|
||||
Slide.first = function () {
|
||||
console.log('first');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
var i = Slide.index = 0;
|
||||
Slide.goTo(i);
|
||||
};
|
||||
|
||||
Slide.last = function () {
|
||||
console.log('end');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
var i = Slide.index = getNumberOfSlides() - 1;
|
||||
Slide.goTo(i);
|
||||
};
|
||||
|
||||
var addEvent = function () {
|
||||
var icon_to;
|
||||
$modal.mousemove(function () {
|
||||
var $buttons = $modal.find('.button');
|
||||
$buttons.show();
|
||||
if (icon_to) { window.clearTimeout(icon_to); }
|
||||
icon_to = window.setTimeout(function() {
|
||||
$buttons.fadeOut();
|
||||
}, 1000);
|
||||
});
|
||||
$modal.find('#button_exit').click(function () {
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 27;
|
||||
$modal.trigger(ev);
|
||||
});
|
||||
$modal.find('#button_left').click(function () {
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 37;
|
||||
$modal.trigger(ev);
|
||||
});
|
||||
$modal.find('#button_right').click(function () {
|
||||
console.log('right');
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 39;
|
||||
$modal.trigger(ev);
|
||||
});
|
||||
|
||||
$pad.contents().find('.CodeMirror').keyup(function (e) { e.stopPropagation(); });
|
||||
$(ifrw).on('keyup', function (e) {
|
||||
//if (!Slide.shown) { return; }
|
||||
if (e.ctrlKey) { return; }
|
||||
switch(e.which) {
|
||||
case 33: // pageup
|
||||
case 38: // up
|
||||
case 37: // left
|
||||
Slide.left();
|
||||
break;
|
||||
case 34: // pagedown
|
||||
case 32: // space
|
||||
case 40: // down
|
||||
case 39: // right
|
||||
Slide.right();
|
||||
break;
|
||||
case 36: // home
|
||||
Slide.first();
|
||||
break;
|
||||
case 35: // end
|
||||
Slide.last();
|
||||
break;
|
||||
case 27: // esc
|
||||
show(false);
|
||||
break;
|
||||
default:
|
||||
console.log(e.which);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$(window).resize(Slide.updateFontSize);
|
||||
|
||||
// Swipe
|
||||
var addSwipeEvents = function () {
|
||||
var touch = {
|
||||
maxTime: 2000,
|
||||
minXDist: 150,
|
||||
maxYDist: 100
|
||||
};
|
||||
|
||||
var resetSwipe = function () {
|
||||
touch.x = 0;
|
||||
touch.y = 0;
|
||||
touch.time = 0;
|
||||
};
|
||||
|
||||
$content.on('touchstart', function (e) {
|
||||
e.preventDefault();
|
||||
resetSwipe();
|
||||
var t = e.originalEvent.changedTouches[0];
|
||||
touch.x = t.pageX;
|
||||
touch.y = t.pageY;
|
||||
touch.time = new Date().getTime();
|
||||
});
|
||||
|
||||
$content.on('touchend', function (e) {
|
||||
e.preventDefault();
|
||||
var t = e.originalEvent.changedTouches[0];
|
||||
var xDist = t.pageX - touch.x;
|
||||
var yDist = t.pageY - touch.y;
|
||||
var time = new Date().getTime() - touch.time;
|
||||
if (time <= touch.maxTime && Math.abs(xDist) >= touch.minXDist && Math.abs(yDist) <= touch.maxYDist) {
|
||||
if (xDist < 0) {
|
||||
Slide.right();
|
||||
return;
|
||||
}
|
||||
Slide.left();
|
||||
}
|
||||
});
|
||||
|
||||
$content.on('touchmove', function (e){
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Slide.setModal = function ($m, $c, $p, iframe, opt, ph) {
|
||||
$modal = Slide.$modal = $m;
|
||||
$content = Slide.$content = $c;
|
||||
$pad = Slide.$pad = $p;
|
||||
ifrw = Slide.ifrw = iframe;
|
||||
placeholder = Slide.placeholder = ph;
|
||||
options = Slide.options = opt;
|
||||
addEvent();
|
||||
addSwipeEvents();
|
||||
};
|
||||
|
||||
Slide.setTitle = function (titleObj) {
|
||||
Title = titleObj;
|
||||
};
|
||||
|
||||
return Slide;
|
||||
});
|
@ -1,398 +0,0 @@
|
||||
@import "/customize/src/less/variables.less";
|
||||
@import "/customize/src/less/mixins.less";
|
||||
@import "/common/markdown.less";
|
||||
|
||||
// used for slides
|
||||
.viewportRatio (@x, @y, @p: 100) {
|
||||
width: @p * 100vw;
|
||||
height: @y * (@p * 100vw) / @x;
|
||||
max-width: @x / @y * (@p * 100vh);
|
||||
max-height: (@p * 100vh);
|
||||
}
|
||||
|
||||
html, body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
font-size: unset;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
//.cp {
|
||||
h1 { font-size: 40px; }
|
||||
h2 { font-size: 37px; }
|
||||
h3 { font-size: 34px; }
|
||||
h4 { font-size: 31px; }
|
||||
h5 { font-size: 27px; }
|
||||
h6 { font-size: 24px; }
|
||||
|
||||
body {
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
font-size: initial;
|
||||
}
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
#colorPicker_check {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
margin: 0;
|
||||
size: landscape;
|
||||
}
|
||||
body {
|
||||
.CodeMirror, #cme_toolbox {
|
||||
display: none;
|
||||
}
|
||||
* {
|
||||
visibility: hidden;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
display:block;
|
||||
}
|
||||
html, body {
|
||||
height: auto;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
html {
|
||||
.cp #print {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
* {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.cp #modal {
|
||||
display: none !important;
|
||||
}
|
||||
.cp {
|
||||
flex: 1 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.userlist-drawer {
|
||||
display: none !important;
|
||||
}
|
||||
#editorContainer {
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#cme_toolbox {
|
||||
z-index: 10000;
|
||||
}
|
||||
#editorContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.CodeMirror {
|
||||
resize: none;
|
||||
width: 100vw;
|
||||
}
|
||||
&.preview {
|
||||
.CodeMirror {
|
||||
//resize: horizontal;
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
.preview {
|
||||
.cp {
|
||||
width: 50vw;
|
||||
overflow: hidden;
|
||||
div#modal:not(.shown) {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
width: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
#content {
|
||||
.slide-container {
|
||||
width: 100%;
|
||||
}
|
||||
.slide-frame {
|
||||
width: 50vw;
|
||||
height: 28.125vw; // height:width ratio = 9/16 = .5625
|
||||
max-height: ~"calc(100vh - 96px)";
|
||||
max-width: ~"calc(16 / 9 * (100vh - 96px))";
|
||||
//max-height: 100vh;
|
||||
//max-width: 177.78vh; // 16/9 = 1.778
|
||||
}
|
||||
}
|
||||
#button_exit {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
.CodeMirror {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.cp {
|
||||
|
||||
/* Slide position (print mode) */
|
||||
@ratio:0.9;
|
||||
#print {
|
||||
position: relative;
|
||||
display: none;
|
||||
font-size: @ratio*11.25vw;
|
||||
.slide-frame {
|
||||
display: block !important;
|
||||
padding: 0.5em;
|
||||
margin: auto;
|
||||
border: 1px solid black;
|
||||
height: @ratio*56.25vw;
|
||||
width: @ratio*100vw;
|
||||
page-break-after: always;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
li {
|
||||
min-width: @ratio*50vw;
|
||||
}
|
||||
h1 {
|
||||
padding-top: 0;
|
||||
}
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
.slide-container {
|
||||
width: 90vw;
|
||||
height: 100vh;
|
||||
margin: 0vh 5vw !important;
|
||||
display: flex;
|
||||
&:last-child {
|
||||
height: ~"calc(100vh - 2px)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Slide position (present mode) */
|
||||
div.modal, div#modal {
|
||||
display: none;
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
||||
/* Navigation buttons */
|
||||
.button {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
font-size: 30px;
|
||||
opacity: 0.6;
|
||||
display: none;
|
||||
z-index: 9001;
|
||||
}
|
||||
.button:hover {
|
||||
opacity: 1;
|
||||
display: block !important;
|
||||
}
|
||||
#button_exit {
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
#button_left {
|
||||
left: 6vw;
|
||||
bottom: 10vh;
|
||||
}
|
||||
#button_right {
|
||||
right: 6vw;
|
||||
bottom: 10vh;
|
||||
}
|
||||
&.shown {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 100000;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
#content {
|
||||
font-size: 20vh;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
.slide-frame {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column !important;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: 1px solid;
|
||||
white-space: normal;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
padding: 0.5em;
|
||||
width: 100vw;
|
||||
height: 56.25vw; // height:width ratio = 9/16 = .5625
|
||||
max-height: 100vh;
|
||||
max-width: 177.78vh; // 16/9 = 1.778
|
||||
margin: auto;
|
||||
}
|
||||
.slide-container {
|
||||
display: inline-flex;
|
||||
height: 100%; width: 100vw;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
&.transition {
|
||||
.slide-container {
|
||||
transition: margin-left 1s;
|
||||
}
|
||||
}
|
||||
media-tag button {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
box-sizing: border-box;
|
||||
z-index: 9001;
|
||||
position: fixed;
|
||||
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: none;
|
||||
|
||||
background-color: @slide-default-bg;
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: auto;
|
||||
border: 1px solid @light-base;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.shown {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slide content */
|
||||
div#modal #content, #print {
|
||||
.slide-frame {
|
||||
* {
|
||||
.size(2.75);
|
||||
* {
|
||||
font-size: 1em;
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
ul, ol {
|
||||
ul, ol {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1 { .size(5); }
|
||||
h2 { .size(4.2); }
|
||||
h3 { .size(3.6); }
|
||||
h4 { .size (3); }
|
||||
h5 { .size(2.2); }
|
||||
h6 { .size(1.6); }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
padding-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.markdown_preformatted-code;
|
||||
|
||||
ul, ol {
|
||||
min-width: 50%;
|
||||
max-width: 100%;
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
|
||||
// fixes image overflowing
|
||||
media-tag {
|
||||
height: 100%;
|
||||
|
||||
& + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
min-width: 1%;
|
||||
margin: auto;
|
||||
}
|
||||
.slideNumber {
|
||||
position: absolute;
|
||||
right: 5vh;
|
||||
bottom: 5vh;
|
||||
.size(1);
|
||||
}
|
||||
.slideDate {
|
||||
position: absolute;
|
||||
left: 5vh;
|
||||
bottom: 5vh;
|
||||
.size(1);
|
||||
}
|
||||
.slideTitle {
|
||||
position: absolute;
|
||||
bottom: 5vh;
|
||||
left: 0px; right: 0px;
|
||||
text-align: center;
|
||||
.size(1);
|
||||
}
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "/common/file-dialog.less";
|
||||
|
||||
.slide-frame * {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
//display: flex;
|
||||
//flex-flow: row wrap;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height:0;
|
||||
min-width:0;
|
||||
//flex: 1;
|
||||
}
|
||||
|
||||
@import "../customize/src/less2/include/mediatag.less";
|
||||
|
||||
.mediatag_base();
|
@ -0,0 +1,52 @@
|
||||
define(function () {
|
||||
var padZero = function (str, len) {
|
||||
len = len || 2;
|
||||
var zeros = new Array(len).join('0');
|
||||
return (zeros + str).slice(-len);
|
||||
};
|
||||
var invertColor = function (hex) {
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
// convert 3-digit hex to 6-digits.
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
if (hex.length !== 6) {
|
||||
console.error(hex);
|
||||
throw new Error('Invalid HEX color.');
|
||||
}
|
||||
// invert color components
|
||||
var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
|
||||
g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
|
||||
b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
|
||||
// pad each with zeros and return
|
||||
return '#' + padZero(r) + padZero(g) + padZero(b);
|
||||
};
|
||||
var rgb2hex = function (rgb) {
|
||||
if (rgb.indexOf('#') === 0) { return rgb; }
|
||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
var hex = function (x) {
|
||||
return ("0" + parseInt(x).toString(16)).slice(-2);
|
||||
};
|
||||
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
||||
};
|
||||
var hex2rgba = function (hex, opacity) {
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
if (!opacity) { opacity = 1; }
|
||||
var r = parseInt(hex.slice(0,2), 16);
|
||||
var g = parseInt(hex.slice(2,4), 16);
|
||||
var b = parseInt(hex.slice(4,6), 16);
|
||||
return 'rgba('+r+', '+g+', '+b+', '+opacity+')';
|
||||
};
|
||||
return {
|
||||
invert: invertColor,
|
||||
rgb2hex: rgb2hex,
|
||||
hex2rgba: hex2rgba
|
||||
};
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -0,0 +1,511 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/whiteboard/colors.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.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/cryptpad.less',
|
||||
'less!/whiteboard/whiteboard.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, AppConfig, Thumb) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = { $:$ };
|
||||
var Fabric = module.Fabric = window.fabric;
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var andThen = function () {
|
||||
/* Initialize Fabric */
|
||||
var canvas = module.canvas = new Fabric.Canvas('canvas');
|
||||
var $canvas = $('canvas');
|
||||
var $controls = $('#controls');
|
||||
var $canvasContainer = $('canvas').parents('.canvas-container');
|
||||
var $pickers = $('#pickers');
|
||||
var $colors = $('#colors');
|
||||
var $cursors = $('#cursors');
|
||||
var $deleteButton = $('#delete');
|
||||
|
||||
var brush = {
|
||||
color: '#000000',
|
||||
opacity: 1
|
||||
};
|
||||
|
||||
var $toggle = $('#toggleDraw');
|
||||
var $width = $('#width');
|
||||
var $widthLabel = $('label[for="width"]');
|
||||
var $opacity = $('#opacity');
|
||||
var $opacityLabel = $('label[for="opacity"]');
|
||||
window.canvas = canvas;
|
||||
var createCursor = function () {
|
||||
var w = canvas.freeDrawingBrush.width;
|
||||
var c = canvas.freeDrawingBrush.color;
|
||||
var size = w > 30 ? w+2 : w+32;
|
||||
$cursors.html('<canvas width="'+size+'" height="'+size+'"></canvas>');
|
||||
var $ccanvas = $cursors.find('canvas');
|
||||
var ccanvas = $ccanvas[0];
|
||||
|
||||
var ctx = ccanvas.getContext('2d');
|
||||
var centerX = size / 2;
|
||||
var centerY = size / 2;
|
||||
var radius = w/2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = c;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = brush.color;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10);
|
||||
ctx.moveTo(size/2, size); ctx.lineTo(size/2, size-10);
|
||||
ctx.moveTo(0, size/2); ctx.lineTo(10, size/2);
|
||||
ctx.moveTo(size, size/2); ctx.lineTo(size-10, size/2);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.stroke();
|
||||
|
||||
var img = ccanvas.toDataURL("image/png");
|
||||
$controls.find('.selected > img').attr('src', img);
|
||||
canvas.freeDrawingCursor = 'url('+img+') '+size/2+' '+size/2+', crosshair';
|
||||
};
|
||||
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
|
||||
$('#width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushWidth();
|
||||
|
||||
$width.on('change', updateBrushWidth);
|
||||
|
||||
var updateBrushOpacity = function () {
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$('#opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushOpacity();
|
||||
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
var pickColor = function (current, cb) {
|
||||
var $picker = $('<input>', {
|
||||
type: 'color',
|
||||
value: '#FFFFFF',
|
||||
})
|
||||
// TODO confirm that this is safe to remove
|
||||
//.css({ visibility: 'hidden' })
|
||||
.on('change', function () {
|
||||
var color = this.value;
|
||||
cb(color);
|
||||
}).appendTo($pickers);
|
||||
setTimeout(function () {
|
||||
$picker.val(current);
|
||||
$picker.click();
|
||||
});
|
||||
};
|
||||
|
||||
var setColor = function (c) {
|
||||
c = Colors.rgb2hex(c);
|
||||
brush.color = c;
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
module.$color.css({
|
||||
'color': c,
|
||||
});
|
||||
createCursor();
|
||||
};
|
||||
|
||||
|
||||
var palette = AppConfig.whiteboardPalette || [
|
||||
'red', 'blue', 'green', 'white', 'black', 'purple',
|
||||
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
|
||||
];
|
||||
|
||||
$('.palette-color').on('click', function () {
|
||||
var color = $(this).css('background-color');
|
||||
setColor(color);
|
||||
});
|
||||
|
||||
module.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
module.draw = !module.draw;
|
||||
canvas.isDrawingMode = module.draw;
|
||||
$toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (module.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
if (canvas.getActiveGroup()) {
|
||||
canvas.getActiveGroup()._objects.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
canvas.discardActiveGroup();
|
||||
}
|
||||
canvas.renderAll();
|
||||
module.onLocal();
|
||||
};
|
||||
$deleteButton.click(deleteSelection);
|
||||
$(window).on('keyup', function (e) {
|
||||
if (e.which === 46) { deleteSelection (); }
|
||||
});
|
||||
|
||||
var setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
if (bool) { $controls.css('display', 'flex'); }
|
||||
else { $controls.hide(); }
|
||||
|
||||
canvas.isDrawingMode = bool ? module.draw : false;
|
||||
if (!bool) {
|
||||
canvas.deactivateAll();
|
||||
canvas.renderAll();
|
||||
}
|
||||
canvas.forEachObject(function (object) {
|
||||
object.selectable = bool;
|
||||
});
|
||||
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
|
||||
};
|
||||
|
||||
var saveImage = module.saveImage = function () {
|
||||
var defaultName = "pretty-picture.png";
|
||||
Cryptpad.prompt(Messages.exportPrompt, defaultName, function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.FM = Cryptpad.createFileManager({});
|
||||
module.upload = function (title) {
|
||||
var canvas = $canvas[0];
|
||||
var finish = function (thumb) {
|
||||
canvas.toBlob(function (blob) {
|
||||
blob.name = title;
|
||||
module.FM.handleFile(blob, void 0, thumb);
|
||||
});
|
||||
};
|
||||
|
||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
||||
// carry on even if you can't get a thumbnail
|
||||
if (e) { console.error(e); }
|
||||
finish(blob);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
validateKey: secret.keys.validateKey,
|
||||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
var addColorToPalette = function (color, i) {
|
||||
if (readOnly) { return; }
|
||||
var $color = $('<span>', {
|
||||
'class': 'palette-color',
|
||||
})
|
||||
.css({
|
||||
'background-color': color,
|
||||
})
|
||||
.click(function () {
|
||||
var c = Colors.rgb2hex($color.css('background-color'));
|
||||
setColor(c);
|
||||
})
|
||||
.on('dblclick', function (e) {
|
||||
e.preventDefault();
|
||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||
$color.css({
|
||||
'background-color': c,
|
||||
});
|
||||
palette.splice(i, 1, c);
|
||||
config.onLocal();
|
||||
setColor(c);
|
||||
});
|
||||
});
|
||||
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var metadataCfg = {};
|
||||
var updatePalette = metadataCfg.updatePalette = function (newPalette) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
};
|
||||
updatePalette(palette);
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
// if colors aren't supported, bail out
|
||||
if ($testColor.attr('type') !== 'color' ||
|
||||
$testColor.val() === '!') {
|
||||
console.log("Colors aren't supported. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var $color = module.$color = $('<button>', {
|
||||
id: "color-picker",
|
||||
title: Messages.canvas_chooseColor,
|
||||
'class': "fa fa-square rightside-button",
|
||||
})
|
||||
.on('click', function () {
|
||||
pickColor($color.css('background-color'), function (color) {
|
||||
setColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
setColor('#000');
|
||||
|
||||
$container.append($color);
|
||||
|
||||
return $color;
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
Title = Cryptpad.createTitle({}, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, 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: $bar,
|
||||
$contentContainer: $('#canvas-area')
|
||||
};
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
var $export = Cryptpad.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
Cryptpad.createButton('savetodrive', true, {}, function () {})
|
||||
.click(function () {
|
||||
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
|
||||
function (name) {
|
||||
if (name === null || !name.trim()) { return; }
|
||||
module.upload(name);
|
||||
});
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
});
|
||||
$rightside.append($forget);
|
||||
|
||||
var editHash;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
makeColorButton($rightside);
|
||||
}
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
// used for debugging, feel free to remove
|
||||
var Catch = function (f) {
|
||||
return function () {
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
Metadata.update(userDoc);
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
// TODO update palette if it has changed
|
||||
|
||||
canvas.loadFromJSON(remoteDoc);
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
});
|
||||
setEditable(false);
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
palette: palette,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
type: 'whiteboard',
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
|
||||
var onLocal = module.onLocal = config.onLocal = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
var content = stringifyInner(canvas.toDatalessJSON());
|
||||
|
||||
module.patchText(content);
|
||||
});
|
||||
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
});
|
||||
|
||||
var isNew = false;
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
else {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
if (typeof(hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'whiteboard')) {
|
||||
Cryptpad.errorLoadingScreen(Messages.typeError);
|
||||
throw new Error(Messages.typeError);
|
||||
}
|
||||
}
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
// TODO onConnectionStateChange
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
$('#clear').on('click', function () {
|
||||
canvas.clear();
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$('#save').on('click', function () {
|
||||
saveImage();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info) {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -0,0 +1,60 @@
|
||||
CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
var Messages = editor.plugins.mediatag.translations;
|
||||
return {
|
||||
title: Messages.title,
|
||||
minWidth: 400,
|
||||
minHeight: 200,
|
||||
contents: [
|
||||
{
|
||||
id: 'tab-basic',
|
||||
label: Messages.title,
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
id: 'width',
|
||||
label: Messages.width,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'height',
|
||||
label: Messages.height,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
onShow: function () {
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
var rect = el.getClientRect();
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
var wInput = inputs[0];
|
||||
var hInput = inputs[1];
|
||||
wInput.value = Math.round(rect.width);
|
||||
hInput.value = Math.round(rect.height);
|
||||
},
|
||||
onOk: function() {
|
||||
var dialog = this;
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
var wInput = inputs[0];
|
||||
var hInput = inputs[1];
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (wInput.value === "") {
|
||||
el.removeAttribute('width');
|
||||
el.removeStyle('width');
|
||||
} else {
|
||||
el.setSize('width', parseInt(wInput.value));
|
||||
}
|
||||
if (hInput.value === "") {
|
||||
el.removeAttribute('height');
|
||||
el.removeStyle('height');
|
||||
} else {
|
||||
el.setSize('height', parseInt(hInput.value));
|
||||
}
|
||||
editor.fire( 'change' );
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview The Image plugin.
|
||||
*/
|
||||
|
||||
( function() {
|
||||
|
||||
CKEDITOR.plugins.add( 'mediatag', {
|
||||
requires: 'dialog',
|
||||
//icons: 'image',
|
||||
//hidpi: true,
|
||||
onLoad: function () {
|
||||
|
||||
CKEDITOR.addCss(
|
||||
'media-tag{' +
|
||||
'display:inline-block;' +
|
||||
'}' +
|
||||
'media-tag.selected{' +
|
||||
'border: 1px solid black;' +
|
||||
'}' +
|
||||
'media-tag iframe{' +
|
||||
'border: 6px solid #eee;' +
|
||||
'}' +
|
||||
'media-tag img{' +
|
||||
'vertical-align: top;' +
|
||||
'}' +
|
||||
'media-tag *{' +
|
||||
'width:100%; height:100%;' +
|
||||
'}');
|
||||
},
|
||||
init: function( editor ) {
|
||||
var pluginName = 'mediatag';
|
||||
|
||||
// Register the dialog.
|
||||
CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' );
|
||||
|
||||
var allowed = 'media-tag[!data-crypto-key,!src,contenteditable,width,height]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
|
||||
required = 'media-tag[data-crypto-key,src]';
|
||||
|
||||
// Register the command.
|
||||
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, {
|
||||
allowedContent: allowed,
|
||||
requiredContent: required,
|
||||
contentTransformations: [
|
||||
[ 'media-tag{width}: sizeToStyle', 'media-tag[width]: sizeToAttribute' ],
|
||||
[ 'media-tag{float}: alignmentToStyle', 'media-tag[align]: alignmentToAttribute' ]
|
||||
]
|
||||
} ) );
|
||||
|
||||
var isMediaTag = function (el) {
|
||||
if (el.is('media-tag')) { return el; }
|
||||
var mt = el.getParents().slice().filter(function (p) {
|
||||
return p.is('media-tag');
|
||||
});
|
||||
if (mt.length !== 1) { return; }
|
||||
return mt[0];
|
||||
};
|
||||
editor.on('doubleclick', function (evt) {
|
||||
var element = evt.data.element;
|
||||
var mt = isMediaTag(element);
|
||||
if (mt && !element.data('cke-realelement')) {
|
||||
editor.plugins.mediatag.clicked = mt;
|
||||
evt.data.dialog = 'mediatag';
|
||||
}
|
||||
});
|
||||
|
||||
// If the "contextmenu" plugin is loaded, register the listeners.
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function (element) {
|
||||
if (getSelectedMediatag(editor, element)) {
|
||||
return { mediatag: CKEDITOR.TRISTATE_OFF };
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
afterInit: function( editor ) {
|
||||
// Customize the behavior of the alignment commands. (http://dev.ckeditor.com/ticket/7430)
|
||||
setupAlignCommand('left');
|
||||
setupAlignCommand('right');
|
||||
setupAlignCommand('center');
|
||||
setupAlignCommand('block');
|
||||
|
||||
function setupAlignCommand (value) {
|
||||
var command = editor.getCommand('justify' + value);
|
||||
if (command) {
|
||||
if (value === 'left' || value === 'right') {
|
||||
command.on('exec', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
if (align === value) {
|
||||
img.removeStyle('float');
|
||||
|
||||
// Remove "align" attribute when necessary.
|
||||
if (value === getMediatagAlignment(img))
|
||||
img.removeAttribute( 'align' );
|
||||
} else {
|
||||
img.setStyle( 'float', value );
|
||||
}
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
command.on('refresh', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
|
||||
this.setState(
|
||||
(align === value) ? CKEDITOR.TRISTATE_ON : ( value === 'right' || value === 'left' ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
function getSelectedMediatag (editor, element) {
|
||||
if (!element) {
|
||||
var sel = editor.getSelection();
|
||||
element = sel.getSelectedElement();
|
||||
}
|
||||
|
||||
if (element && element.is('media-tag') && !element.data('cke-realelement')
|
||||
&& !element.isReadOnly()) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
function getMediatagAlignment (element) {
|
||||
var align = element.getStyle('float');
|
||||
|
||||
if (align === 'inherit' || align === 'none') {
|
||||
align = 0;
|
||||
}
|
||||
|
||||
if (!align) {
|
||||
align = element.getAttribute('align');
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
} )();
|
||||
|
||||
/**
|
||||
* Determines whether dimension inputs should be automatically filled when the image URL changes in the Image plugin dialog window.
|
||||
*
|
||||
* config.image_prefillDimensions = false;
|
||||
*
|
||||
* @since 4.5
|
||||
* @cfg {Boolean} [image_prefillDimensions=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether to remove links when emptying the link URL field in the Image dialog window.
|
||||
*
|
||||
* config.image_removeLinkByEmptyURL = false;
|
||||
*
|
||||
* @cfg {Boolean} [image_removeLinkByEmptyURL=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
CKEDITOR.config.mediatag_removeLinkByEmptyURL = true;
|
||||
|
||||
/**
|
||||
* Padding text to set off the image in the preview area.
|
||||
*
|
||||
* config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 );
|
||||
*
|
||||
* @cfg {String} [image_previewText='Lorem ipsum dolor...' (placeholder text)]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
@ -0,0 +1,148 @@
|
||||
@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';
|
||||
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-whiteboard {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
|
||||
.middle () {
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// created in the html
|
||||
#cp-app-whiteboard-canvas-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
// created by fabricjs. styled so defaults don't break anything
|
||||
.cp-app-whiteboard-canvas-container {
|
||||
margin: auto;
|
||||
background: white;
|
||||
& > canvas {
|
||||
border: 1px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// contains user tools
|
||||
#cp-app-whiteboard-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: relative;
|
||||
border-top: 1px solid black;
|
||||
background: white;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
& > * + * {
|
||||
margin: 0;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#cp-app-whiteboard-width, #cp-app-whiteboard-opacity {
|
||||
.middle;
|
||||
}
|
||||
#cp-app-whiteboard-clear, #cp-app-whiteboard-delete, #cp-app-whiteboard-toggledraw {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cp-app-whiteboard-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9001;
|
||||
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-range-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
input[type="range"] {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
& > span {
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.cp-app-whiteboard-range-group:first-of-type {
|
||||
margin-left: 2em;
|
||||
}
|
||||
.cp-app-whiteboard-range-group:last-of-type {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
#cp-app-whiteboard-colors {
|
||||
.middle;
|
||||
z-index: 100;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
span.cp-app-whiteboard-palette-color {
|
||||
height: 4vw;
|
||||
width: 4vw;
|
||||
display: block;
|
||||
margin: 5px;
|
||||
border: 1px solid black;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used in the toolbar if supported
|
||||
#cp-app-whiteboard-color-picker {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// input[type=color] must exist in the dom to work correctly
|
||||
// styled so that they don't break layouts
|
||||
|
||||
#cp-app-whiteboard-pickers {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -5;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<html>
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#sbox-iframe {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
#sbox-filePicker-iframe {
|
||||
position: fixed;
|
||||
top:0; left:0;
|
||||
bottom:0; right:0;
|
||||
width:100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<iframe id="sbox-iframe">
|
||||
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/whiteboard/inner.js" data-main="/common/sframe-boot.js?ver=1.5" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-whiteboard">
|
||||
|
@ -1,511 +1,41 @@
|
||||
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/whiteboard/colors.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.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/cryptpad.less',
|
||||
'less!/whiteboard/whiteboard.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, AppConfig, Thumb) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = { $:$ };
|
||||
var Fabric = module.Fabric = window.fabric;
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var andThen = function () {
|
||||
/* Initialize Fabric */
|
||||
var canvas = module.canvas = new Fabric.Canvas('canvas');
|
||||
var $canvas = $('canvas');
|
||||
var $controls = $('#controls');
|
||||
var $canvasContainer = $('canvas').parents('.canvas-container');
|
||||
var $pickers = $('#pickers');
|
||||
var $colors = $('#colors');
|
||||
var $cursors = $('#cursors');
|
||||
var $deleteButton = $('#delete');
|
||||
|
||||
var brush = {
|
||||
color: '#000000',
|
||||
opacity: 1
|
||||
};
|
||||
|
||||
var $toggle = $('#toggleDraw');
|
||||
var $width = $('#width');
|
||||
var $widthLabel = $('label[for="width"]');
|
||||
var $opacity = $('#opacity');
|
||||
var $opacityLabel = $('label[for="opacity"]');
|
||||
window.canvas = canvas;
|
||||
var createCursor = function () {
|
||||
var w = canvas.freeDrawingBrush.width;
|
||||
var c = canvas.freeDrawingBrush.color;
|
||||
var size = w > 30 ? w+2 : w+32;
|
||||
$cursors.html('<canvas width="'+size+'" height="'+size+'"></canvas>');
|
||||
var $ccanvas = $cursors.find('canvas');
|
||||
var ccanvas = $ccanvas[0];
|
||||
|
||||
var ctx = ccanvas.getContext('2d');
|
||||
var centerX = size / 2;
|
||||
var centerY = size / 2;
|
||||
var radius = w/2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = c;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = brush.color;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10);
|
||||
ctx.moveTo(size/2, size); ctx.lineTo(size/2, size-10);
|
||||
ctx.moveTo(0, size/2); ctx.lineTo(10, size/2);
|
||||
ctx.moveTo(size, size/2); ctx.lineTo(size-10, size/2);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.stroke();
|
||||
|
||||
var img = ccanvas.toDataURL("image/png");
|
||||
$controls.find('.selected > img').attr('src', img);
|
||||
canvas.freeDrawingCursor = 'url('+img+') '+size/2+' '+size/2+', crosshair';
|
||||
};
|
||||
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
|
||||
$('#width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushWidth();
|
||||
|
||||
$width.on('change', updateBrushWidth);
|
||||
|
||||
var updateBrushOpacity = function () {
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$('#opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushOpacity();
|
||||
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
var pickColor = function (current, cb) {
|
||||
var $picker = $('<input>', {
|
||||
type: 'color',
|
||||
value: '#FFFFFF',
|
||||
})
|
||||
// TODO confirm that this is safe to remove
|
||||
//.css({ visibility: 'hidden' })
|
||||
.on('change', function () {
|
||||
var color = this.value;
|
||||
cb(color);
|
||||
}).appendTo($pickers);
|
||||
setTimeout(function () {
|
||||
$picker.val(current);
|
||||
$picker.click();
|
||||
});
|
||||
};
|
||||
|
||||
var setColor = function (c) {
|
||||
c = Colors.rgb2hex(c);
|
||||
brush.color = c;
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
module.$color.css({
|
||||
'color': c,
|
||||
});
|
||||
createCursor();
|
||||
};
|
||||
|
||||
|
||||
var palette = AppConfig.whiteboardPalette || [
|
||||
'red', 'blue', 'green', 'white', 'black', 'purple',
|
||||
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
|
||||
];
|
||||
|
||||
$('.palette-color').on('click', function () {
|
||||
var color = $(this).css('background-color');
|
||||
setColor(color);
|
||||
});
|
||||
|
||||
module.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
module.draw = !module.draw;
|
||||
canvas.isDrawingMode = module.draw;
|
||||
$toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (module.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
if (canvas.getActiveGroup()) {
|
||||
canvas.getActiveGroup()._objects.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
canvas.discardActiveGroup();
|
||||
}
|
||||
canvas.renderAll();
|
||||
module.onLocal();
|
||||
};
|
||||
$deleteButton.click(deleteSelection);
|
||||
$(window).on('keyup', function (e) {
|
||||
if (e.which === 46) { deleteSelection (); }
|
||||
});
|
||||
|
||||
var setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
if (bool) { $controls.css('display', 'flex'); }
|
||||
else { $controls.hide(); }
|
||||
|
||||
canvas.isDrawingMode = bool ? module.draw : false;
|
||||
if (!bool) {
|
||||
canvas.deactivateAll();
|
||||
canvas.renderAll();
|
||||
}
|
||||
canvas.forEachObject(function (object) {
|
||||
object.selectable = bool;
|
||||
});
|
||||
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
|
||||
};
|
||||
|
||||
var saveImage = module.saveImage = function () {
|
||||
var defaultName = "pretty-picture.png";
|
||||
Cryptpad.prompt(Messages.exportPrompt, defaultName, function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.FM = Cryptpad.createFileManager({});
|
||||
module.upload = function (title) {
|
||||
var canvas = $canvas[0];
|
||||
var finish = function (thumb) {
|
||||
canvas.toBlob(function (blob) {
|
||||
blob.name = title;
|
||||
module.FM.handleFile(blob, void 0, thumb);
|
||||
});
|
||||
};
|
||||
|
||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
||||
// carry on even if you can't get a thumbnail
|
||||
if (e) { console.error(e); }
|
||||
finish(blob);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
validateKey: secret.keys.validateKey,
|
||||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
var addColorToPalette = function (color, i) {
|
||||
if (readOnly) { return; }
|
||||
var $color = $('<span>', {
|
||||
'class': 'palette-color',
|
||||
})
|
||||
.css({
|
||||
'background-color': color,
|
||||
})
|
||||
.click(function () {
|
||||
var c = Colors.rgb2hex($color.css('background-color'));
|
||||
setColor(c);
|
||||
})
|
||||
.on('dblclick', function (e) {
|
||||
e.preventDefault();
|
||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||
$color.css({
|
||||
'background-color': c,
|
||||
});
|
||||
palette.splice(i, 1, c);
|
||||
config.onLocal();
|
||||
setColor(c);
|
||||
});
|
||||
});
|
||||
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var metadataCfg = {};
|
||||
var updatePalette = metadataCfg.updatePalette = function (newPalette) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
};
|
||||
updatePalette(palette);
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
// if colors aren't supported, bail out
|
||||
if ($testColor.attr('type') !== 'color' ||
|
||||
$testColor.val() === '!') {
|
||||
console.log("Colors aren't supported. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var $color = module.$color = $('<button>', {
|
||||
id: "color-picker",
|
||||
title: Messages.canvas_chooseColor,
|
||||
'class': "fa fa-square rightside-button",
|
||||
})
|
||||
.on('click', function () {
|
||||
pickColor($color.css('background-color'), function (color) {
|
||||
setColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
setColor('#000');
|
||||
|
||||
$container.append($color);
|
||||
|
||||
return $color;
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
Title = Cryptpad.createTitle({}, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, 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: $bar,
|
||||
$contentContainer: $('#canvas-area')
|
||||
};
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
var $export = Cryptpad.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
Cryptpad.createButton('savetodrive', true, {}, function () {})
|
||||
.click(function () {
|
||||
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
|
||||
function (name) {
|
||||
if (name === null || !name.trim()) { return; }
|
||||
module.upload(name);
|
||||
});
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
});
|
||||
$rightside.append($forget);
|
||||
|
||||
var editHash;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
makeColorButton($rightside);
|
||||
}
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
// used for debugging, feel free to remove
|
||||
var Catch = function (f) {
|
||||
return function () {
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
Metadata.update(userDoc);
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
// TODO update palette if it has changed
|
||||
|
||||
canvas.loadFromJSON(remoteDoc);
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
});
|
||||
setEditable(false);
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
palette: palette,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
type: 'whiteboard',
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
|
||||
var onLocal = module.onLocal = config.onLocal = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
var content = stringifyInner(canvas.toDatalessJSON());
|
||||
|
||||
module.patchText(content);
|
||||
});
|
||||
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
});
|
||||
|
||||
var isNew = false;
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
else {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
if (typeof(hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'whiteboard')) {
|
||||
Cryptpad.errorLoadingScreen(Messages.typeError);
|
||||
throw new Error(Messages.typeError);
|
||||
}
|
||||
}
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
// TODO onConnectionStateChange
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
$('#clear').on('click', function () {
|
||||
canvas.clear();
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$('#save').on('click', function () {
|
||||
saveImage();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info) {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
|
||||
'jquery',
|
||||
'/common/requireconfig.js',
|
||||
'/common/sframe-common-outer.js'
|
||||
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
// Loaded in load #2
|
||||
nThen(function (waitFor) {
|
||||
$(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
var req = {
|
||||
cfg: requireConfig,
|
||||
req: [ '/common/loading.js' ],
|
||||
pfx: window.location.origin
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
window.apiconf = ApiConfig;
|
||||
$('#sbox-iframe').attr('src',
|
||||
ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs +
|
||||
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||
|
||||
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||
// loading screen setup.
|
||||
var done = waitFor();
|
||||
var onMsg = function (msg) {
|
||||
var data = JSON.parse(msg.data);
|
||||
if (data.q !== 'READY') { return; }
|
||||
window.removeEventListener('message', onMsg);
|
||||
var _done = done;
|
||||
done = function () { };
|
||||
_done();
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
SFCommonO.start();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue