commit
ee99310e99
@ -0,0 +1,245 @@
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function (Util, Crypto) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var Hash = {};
|
||||
|
||||
var uint8ArrayToHex = Util.uint8ArrayToHex;
|
||||
var hexToBase64 = Util.hexToBase64;
|
||||
var base64ToHex = Util.base64ToHex;
|
||||
|
||||
// This implementation must match that on the server
|
||||
// it's used for a checksum
|
||||
Hash.hashChannelList = function (list) {
|
||||
return Nacl.util.encodeBase64(Nacl.hash(Nacl.util
|
||||
.decodeUTF8(JSON.stringify(list))));
|
||||
};
|
||||
|
||||
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
|
||||
if (typeof keys === 'string') {
|
||||
return chanKey + keys;
|
||||
}
|
||||
if (!keys.editKeyStr) { return; }
|
||||
return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr);
|
||||
};
|
||||
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
|
||||
if (typeof keys === 'string') {
|
||||
return;
|
||||
}
|
||||
return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr);
|
||||
};
|
||||
|
||||
var parsePadUrl = Hash.parsePadUrl = function (href) {
|
||||
var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
|
||||
|
||||
var ret = {};
|
||||
|
||||
if (!href) { return ret; }
|
||||
|
||||
if (!/^https*:\/\//.test(href)) {
|
||||
var idx = href.indexOf('/#');
|
||||
ret.type = href.slice(1, idx);
|
||||
ret.hash = href.slice(idx + 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var hash = href.replace(patt, function (a, domain, type, hash) {
|
||||
ret.domain = domain;
|
||||
ret.type = type;
|
||||
return '';
|
||||
});
|
||||
ret.hash = hash.replace(/#/g, '');
|
||||
return ret;
|
||||
};
|
||||
|
||||
var getRelativeHref = Hash.getRelativeHref = function (href) {
|
||||
if (!href) { return; }
|
||||
if (href.indexOf('#') === -1) { return; }
|
||||
var parsed = parsePadUrl(href);
|
||||
return '/' + parsed.type + '/#' + parsed.hash;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns all needed keys for a realtime channel
|
||||
* - no argument: use the URL hash or create one if it doesn't exist
|
||||
* - secretHash provided: use secretHash to find the keys
|
||||
*/
|
||||
var getSecrets = Hash.getSecrets = function (secretHash) {
|
||||
var secret = {};
|
||||
var generate = function () {
|
||||
secret.keys = Crypto.createEditCryptor();
|
||||
secret.key = Crypto.createEditCryptor().editKeyStr;
|
||||
};
|
||||
if (!secretHash && !/#/.test(window.location.href)) {
|
||||
generate();
|
||||
return secret;
|
||||
} else {
|
||||
var hash = secretHash || window.location.hash.slice(1);
|
||||
if (hash.length === 0) {
|
||||
generate();
|
||||
return secret;
|
||||
}
|
||||
// old hash system : #{hexChanKey}{cryptKey}
|
||||
// new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey}
|
||||
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
||||
// Old hash
|
||||
secret.channel = hash.slice(0, 32);
|
||||
secret.key = hash.slice(32);
|
||||
}
|
||||
else {
|
||||
// New hash
|
||||
var hashArray = hash.split('/');
|
||||
if (hashArray.length < 4) {
|
||||
Hash.alert("Unable to parse the key");
|
||||
throw new Error("Unable to parse the key");
|
||||
}
|
||||
var version = hashArray[1];
|
||||
if (version === "1") {
|
||||
var mode = hashArray[2];
|
||||
if (mode === 'edit') {
|
||||
secret.channel = base64ToHex(hashArray[3]);
|
||||
var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/'));
|
||||
secret.keys = keys;
|
||||
secret.key = keys.editKeyStr;
|
||||
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
||||
Hash.alert("The channel key and/or the encryption key is invalid");
|
||||
throw new Error("The channel key and/or the encryption key is invalid");
|
||||
}
|
||||
}
|
||||
else if (mode === 'view') {
|
||||
secret.channel = base64ToHex(hashArray[3]);
|
||||
secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/'));
|
||||
if (secret.channel.length !== 32) {
|
||||
Hash.alert("The channel key is invalid");
|
||||
throw new Error("The channel key is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return secret;
|
||||
};
|
||||
|
||||
var getHashes = Hash.getHashes = function (channel, secret) {
|
||||
var hashes = {};
|
||||
if (secret.keys.editKeyStr) {
|
||||
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
|
||||
}
|
||||
if (secret.keys.viewKeyStr) {
|
||||
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
|
||||
}
|
||||
return hashes;
|
||||
};
|
||||
|
||||
var createChannelId = Hash.createChannelId = function () {
|
||||
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
|
||||
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
|
||||
throw new Error('channel ids must consist of 32 hex characters');
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
var createRandomHash = Hash.createRandomHash = function () {
|
||||
// 16 byte channel Id
|
||||
var channelId = Util.hexToBase64(createChannelId());
|
||||
// 18 byte encryption key
|
||||
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
|
||||
return '/1/edit/' + [channelId, key].join('/');
|
||||
};
|
||||
|
||||
var parseHash = Hash.parseHash = function (hash) {
|
||||
var parsed = {};
|
||||
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
||||
// Old hash
|
||||
parsed.channel = hash.slice(0, 32);
|
||||
parsed.key = hash.slice(32);
|
||||
parsed.version = 0;
|
||||
return parsed;
|
||||
}
|
||||
var hashArr = hash.split('/');
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.mode = hashArr[2];
|
||||
parsed.channel = hashArr[3];
|
||||
parsed.key = hashArr[4];
|
||||
parsed.present = hashArr[5] && hashArr[5] === 'present';
|
||||
return parsed;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
var findWeaker = Hash.findWeaker = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
var parsed = parsePadUrl(rHref);
|
||||
if (!parsed.hash) { return false; }
|
||||
var weaker;
|
||||
recents.some(function (pad) {
|
||||
var p = parsePadUrl(pad.href);
|
||||
if (p.type !== parsed.type) { return; } // Not the same type
|
||||
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
|
||||
var pHash = parseHash(p.hash);
|
||||
var parsedHash = parseHash(parsed.hash);
|
||||
if (!parsedHash || !pHash) { return; }
|
||||
if (pHash.version !== parsedHash.version) { return; }
|
||||
if (pHash.channel !== parsedHash.channel) { return; }
|
||||
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
|
||||
weaker = pad.href;
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
return weaker;
|
||||
};
|
||||
var findStronger = Hash.findStronger = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
var parsed = parsePadUrl(rHref);
|
||||
if (!parsed.hash) { return false; }
|
||||
var stronger;
|
||||
recents.some(function (pad) {
|
||||
var p = parsePadUrl(pad.href);
|
||||
if (p.type !== parsed.type) { return; } // Not the same type
|
||||
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
|
||||
var pHash = parseHash(p.hash);
|
||||
var parsedHash = parseHash(parsed.hash);
|
||||
if (!parsedHash || !pHash) { return; }
|
||||
if (pHash.version !== parsedHash.version) { return; }
|
||||
if (pHash.channel !== parsedHash.channel) { return; }
|
||||
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
|
||||
stronger = pad.href;
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
return stronger;
|
||||
};
|
||||
var isNotStrongestStored = Hash.isNotStrongestStored = function (href, recents) {
|
||||
return findStronger(href, recents);
|
||||
};
|
||||
|
||||
var hrefToHexChannelId = Hash.hrefToHexChannelId = function (href) {
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed || !parsed.hash) { return; }
|
||||
|
||||
parsed = Hash.parseHash(parsed.hash);
|
||||
|
||||
if (parsed.version === 0) {
|
||||
return parsed.channel;
|
||||
} else if (parsed.version !== 1) {
|
||||
console.error("parsed href had no version");
|
||||
console.error(parsed);
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = parsed.channel;
|
||||
if (!channel) { return; }
|
||||
|
||||
var hex = base64ToHex(channel);
|
||||
return hex;
|
||||
};
|
||||
|
||||
return Hash;
|
||||
});
|
@ -0,0 +1,220 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/customize/messages.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify) {
|
||||
|
||||
var UI = {};
|
||||
|
||||
/*
|
||||
* Alertifyjs
|
||||
*/
|
||||
UI.Alertify = Alertify;
|
||||
|
||||
// set notification timeout
|
||||
Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000;
|
||||
|
||||
var findCancelButton = UI.findCancelButton = function () {
|
||||
return $('button.cancel');
|
||||
};
|
||||
|
||||
var findOKButton = UI.findOKButton = function () {
|
||||
return $('button.ok');
|
||||
};
|
||||
|
||||
var listenForKeys = UI.listenForKeys = function (yes, no) {
|
||||
var handler = function (e) {
|
||||
switch (e.which) {
|
||||
case 27: // cancel
|
||||
if (typeof(no) === 'function') { no(e); }
|
||||
no();
|
||||
break;
|
||||
case 13: // enter
|
||||
if (typeof(yes) === 'function') { yes(e); }
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$(window).keyup(handler);
|
||||
return handler;
|
||||
};
|
||||
|
||||
var stopListening = UI.stopListening = function (handler) {
|
||||
$(window).off('keyup', handler);
|
||||
};
|
||||
|
||||
UI.alert = function (msg, cb, force) {
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
var close = function (e) {
|
||||
findOKButton().click();
|
||||
};
|
||||
var keyHandler = listenForKeys(close, close);
|
||||
Alertify.alert(msg, function (ev) {
|
||||
cb(ev);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
window.setTimeout(function () {
|
||||
findOKButton().focus();
|
||||
});
|
||||
};
|
||||
|
||||
UI.prompt = function (msg, def, cb, opt, force) {
|
||||
opt = opt || {};
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function (e) { // yes
|
||||
findOKButton().click();
|
||||
}, function (e) { // no
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
Alertify
|
||||
.defaultValue(def || '')
|
||||
.okBtn(opt.ok || Messages.okButton || 'OK')
|
||||
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
|
||||
.prompt(msg, function (val, ev) {
|
||||
cb(val, ev);
|
||||
stopListening(keyHandler);
|
||||
}, function (ev) {
|
||||
cb(null, ev);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
};
|
||||
|
||||
UI.confirm = function (msg, cb, opt, force, styleCB) {
|
||||
opt = opt || {};
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function (e) {
|
||||
findOKButton().click();
|
||||
}, function (e) {
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
Alertify
|
||||
.okBtn(opt.ok || Messages.okButton || 'OK')
|
||||
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
|
||||
.confirm(msg, function () {
|
||||
cb(true);
|
||||
stopListening(keyHandler);
|
||||
}, function () {
|
||||
cb(false);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
|
||||
window.setTimeout(function () {
|
||||
var $ok = findOKButton();
|
||||
var $cancel = findCancelButton();
|
||||
if (opt.okClass) { $ok.addClass(opt.okClass); }
|
||||
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
|
||||
if (opt.reverseOrder) {
|
||||
$ok.insertBefore($ok.prev());
|
||||
}
|
||||
if (typeof(styleCB) === 'function') {
|
||||
styleCB($ok.closest('.dialog'));
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
UI.log = function (msg) {
|
||||
Alertify.success(Util.fixHTML(msg));
|
||||
};
|
||||
|
||||
UI.warn = function (msg) {
|
||||
Alertify.error(Util.fixHTML(msg));
|
||||
};
|
||||
|
||||
/*
|
||||
* spinner
|
||||
*/
|
||||
UI.spinner = function (parent) {
|
||||
var $target = $('<span>', {
|
||||
'class': 'fa fa-spinner fa-pulse fa-4x fa-fw'
|
||||
}).hide();
|
||||
|
||||
$(parent).append($target);
|
||||
|
||||
return {
|
||||
show: function () {
|
||||
$target.show();
|
||||
return this;
|
||||
},
|
||||
hide: function () {
|
||||
$target.hide();
|
||||
return this;
|
||||
},
|
||||
get: function () {
|
||||
return $target;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var LOADING = 'loading';
|
||||
|
||||
var getRandomTip = function () {
|
||||
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
|
||||
var keys = Object.keys(Messages.tips);
|
||||
var rdm = Math.floor(Math.random() * keys.length);
|
||||
return Messages.tips[keys[rdm]];
|
||||
};
|
||||
UI.addLoadingScreen = function (loadingText, hideTips) {
|
||||
var $loading, $container;
|
||||
if ($('#' + LOADING).length) {
|
||||
$loading = $('#' + LOADING).show();
|
||||
if (loadingText) {
|
||||
$('#' + LOADING).find('p').text(loadingText);
|
||||
}
|
||||
$container = $loading.find('.loadingContainer');
|
||||
} else {
|
||||
$loading = $('<div>', {id: LOADING});
|
||||
$container = $('<div>', {'class': 'loadingContainer'});
|
||||
$container.append('<img class="cryptofist" src="/customize/cryptofist_small.png" />');
|
||||
var $spinner = $('<div>', {'class': 'spinnerContainer'});
|
||||
UI.spinner($spinner).show();
|
||||
var $text = $('<p>').text(loadingText || Messages.loading);
|
||||
$container.append($spinner).append($text);
|
||||
$loading.append($container);
|
||||
$('body').append($loading);
|
||||
}
|
||||
if (Messages.tips && !hideTips) {
|
||||
var $loadingTip = $('<div>', {'id': 'loadingTip'});
|
||||
var $tip = $('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
|
||||
$loadingTip.css({
|
||||
'top': $('body').height()/2 + $container.height()/2 + 20 + 'px'
|
||||
});
|
||||
$('body').append($loadingTip);
|
||||
}
|
||||
};
|
||||
UI.removeLoadingScreen = function (cb) {
|
||||
$('#' + LOADING).fadeOut(750, cb);
|
||||
$('#loadingTip').css('top', '');
|
||||
window.setTimeout(function () {
|
||||
$('#loadingTip').fadeOut(750);
|
||||
}, 3000);
|
||||
};
|
||||
UI.errorLoadingScreen = function (error, transparent) {
|
||||
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }
|
||||
$('.spinnerContainer').hide();
|
||||
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
|
||||
$('#' + LOADING).find('p').html(error || Messages.error);
|
||||
};
|
||||
|
||||
var importContent = UI.importContent = function (type, f) {
|
||||
return function () {
|
||||
var $files = $('<input type="file">').click();
|
||||
$files.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) { f(e.target.result, file); };
|
||||
reader.readAsText(file, type);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return UI;
|
||||
});
|
@ -0,0 +1,85 @@
|
||||
define([], function () {
|
||||
var Util = {};
|
||||
|
||||
var find = Util.find = function (map, path) {
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof(p[n]) !== 'undefined' && p[n];
|
||||
}, map));
|
||||
};
|
||||
|
||||
var fixHTML = Util.fixHTML = function (str) {
|
||||
if (!str) { return ''; }
|
||||
return str.replace(/[<>&"']/g, function (x) {
|
||||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||||
});
|
||||
};
|
||||
|
||||
var hexToBase64 = Util.hexToBase64 = function (hex) {
|
||||
var hexArray = hex
|
||||
.replace(/\r|\n/g, "")
|
||||
.replace(/([\da-fA-F]{2}) ?/g, "0x$1 ")
|
||||
.replace(/ +$/, "")
|
||||
.split(" ");
|
||||
var byteString = String.fromCharCode.apply(null, hexArray);
|
||||
return window.btoa(byteString).replace(/\//g, '-').slice(0,-2);
|
||||
};
|
||||
|
||||
var base64ToHex = Util.base64ToHex = function (b64String) {
|
||||
var hexArray = [];
|
||||
atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
|
||||
var h = e.charCodeAt(0).toString(16);
|
||||
if (h.length === 1) { h = "0"+h; }
|
||||
hexArray.push(h);
|
||||
});
|
||||
return hexArray.join("");
|
||||
};
|
||||
|
||||
var uint8ArrayToHex = Util.uint8ArrayToHex = function (a) {
|
||||
// call slice so Uint8Arrays work as expected
|
||||
return Array.prototype.slice.call(a).map(function (e, i) {
|
||||
var n = Number(e & 0xff).toString(16);
|
||||
if (n === 'NaN') {
|
||||
throw new Error('invalid input resulted in NaN');
|
||||
}
|
||||
|
||||
switch (n.length) {
|
||||
case 0: return '00'; // just being careful, shouldn't happen
|
||||
case 1: return '0' + n;
|
||||
case 2: return n;
|
||||
default: throw new Error('unexpected value');
|
||||
}
|
||||
}).join('');
|
||||
};
|
||||
|
||||
var deduplicateString = Util.deduplicateString = function (array) {
|
||||
var a = array.slice();
|
||||
for(var i=0; i<a.length; i++) {
|
||||
for(var j=i+1; j<a.length; j++) {
|
||||
if(a[i] === a[j]) { a.splice(j--, 1); }
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
var getHash = Util.getHash = function () {
|
||||
return window.location.hash.slice(1);
|
||||
};
|
||||
|
||||
var replaceHash = Util.replaceHash = function (hash) {
|
||||
if (window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
return void window.history.replaceState({}, window.document.title, hash);
|
||||
}
|
||||
window.location.hash = hash;
|
||||
};
|
||||
|
||||
/*
|
||||
* Saving files
|
||||
*/
|
||||
var fixFileName = Util.fixFileName = function (filename) {
|
||||
return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
|
||||
.replace(/_+/g, '_');
|
||||
};
|
||||
|
||||
return Util;
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
define([
|
||||
'/common/virtual-dom.js',
|
||||
'/bower_components/hyperjson/hyperjson.amd.js',
|
||||
'/common/hyperscript.js'
|
||||
], function (vdom, hyperjson, hyperscript) {
|
||||
// complain if you don't find the required APIs
|
||||
if (!(vdom && hyperjson && hyperscript)) { throw new Error(); }
|
||||
|
||||
// Generate a matrix of conversions
|
||||
/*
|
||||
convert.dom.to.hjson, convert.hjson.to.dom,
|
||||
convert.dom.to.vdom, convert.vdom.to.dom,
|
||||
convert.vdom.to.hjson, convert.hjson.to.vdom
|
||||
|
||||
and of course, identify functions in case you try to
|
||||
convert a datatype to itself
|
||||
*/
|
||||
var convert = (function () {
|
||||
var Self = function (x) {
|
||||
return x;
|
||||
},
|
||||
methods = {
|
||||
dom:{
|
||||
dom: Self,
|
||||
hjson: hyperjson.fromDOM,
|
||||
vdom: function (D) {
|
||||
return hyperjson.callOn(hyperjson.fromDOM(D), vdom.h);
|
||||
}
|
||||
},
|
||||
hjson:{
|
||||
hjson: Self,
|
||||
dom: function (H) {
|
||||
// hyperjson.fromDOM,
|
||||
return hyperjson.callOn(H, hyperscript);
|
||||
},
|
||||
vdom: function (H) {
|
||||
return hyperjson.callOn(H, vdom.h);
|
||||
}
|
||||
},
|
||||
vdom:{
|
||||
vdom: Self,
|
||||
dom: function (V) {
|
||||
return vdom.create(V);
|
||||
},
|
||||
hjson: function (V) {
|
||||
return hyperjson.fromDOM(vdom.create(V));
|
||||
}
|
||||
}
|
||||
},
|
||||
convert = {};
|
||||
Object.keys(methods).forEach(function (method) {
|
||||
convert[method] = { to: methods[method] };
|
||||
});
|
||||
return convert;
|
||||
}());
|
||||
|
||||
convert.core = {
|
||||
vdom: vdom,
|
||||
hyperjson: hyperjson,
|
||||
hyperscript: hyperscript
|
||||
};
|
||||
return convert;
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,29 +0,0 @@
|
||||
define([], function () {
|
||||
return function (n) {
|
||||
n = n || 24; // default is 24 colours
|
||||
var r = 0.6,
|
||||
i = 0,
|
||||
t = [],
|
||||
rgb = [0,2,4];
|
||||
|
||||
while(i<n) { t.push(i++); }
|
||||
|
||||
var colours = t.map(function (c, I) {
|
||||
return '#'+ rgb.map(function (j) {
|
||||
var x = ((Math.sin(r*(I+22)+j)*127+128) *0x01<<0)
|
||||
.toString(16);
|
||||
return x.length<2?"0"+x:x;
|
||||
}).join("");
|
||||
});
|
||||
|
||||
var J = 0;
|
||||
return function () {
|
||||
var j = J++;
|
||||
if (colours[j]) {
|
||||
return colours[j];
|
||||
}
|
||||
J = 0;
|
||||
return colours[0];
|
||||
};
|
||||
};
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
define([
|
||||
'/api/config',
|
||||
], function (Config) {
|
||||
var urlArgs = Config && Config.requireConf && Config.requireConf.urlArgs;
|
||||
if (!urlArgs) { return; }
|
||||
document.querySelectorAll('link[rel="stylesheet"][data-rewrite-href]').forEach(function (e) {
|
||||
var href = e.getAttribute('data-rewrite-href');
|
||||
href += (/\?/.test(href)?'&':'?') + urlArgs;
|
||||
e.setAttribute('href', href);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,898 @@
|
||||
define([
|
||||
'jquery',
|
||||
], function ($) {
|
||||
var module = {};
|
||||
|
||||
var ROOT = module.ROOT = "root";
|
||||
var UNSORTED = module.UNSORTED = "unsorted";
|
||||
var TRASH = module.TRASH = "trash";
|
||||
var TEMPLATE = module.TEMPLATE = "template";
|
||||
|
||||
var init = module.init = function (files, config) {
|
||||
var exp = {};
|
||||
var Cryptpad = config.Cryptpad;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Cryptpad.storageKey;
|
||||
var NEW_FOLDER_NAME = Messages.fm_newFolder;
|
||||
var NEW_FILE_NAME = Messages.fm_newFile;
|
||||
|
||||
// Logging
|
||||
var DEBUG = config.DEBUG || false;
|
||||
var logging = function () {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
var log = config.log || logging;
|
||||
var logError = config.logError || logging;
|
||||
var debug = config.debug || logging;
|
||||
var error = exp.error = function() {
|
||||
exp.fixFiles();
|
||||
console.error.apply(console, arguments);
|
||||
};
|
||||
|
||||
// TODO: workgroup
|
||||
var workgroup = config.workgroup;
|
||||
|
||||
|
||||
/*
|
||||
* UTILS
|
||||
*/
|
||||
|
||||
var getStructure = exp.getStructure = function () {
|
||||
var a = {};
|
||||
a[ROOT] = {};
|
||||
a[UNSORTED] = [];
|
||||
a[TRASH] = {};
|
||||
a[FILES_DATA] = [];
|
||||
a[TEMPLATE] = [];
|
||||
return a;
|
||||
};
|
||||
var getHrefArray = function () {
|
||||
return [UNSORTED, TEMPLATE];
|
||||
};
|
||||
|
||||
|
||||
var compareFiles = function (fileA, fileB) { return fileA === fileB; };
|
||||
|
||||
var isFile = exp.isFile = function (element) {
|
||||
return typeof(element) === "string";
|
||||
};
|
||||
|
||||
var isReadOnlyFile = exp.isReadOnlyFile = function (element) {
|
||||
if (!isFile(element)) { return false; }
|
||||
var parsed = Cryptpad.parsePadUrl(element);
|
||||
if (!parsed) { return false; }
|
||||
var hash = parsed.hash;
|
||||
var pHash = Cryptpad.parseHash(hash);
|
||||
if (pHash && !pHash.mode) { return; }
|
||||
return pHash && pHash.mode === 'view';
|
||||
};
|
||||
|
||||
var isFolder = exp.isFolder = function (element) {
|
||||
return typeof(element) !== "string";
|
||||
};
|
||||
var isFolderEmpty = exp.isFolderEmpty = function (element) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
return Object.keys(element).length === 0;
|
||||
};
|
||||
|
||||
var hasSubfolder = exp.hasSubfolder = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var subfolder = 0;
|
||||
var addSubfolder = function (el, idx) {
|
||||
subfolder += isFolder(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
if (trashRoot) {
|
||||
if ($.isArray(element[f])) {
|
||||
element[f].forEach(addSubfolder);
|
||||
}
|
||||
} else {
|
||||
subfolder += isFolder(element[f]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return subfolder;
|
||||
};
|
||||
|
||||
var hasFile = exp.hasFile = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var file = 0;
|
||||
var addFile = function (el, idx) {
|
||||
file += isFile(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
if (trashRoot) {
|
||||
if ($.isArray(element[f])) {
|
||||
element[f].forEach(addFile);
|
||||
}
|
||||
} else {
|
||||
file += isFile(element[f]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
};
|
||||
|
||||
// Get data from AllFiles (Cryptpad_RECENTPADS)
|
||||
var getFileData = exp.getFileData = function (file) {
|
||||
if (!file) { return; }
|
||||
var res;
|
||||
files[FILES_DATA].some(function(arr) {
|
||||
var href = arr.href;
|
||||
if (href === file) {
|
||||
res = arr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
// Data from filesData
|
||||
var getTitle = exp.getTitle = function (href) {
|
||||
if (workgroup) { debug("No titles in workgroups"); return; }
|
||||
var data = getFileData(href);
|
||||
if (!href || !data) {
|
||||
error("getTitle called with a non-existing href: ", href);
|
||||
return;
|
||||
}
|
||||
return data.title;
|
||||
};
|
||||
|
||||
|
||||
// PATHS
|
||||
|
||||
var comparePath = exp.comparePath = function (a, b) {
|
||||
if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; }
|
||||
if (a.length !== b.length) { return false; }
|
||||
var result = true;
|
||||
var i = a.length - 1;
|
||||
while (result && i >= 0) {
|
||||
result = a[i] === b[i];
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var isSubpath = exp.isSubpath = function (path, parentPath) {
|
||||
var pathA = parentPath.slice();
|
||||
var pathB = path.slice(0, pathA.length);
|
||||
return comparePath(pathA, pathB);
|
||||
};
|
||||
|
||||
var isPathIn = exp.isPathIn = function (path, categories) {
|
||||
if (!categories) { return; }
|
||||
var idx = categories.indexOf('hrefArray');
|
||||
if (idx !== -1) {
|
||||
categories.splice(idx, 1);
|
||||
categories = categories.concat(getHrefArray());
|
||||
}
|
||||
return categories.some(function (c) {
|
||||
return Array.isArray(path) && path[0] === c;
|
||||
});
|
||||
};
|
||||
|
||||
var isInTrashRoot = exp.isInTrashRoot = function (path) {
|
||||
return path[0] === TRASH && path.length === 4;
|
||||
};
|
||||
|
||||
|
||||
// FIND
|
||||
|
||||
var findElement = function (root, pathInput) {
|
||||
if (!pathInput) {
|
||||
error("Invalid path:\n", pathInput, "\nin root\n", root);
|
||||
return;
|
||||
}
|
||||
if (pathInput.length === 0) { return root; }
|
||||
var path = pathInput.slice();
|
||||
var key = path.shift();
|
||||
if (typeof root[key] === "undefined") {
|
||||
debug("Unable to find the key '" + key + "' in the root object provided:", root);
|
||||
return;
|
||||
}
|
||||
return findElement(root[key], path);
|
||||
};
|
||||
|
||||
var find = exp.find = function (path) {
|
||||
return findElement(files, path);
|
||||
};
|
||||
|
||||
|
||||
// GET FILES
|
||||
|
||||
var getFilesRecursively = function (root, arr) {
|
||||
for (var e in root) {
|
||||
if (isFile(root[e])) {
|
||||
if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); }
|
||||
} else {
|
||||
getFilesRecursively(root[e], arr);
|
||||
}
|
||||
}
|
||||
};
|
||||
var _getFiles = {};
|
||||
_getFiles['array'] = function (cat) {
|
||||
if (!files[cat]) { files[cat] = []; }
|
||||
return files[cat].slice();
|
||||
};
|
||||
getHrefArray().forEach(function (c) {
|
||||
_getFiles[c] = function () { return _getFiles['array'](c); };
|
||||
});
|
||||
_getFiles['hrefArray'] = function () {
|
||||
var ret = [];
|
||||
getHrefArray().forEach(function (c) {
|
||||
ret = ret.concat(_getFiles[c]());
|
||||
});
|
||||
return Cryptpad.deduplicateString(ret);
|
||||
};
|
||||
_getFiles[ROOT] = function () {
|
||||
var ret = [];
|
||||
getFilesRecursively(files[ROOT], ret);
|
||||
return ret;
|
||||
};
|
||||
_getFiles[TRASH] = function () {
|
||||
var root = files[TRASH];
|
||||
var ret = [];
|
||||
var addFiles = function (el, idx) {
|
||||
if (isFile(el.element)) {
|
||||
if(ret.indexOf(el.element) === -1) { ret.push(el.element); }
|
||||
} else {
|
||||
getFilesRecursively(el.element, ret);
|
||||
}
|
||||
};
|
||||
for (var e in root) {
|
||||
if (!$.isArray(root[e])) {
|
||||
error("Trash contains a non-array element");
|
||||
return;
|
||||
}
|
||||
root[e].forEach(addFiles);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
_getFiles[FILES_DATA] = function () {
|
||||
var ret = [];
|
||||
files[FILES_DATA].forEach(function (el) {
|
||||
if (el.href && ret.indexOf(el.href) === -1) {
|
||||
ret.push(el.href);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
var getFiles = exp.getFiles = function (categories) {
|
||||
var ret = [];
|
||||
if (!categories || !categories.length) {
|
||||
categories = [ROOT, 'hrefArray', TRASH, FILES_DATA];
|
||||
}
|
||||
categories.forEach(function (c) {
|
||||
if (typeof _getFiles[c] === "function") {
|
||||
ret = ret.concat(_getFiles[c]());
|
||||
}
|
||||
});
|
||||
return Cryptpad.deduplicateString(ret);
|
||||
};
|
||||
|
||||
// SEARCH
|
||||
var _findFileInRoot = function (path, href) {
|
||||
if (!isPathIn(path, [ROOT, TRASH])) { return []; }
|
||||
var paths = [];
|
||||
var root = find(path);
|
||||
var addPaths = function (p) {
|
||||
if (paths.indexOf(p) === -1) {
|
||||
paths.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
if (isFile(root)) {
|
||||
if (compareFiles(href, root)) {
|
||||
if (paths.indexOf(path) === -1) {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
for (var e in root) {
|
||||
var nPath = path.slice();
|
||||
nPath.push(e);
|
||||
_findFileInRoot(nPath, href).forEach(addPaths);
|
||||
}
|
||||
|
||||
return paths;
|
||||
};
|
||||
var _findFileInHrefArray = function (rootName, href) {
|
||||
var unsorted = files[rootName].slice();
|
||||
var ret = [];
|
||||
var i = -1;
|
||||
while ((i = unsorted.indexOf(href, i+1)) !== -1){
|
||||
ret.push([rootName, i]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
var _findFileInTrash = function (path, href) {
|
||||
var root = find(path);
|
||||
var paths = [];
|
||||
var addPaths = function (p) {
|
||||
if (paths.indexOf(p) === -1) {
|
||||
paths.push(p);
|
||||
}
|
||||
};
|
||||
if (path.length === 1 && typeof(root) === 'object') {
|
||||
Object.keys(root).forEach(function (key) {
|
||||
var arr = root[key];
|
||||
if (!Array.isArray(arr)) { return; }
|
||||
var nPath = path.slice();
|
||||
nPath.push(key);
|
||||
_findFileInTrash(nPath, href).forEach(addPaths);
|
||||
});
|
||||
}
|
||||
if (path.length === 2) {
|
||||
if (!Array.isArray(root)) { return []; }
|
||||
root.forEach(function (el, i) {
|
||||
var nPath = path.slice();
|
||||
nPath.push(i);
|
||||
nPath.push('element');
|
||||
if (isFile(el.element)) {
|
||||
if (compareFiles(href, el.element)) {
|
||||
addPaths(nPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_findFileInTrash(nPath, href).forEach(addPaths);
|
||||
});
|
||||
}
|
||||
if (path.length >= 4) {
|
||||
_findFileInRoot(path, href).forEach(addPaths);
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
var findFile = exp.findFile = function (href) {
|
||||
var rootpaths = _findFileInRoot([ROOT], href);
|
||||
var unsortedpaths = _findFileInHrefArray(UNSORTED, href);
|
||||
var templatepaths = _findFileInHrefArray(TEMPLATE, href);
|
||||
var trashpaths = _findFileInTrash([TRASH], href);
|
||||
return rootpaths.concat(unsortedpaths, templatepaths, trashpaths);
|
||||
};
|
||||
var search = exp.search = function (value) {
|
||||
if (typeof(value) !== "string") { return []; }
|
||||
var res = [];
|
||||
// Search in ROOT
|
||||
var findIn = function (root) {
|
||||
Object.keys(root).forEach(function (k) {
|
||||
if (isFile(root[k])) {
|
||||
if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
res.push(root[k]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
findIn(root[k]);
|
||||
});
|
||||
};
|
||||
findIn(files[ROOT]);
|
||||
// Search in TRASH
|
||||
var trash = files[TRASH];
|
||||
Object.keys(trash).forEach(function (k) {
|
||||
if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
trash[k].forEach(function (el) {
|
||||
if (isFile(el.element)) {
|
||||
res.push(el.element);
|
||||
}
|
||||
});
|
||||
}
|
||||
trash[k].forEach(function (el) {
|
||||
if (isFolder(el.element)) {
|
||||
findIn(el.element);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Search title
|
||||
var allFilesList = files[FILES_DATA].slice();
|
||||
allFilesList.forEach(function (t) {
|
||||
if (t.title && t.title.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
res.push(t.href);
|
||||
}
|
||||
});
|
||||
|
||||
// Search Href
|
||||
var href = Cryptpad.getRelativeHref(value);
|
||||
if (href) {
|
||||
res.push(href);
|
||||
}
|
||||
|
||||
res = Cryptpad.deduplicateString(res);
|
||||
|
||||
var ret = [];
|
||||
res.forEach(function (l) {
|
||||
var paths = findFile(l);
|
||||
ret.push({
|
||||
paths: findFile(l),
|
||||
data: exp.getFileData(l)
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* OPERATIONS
|
||||
*/
|
||||
|
||||
var getAvailableName = function (parentEl, name) {
|
||||
if (typeof(parentEl[name]) === "undefined") { return name; }
|
||||
var newName = name;
|
||||
var i = 1;
|
||||
while (typeof(parentEl[newName]) !== "undefined") {
|
||||
newName = name + "_" + i;
|
||||
i++;
|
||||
}
|
||||
return newName;
|
||||
};
|
||||
|
||||
// FILES DATA
|
||||
var pushFileData = exp.pushData = function (data) {
|
||||
Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) {
|
||||
if (e) { console.log(e); return; }
|
||||
console.log(hash);
|
||||
});
|
||||
files[FILES_DATA].push(data);
|
||||
};
|
||||
var spliceFileData = exp.removeData = function (idx) {
|
||||
var data = files[FILES_DATA][idx];
|
||||
if (typeof data === "object") {
|
||||
Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) {
|
||||
if (e) { console.log(e); return; }
|
||||
console.log(hash);
|
||||
});
|
||||
}
|
||||
files[FILES_DATA].splice(idx, 1);
|
||||
};
|
||||
|
||||
// MOVE
|
||||
var pushToTrash = function (name, element, path) {
|
||||
var trash = files[TRASH];
|
||||
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
|
||||
var trashArray = trash[name];
|
||||
var trashElement = {
|
||||
element: element,
|
||||
path: path
|
||||
};
|
||||
trashArray.push(trashElement);
|
||||
};
|
||||
var copyElement = function (elementPath, newParentPath) {
|
||||
if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
|
||||
var element = find(elementPath);
|
||||
var newParent = find(newParentPath);
|
||||
|
||||
// Never move a folder in one of its children
|
||||
if (isSubpath(newParentPath, elementPath)) {
|
||||
log(Messages.fo_moveFolderToChildError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to Trash
|
||||
if (isPathIn(newParentPath, [TRASH])) {
|
||||
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
|
||||
debug("Can't move an element from the trash to the trash: ", elementPath);
|
||||
return;
|
||||
}
|
||||
var key = elementPath[elementPath.length - 1];
|
||||
var elName = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key;
|
||||
var parentPath = elementPath.slice();
|
||||
parentPath.pop();
|
||||
pushToTrash(elName, element, parentPath);
|
||||
return true;
|
||||
}
|
||||
// Move to hrefArray
|
||||
if (isPathIn(newParentPath, ['hrefArray'])) {
|
||||
if (isFolder(element)) {
|
||||
log(Messages.fo_moveUnsortedError);
|
||||
return;
|
||||
} else {
|
||||
if (elementPath[0] === newParentPath[0]) { return; }
|
||||
var fileRoot = newParentPath[0];
|
||||
if (files[fileRoot].indexOf(element) === -1) {
|
||||
files[fileRoot].push(element);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Move to root
|
||||
var name;
|
||||
if (isPathIn(elementPath, ['hrefArray'])) {
|
||||
name = getTitle(element);
|
||||
} else if (isInTrashRoot(elementPath)) {
|
||||
// Element from the trash root: elementPath = [TRASH, "{dirName}", 0, 'element']
|
||||
name = elementPath[1];
|
||||
} else {
|
||||
name = elementPath[elementPath.length-1];
|
||||
}
|
||||
var newName = !isPathIn(elementPath, [ROOT]) ? getAvailableName(newParent, name) : name;
|
||||
|
||||
if (typeof(newParent[newName]) !== "undefined") {
|
||||
log(Messages.fo_unavailableName);
|
||||
return;
|
||||
}
|
||||
newParent[newName] = element;
|
||||
return true;
|
||||
};
|
||||
var move = exp.move = function (paths, newPath, cb) {
|
||||
// Copy the elements to their new location
|
||||
var toRemove = [];
|
||||
paths.forEach(function (p) {
|
||||
var parentPath = p.slice();
|
||||
parentPath.pop();
|
||||
if (comparePath(parentPath, newPath)) { return; }
|
||||
copyElement(p, newPath);
|
||||
toRemove.push(p);
|
||||
});
|
||||
exp.delete(toRemove, cb);
|
||||
};
|
||||
var restore = exp.restore = function (path, cb) {
|
||||
if (!isInTrashRoot(path)) { return; }
|
||||
var parentPath = path.slice();
|
||||
parentPath.pop();
|
||||
var oldPath = find(parentPath).path;
|
||||
move([path], oldPath, cb);
|
||||
};
|
||||
|
||||
|
||||
// ADD
|
||||
var add = exp.add = function (href, path, name, cb) {
|
||||
if (!href) { return; }
|
||||
var newPath = path, parentEl;
|
||||
if (path && !Array.isArray(path)) {
|
||||
newPath = decodeURIComponent(path).split(',');
|
||||
}
|
||||
// Add to href array
|
||||
if (path && isPathIn(newPath, ['hrefArray'])) {
|
||||
parentEl = find(newPath);
|
||||
parentEl.push(href);
|
||||
return;
|
||||
}
|
||||
// Add to root
|
||||
if (path && isPathIn(newPath, [ROOT]) && name) {
|
||||
parentEl = find(newPath);
|
||||
if (parentEl) {
|
||||
var newName = getAvailableName(parentEl, name);
|
||||
parentEl[newName] = href;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No path: push to unsorted
|
||||
var filesList = getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
if (filesList.indexOf(href) === -1) { files[UNSORTED].push(href); }
|
||||
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
var addFile = exp.addFile = function (filePath, name, type, cb) {
|
||||
var parentEl = findElement(files, filePath);
|
||||
var fileName = getAvailableName(parentEl, name || NEW_FILE_NAME);
|
||||
var href = '/' + type + '/#' + Cryptpad.createRandomHash();
|
||||
parentEl[fileName] = href;
|
||||
|
||||
pushFileData({
|
||||
href: href,
|
||||
title: fileName,
|
||||
atime: +new Date(),
|
||||
ctime: +new Date()
|
||||
});
|
||||
|
||||
var newPath = filePath.slice();
|
||||
newPath.push(fileName);
|
||||
cb({
|
||||
newPath: newPath
|
||||
});
|
||||
};
|
||||
var addFolder = exp.addFolder = function (folderPath, name, cb) {
|
||||
var parentEl = find(folderPath);
|
||||
var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME);
|
||||
parentEl[folderName] = {};
|
||||
var newPath = folderPath.slice();
|
||||
newPath.push(folderName);
|
||||
cb({
|
||||
newPath: newPath
|
||||
});
|
||||
};
|
||||
|
||||
// FORGET (move with href not path)
|
||||
var forget = exp.forget = function (href) {
|
||||
var paths = findFile(href);
|
||||
move(paths, [TRASH]);
|
||||
};
|
||||
|
||||
// DELETE
|
||||
// Permanently delete multiple files at once using a list of paths
|
||||
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
|
||||
var removePadAttribute = function (f) {
|
||||
Object.keys(files).forEach(function (key) {
|
||||
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
|
||||
if (hash && key.indexOf(hash) === 0) {
|
||||
debug("Deleting pad attribute in the realtime object");
|
||||
files[key] = undefined;
|
||||
delete files[key];
|
||||
}
|
||||
});
|
||||
};
|
||||
var checkDeletedFiles = function () {
|
||||
// Nothing in FILES_DATA for workgroups
|
||||
if (workgroup) { return; }
|
||||
|
||||
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
var toRemove = [];
|
||||
files[FILES_DATA].forEach(function (arr) {
|
||||
var f = arr.href;
|
||||
if (filesList.indexOf(f) === -1) {
|
||||
toRemove.push(arr);
|
||||
}
|
||||
});
|
||||
toRemove.forEach(function (f) {
|
||||
var idx = files[FILES_DATA].indexOf(f);
|
||||
if (idx !== -1) {
|
||||
debug("Removing", f, "from filesData");
|
||||
spliceFileData(idx);
|
||||
removePadAttribute(f.href);
|
||||
}
|
||||
});
|
||||
};
|
||||
var deleteHrefs = function (hrefs) {
|
||||
hrefs.forEach(function (obj) {
|
||||
var idx = files[obj.root].indexOf(obj.href);
|
||||
files[obj.root].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultipleTrashRoot = function (roots) {
|
||||
roots.forEach(function (obj) {
|
||||
var idx = files[TRASH][obj.name].indexOf(obj.el);
|
||||
files[TRASH][obj.name].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultiplePermanently = function (paths, nocheck) {
|
||||
var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); });
|
||||
var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); });
|
||||
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
|
||||
|
||||
var hrefs = [];
|
||||
hrefPaths.forEach(function (path) {
|
||||
var href = find(path);
|
||||
hrefs.push({
|
||||
root: path[0],
|
||||
href: href
|
||||
});
|
||||
});
|
||||
deleteHrefs(hrefs);
|
||||
|
||||
rootPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
parentEl[key] = undefined;
|
||||
delete parentEl[key];
|
||||
});
|
||||
|
||||
var trashRoot = [];
|
||||
trashPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
|
||||
// of another element in the loop
|
||||
if (path.length === 4) {
|
||||
trashRoot.push({
|
||||
name: path[1],
|
||||
el: parentEl
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Trash but not root: it's just a tree so remove the key
|
||||
parentEl[key] = undefined;
|
||||
delete parentEl[key];
|
||||
});
|
||||
deleteMultipleTrashRoot(trashRoot);
|
||||
|
||||
// In some cases, we want to remove pads from a location without removing them from
|
||||
// FILES_DATA (replaceHref)
|
||||
if (!nocheck) { checkDeletedFiles(); }
|
||||
};
|
||||
var deletePath = exp.delete = function (paths, cb, nocheck) {
|
||||
deleteMultiplePermanently(paths, nocheck);
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
var emptyTrash = exp.emptyTrash = function (cb) {
|
||||
files[TRASH] = {};
|
||||
checkDeletedFiles();
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
// RENAME
|
||||
var rename = exp.rename = function (path, newName, cb) {
|
||||
if (path.length <= 1) {
|
||||
logError('Renaming `root` is forbidden');
|
||||
return;
|
||||
}
|
||||
if (!newName || newName.trim() === "") { return; }
|
||||
// Copy the element path and remove the last value to have the parent path and the old name
|
||||
var element = find(path);
|
||||
var parentPath = path.slice();
|
||||
var oldName = parentPath.pop();
|
||||
if (oldName === newName) {
|
||||
return;
|
||||
}
|
||||
var parentEl = find(parentPath);
|
||||
if (typeof(parentEl[newName]) !== "undefined") {
|
||||
log(Messages.fo_existingNameError);
|
||||
return;
|
||||
}
|
||||
parentEl[newName] = element;
|
||||
parentEl[oldName] = undefined;
|
||||
delete parentEl[oldName];
|
||||
cb();
|
||||
};
|
||||
|
||||
// REPLACE
|
||||
var replaceFile = function (path, o, n) {
|
||||
var root = find(path);
|
||||
|
||||
if (isFile(root)) { return; }
|
||||
for (var e in root) {
|
||||
if (isFile(root[e])) {
|
||||
if (compareFiles(o, root[e])) {
|
||||
root[e] = n;
|
||||
}
|
||||
} else {
|
||||
var nPath = path.slice();
|
||||
nPath.push(e);
|
||||
replaceFile(nPath, o, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Replace a href by a stronger one everywhere in the drive (except FILES_DATA)
|
||||
var replaceHref = exp.replace = function (o, n) {
|
||||
if (!isFile(o) || !isFile(n)) { return; }
|
||||
var paths = findFile(o);
|
||||
|
||||
// Remove all the occurences in the trash
|
||||
// Replace all the occurences not in the trash
|
||||
// If all the occurences are in the trash or no occurence, add the pad to unsorted
|
||||
var allInTrash = true;
|
||||
paths.forEach(function (p) {
|
||||
if (p[0] === TRASH) {
|
||||
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
|
||||
return;
|
||||
} else {
|
||||
allInTrash = false;
|
||||
var parentPath = p.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
parentEl[key] = n;
|
||||
}
|
||||
});
|
||||
if (allInTrash) {
|
||||
add(n);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* INTEGRITY CHECK
|
||||
*/
|
||||
|
||||
var fixFiles = exp.fixFiles = function () {
|
||||
// Explore the tree and check that everything is correct:
|
||||
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
|
||||
// * ROOT: Folders are objects, files are href
|
||||
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
|
||||
// * FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
|
||||
// - Dates (adate, cdate) can be parsed/formatted
|
||||
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
|
||||
// * UNSORTED: Contains only files (href), and does not contains files that are in ROOT
|
||||
debug("Cleaning file system...");
|
||||
|
||||
var before = JSON.stringify(files);
|
||||
|
||||
var fixRoot = function (elem) {
|
||||
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
|
||||
var element = elem || files[ROOT];
|
||||
for (var el in element) {
|
||||
if (!isFile(element[el]) && !isFolder(element[el])) {
|
||||
debug("An element in ROOT was not a folder nor a file. ", element[el]);
|
||||
element[el] = undefined;
|
||||
delete element[el];
|
||||
} else if (isFolder(element[el])) {
|
||||
fixRoot(element[el]);
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixTrashRoot = function () {
|
||||
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
|
||||
var tr = files[TRASH];
|
||||
var toClean;
|
||||
var addToClean = function (obj, idx) {
|
||||
if (typeof(obj) !== "object") { toClean.push(idx); return; }
|
||||
if (!isFile(obj.element) && !isFolder(obj.element)) { toClean.push(idx); return; }
|
||||
if (!$.isArray(obj.path)) { toClean.push(idx); return; }
|
||||
};
|
||||
for (var el in tr) {
|
||||
if (!$.isArray(tr[el])) {
|
||||
debug("An element in TRASH root is not an array. ", tr[el]);
|
||||
tr[el] = undefined;
|
||||
delete tr[el];
|
||||
} else {
|
||||
toClean = [];
|
||||
tr[el].forEach(addToClean);
|
||||
for (var i = toClean.length-1; i>=0; i--) {
|
||||
tr[el].splice(toClean[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixUnsorted = function () {
|
||||
if (!Array.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; }
|
||||
files[UNSORTED] = Cryptpad.deduplicateString(files[UNSORTED].slice());
|
||||
var us = files[UNSORTED];
|
||||
var rootFiles = getFiles([ROOT, TEMPLATE]).slice();
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(idx);
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (idx) {
|
||||
us.splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var fixTemplate = function () {
|
||||
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
|
||||
files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice());
|
||||
var us = files[TEMPLATE];
|
||||
var rootFiles = getFiles([ROOT, UNSORTED]).slice();
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(idx);
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (idx) {
|
||||
us.splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var fixFilesData = function () {
|
||||
if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; }
|
||||
var fd = files[FILES_DATA];
|
||||
var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
var toClean = [];
|
||||
fd.forEach(function (el, idx) {
|
||||
if (!el || typeof(el) !== "object") {
|
||||
debug("An element in filesData was not an object.", el);
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
if (rootFiles.indexOf(el.href) === -1) {
|
||||
debug("An element in filesData was not in ROOT, UNSORTED or TRASH.", el);
|
||||
files[UNSORTED].push(el.href);
|
||||
return;
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (el) {
|
||||
var idx = fd.indexOf(el);
|
||||
if (idx !== -1) {
|
||||
spliceFileData(idx);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fixRoot();
|
||||
fixTrashRoot();
|
||||
if (!workgroup) {
|
||||
fixUnsorted();
|
||||
fixTemplate();
|
||||
fixFilesData();
|
||||
}
|
||||
|
||||
if (JSON.stringify(files) !== before) {
|
||||
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");
|
||||
return;
|
||||
}
|
||||
debug("File system was clean");
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
@ -0,0 +1,357 @@
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
.cryptpad-toolbar h2 {
|
||||
font: normal normal normal 12px Arial, Helvetica, Tahoma, Verdana, Sans-Serif;
|
||||
color: #000;
|
||||
line-height: auto;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
display: inline-block;
|
||||
}
|
||||
.realtime {
|
||||
display: block;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.realtime input[type="text"] {
|
||||
height: 1em;
|
||||
margin: 0px;
|
||||
}
|
||||
.text-cell input[type="text"] {
|
||||
width: 400px;
|
||||
}
|
||||
input[type="text"][disabled],
|
||||
textarea[disabled] {
|
||||
background-color: transparent;
|
||||
font: white;
|
||||
border: 0px;
|
||||
}
|
||||
table#table {
|
||||
margin: 0px;
|
||||
}
|
||||
#tableContainer {
|
||||
position: relative;
|
||||
padding: 29px;
|
||||
padding-right: 79px;
|
||||
}
|
||||
#tableContainer button {
|
||||
height: 2rem;
|
||||
display: none;
|
||||
}
|
||||
#publish {
|
||||
display: none;
|
||||
}
|
||||
#publish,
|
||||
#admin {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#create-user {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
/*left: 0px;*/
|
||||
top: 55px;
|
||||
width: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#create-option {
|
||||
width: 50px;
|
||||
}
|
||||
#tableScroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin-left: calc(30% - 50px + 29px);
|
||||
max-width: 70%;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
#description {
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
min-height: 5em;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
#description[disabled] {
|
||||
resize: none;
|
||||
color: #000;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
#commit {
|
||||
width: 100%;
|
||||
}
|
||||
#howItWorks {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
div.upper {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin: 20px;
|
||||
}
|
||||
tbody {
|
||||
border: 1px solid #555;
|
||||
}
|
||||
tbody tr {
|
||||
text-align: center;
|
||||
}
|
||||
tbody tr:first-of-type th {
|
||||
font-size: 20px;
|
||||
border-top: 0px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
tbody tr:first-of-type th.table-refresh {
|
||||
color: #46E981;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
tbody tr th:first-of-type {
|
||||
border-left: 0px;
|
||||
}
|
||||
tbody tr th {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
tbody tr th,
|
||||
tbody tr td {
|
||||
color: #555;
|
||||
}
|
||||
tbody tr th.remove,
|
||||
tbody tr td.remove {
|
||||
cursor: pointer;
|
||||
}
|
||||
tbody tr th:last-child {
|
||||
border-right: 0px;
|
||||
}
|
||||
tbody td {
|
||||
border-right: 1px solid #555;
|
||||
padding: 12px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
tbody td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
form.realtime,
|
||||
div.realtime {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
form.realtime > textarea,
|
||||
div.realtime > textarea {
|
||||
width: 50%;
|
||||
height: 15vh;
|
||||
}
|
||||
form.realtime table,
|
||||
div.realtime table {
|
||||
border-collapse: collapse;
|
||||
width: calc(100% - 1px);
|
||||
}
|
||||
form.realtime table tr td:first-child,
|
||||
div.realtime table tr td:first-child {
|
||||
position: absolute;
|
||||
left: 29px;
|
||||
top: auto;
|
||||
width: calc(30% - 50px);
|
||||
}
|
||||
form.realtime table tr td,
|
||||
div.realtime table tr td {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
form.realtime table tr td div.text-cell,
|
||||
div.realtime table tr td div.text-cell {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
form.realtime table tr td div.text-cell input,
|
||||
div.realtime table tr td div.text-cell input {
|
||||
width: 80%;
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
}
|
||||
form.realtime table tr td div.text-cell input[disabled],
|
||||
div.realtime table tr td div.text-cell input[disabled] {
|
||||
background-color: transparent;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell,
|
||||
div.realtime table tr td.checkbox-cell {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 100%;
|
||||
min-width: 150px;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain label,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain label {
|
||||
background-color: transparent;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable),
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) {
|
||||
display: none;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover {
|
||||
font-weight: bold;
|
||||
background-color: #FA5858;
|
||||
color: #000;
|
||||
display: block;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after {
|
||||
height: 100%;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover:after {
|
||||
content: "✖";
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes {
|
||||
background-color: #46E981;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.yes:after {
|
||||
content: "✔";
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.uncommitted {
|
||||
background: #ddd;
|
||||
}
|
||||
form.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine,
|
||||
div.realtime table tr td.checkbox-cell div.checkbox-contain input[type="checkbox"]:not(.editable) ~ .cover.mine {
|
||||
display: none;
|
||||
}
|
||||
form.realtime table input[type="text"],
|
||||
div.realtime table input[type="text"] {
|
||||
height: auto;
|
||||
border: 1px solid #fff;
|
||||
width: 80%;
|
||||
}
|
||||
form.realtime table thead td,
|
||||
div.realtime table thead td {
|
||||
padding: 0px 5px;
|
||||
background: #aaa;
|
||||
border-radius: 20px 20px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
form.realtime table thead td input[type="text"],
|
||||
div.realtime table thead td input[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
form.realtime table thead td input[type="text"][disabled],
|
||||
div.realtime table thead td input[type="text"][disabled] {
|
||||
color: #000;
|
||||
padding: 1px 5px;
|
||||
border: none;
|
||||
}
|
||||
form.realtime table tbody .text-cell,
|
||||
div.realtime table tbody .text-cell {
|
||||
background: #aaa;
|
||||
}
|
||||
form.realtime table tbody .text-cell input[type="text"],
|
||||
div.realtime table tbody .text-cell input[type="text"] {
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
form.realtime table tbody .text-cell .edit,
|
||||
div.realtime table tbody .text-cell .edit {
|
||||
float: right;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
form.realtime table tbody .text-cell .remove,
|
||||
div.realtime table tbody .text-cell .remove {
|
||||
float: left;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
form.realtime table tbody td label,
|
||||
div.realtime table tbody td label {
|
||||
border: 0.5px solid #555;
|
||||
}
|
||||
form.realtime table .edit,
|
||||
div.realtime table .edit {
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
form.realtime table .remove,
|
||||
div.realtime table .remove {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
form.realtime table thead tr th input[type="text"][disabled],
|
||||
div.realtime table thead tr th input[type="text"][disabled] {
|
||||
background-color: transparent;
|
||||
color: #555;
|
||||
font-weight: bold;
|
||||
}
|
||||
form.realtime table thead tr th .remove,
|
||||
div.realtime table thead tr th .remove {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
form.realtime table tfoot tr,
|
||||
div.realtime table tfoot tr {
|
||||
border: none;
|
||||
}
|
||||
form.realtime table tfoot tr td,
|
||||
div.realtime table tfoot tr td {
|
||||
border: none;
|
||||
text-align: center;
|
||||
}
|
||||
form.realtime table tfoot tr td .save,
|
||||
div.realtime table tfoot tr td .save {
|
||||
padding: 15px;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
form.realtime #adduser,
|
||||
div.realtime #adduser,
|
||||
form.realtime #addoption,
|
||||
div.realtime #addoption {
|
||||
color: #46E981;
|
||||
border: 1px solid #46E981;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
form.realtime #adduser,
|
||||
div.realtime #adduser {
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
form.realtime #addoption,
|
||||
div.realtime #addoption {
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
@ -0,0 +1,387 @@
|
||||
@import "../../customize.dist/src/less/variables.less";
|
||||
@import "../../customize.dist/src/less/mixins.less";
|
||||
|
||||
@poll-th-bg: #aaa;
|
||||
@poll-td-bg: #aaa;
|
||||
@poll-border-color: #555;
|
||||
@poll-cover-color: #000;
|
||||
@poll-fg: #000;
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.cryptpad-toolbar h2 {
|
||||
font: normal normal normal 12px Arial, Helvetica, Tahoma, Verdana, Sans-Serif;
|
||||
color: #000;
|
||||
line-height: auto;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
display: inline-block;
|
||||
}
|
||||
.realtime {
|
||||
display: block;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.realtime input[type="text"] {
|
||||
height: 1em;
|
||||
margin: 0px;
|
||||
}
|
||||
.text-cell input[type="text"] {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
input[type="text"][disabled], textarea[disabled] {
|
||||
background-color: transparent;
|
||||
font: white;
|
||||
border: 0px;
|
||||
}
|
||||
table#table {
|
||||
margin: 0px;
|
||||
}
|
||||
#tableContainer {
|
||||
position: relative;
|
||||
padding: 29px;
|
||||
padding-right: 79px;
|
||||
}
|
||||
#tableContainer button {
|
||||
height: 2rem;
|
||||
display: none;
|
||||
}
|
||||
#publish {
|
||||
display: none;
|
||||
}
|
||||
#publish, #admin {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#create-user {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
/*left: 0px;*/
|
||||
top: 55px;
|
||||
width: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#create-option {
|
||||
width: 50px;
|
||||
}
|
||||
#tableScroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin-left: calc(~"30% - 50px + 29px");
|
||||
max-width: 70%;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
#description {
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
min-height: 5em;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
#description[disabled] {
|
||||
resize: none;
|
||||
color: #000;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
#commit {
|
||||
width: 100%;
|
||||
}
|
||||
#howItWorks {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
div.upper {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
// from cryptpad.less
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin: 20px;
|
||||
}
|
||||
tbody {
|
||||
border: 1px solid @poll-border-color;
|
||||
tr {
|
||||
text-align: center;
|
||||
&:first-of-type th{
|
||||
font-size: 20px;
|
||||
border-top: 0px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
text-decoration: underline;
|
||||
&.table-refresh {
|
||||
color: @cp-green;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
&:nth-child(odd) {
|
||||
background-color: @light-base;
|
||||
}
|
||||
th:first-of-type {
|
||||
border-left: 0px;
|
||||
}
|
||||
th {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid @poll-border-color;
|
||||
}
|
||||
th, td {
|
||||
color: @fore;
|
||||
|
||||
&.remove {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
th:last-child {
|
||||
border-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
border-right: 1px solid @poll-border-color;
|
||||
padding: 12px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.realtime, div.realtime {
|
||||
> input {
|
||||
&[type="text"] {
|
||||
|
||||
}
|
||||
}
|
||||
> textarea {
|
||||
width: 50%;
|
||||
height: 15vh;
|
||||
}
|
||||
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: ~"calc(100% - 1px)";
|
||||
tr {
|
||||
td:first-child {
|
||||
position:absolute;
|
||||
left: 29px;
|
||||
top: auto;
|
||||
width: ~"calc(30% - 50px)";
|
||||
}
|
||||
td {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
div.text-cell {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
|
||||
input {
|
||||
width: 80%;
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
&[disabled] {
|
||||
background-color: transparent;
|
||||
color: @poll-fg;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.checkbox-cell {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 100%;
|
||||
min-width: 150px;
|
||||
|
||||
div.checkbox-contain {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
background-color: transparent;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
&[type="checkbox"] {
|
||||
&:not(.editable) {
|
||||
display: none;
|
||||
|
||||
~ .cover {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
|
||||
background-color: @cp-red;
|
||||
color: @poll-cover-color;
|
||||
|
||||
&:after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:after { content: "✖"; }
|
||||
|
||||
display: block;
|
||||
&.yes {
|
||||
background-color: @cp-green;
|
||||
&:after { content: "✔"; }
|
||||
}
|
||||
|
||||
&.uncommitted {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
|
||||
&.mine {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
&[type="text"] {
|
||||
height: auto;
|
||||
border: 1px solid @base;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
thead {
|
||||
td {
|
||||
padding: 0px 5px;
|
||||
background: @poll-th-bg;
|
||||
border-radius: 20px 20px 0 0;
|
||||
text-align: center;
|
||||
input {
|
||||
&[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
&[disabled] {
|
||||
color: @poll-fg;
|
||||
padding: 1px 5px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
.text-cell {
|
||||
background: @poll-td-bg;
|
||||
//border-radius: 20px 0 0 20px;
|
||||
input[type="text"] {
|
||||
width: ~"calc(100% - 50px)";
|
||||
}
|
||||
.edit {
|
||||
float:right;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
.remove {
|
||||
float: left;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
td {
|
||||
label {
|
||||
border: .5px solid @poll-border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.edit {
|
||||
color: @poll-cover-color;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
input[type="text"][disabled] {
|
||||
background-color: transparent;
|
||||
color: @fore;
|
||||
font-weight: bold;
|
||||
}
|
||||
.remove {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
td {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
tfoot {
|
||||
tr {
|
||||
border: none;
|
||||
td {
|
||||
border: none;
|
||||
text-align: center;
|
||||
.save {
|
||||
padding: 15px;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#adduser,
|
||||
#addoption {
|
||||
color: @cp-green;
|
||||
border: 1px solid @cp-green;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#adduser { .top-left; }
|
||||
#addoption { .bottom-left; }
|
||||
}
|
Loading…
Reference in New Issue