define([ '/customize/messages.js', '/customize/store.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/bower_components/spin.js/spin.min.js', '/customize/user.js', '/bower_components/jquery/dist/jquery.min.js', ], function (Messages, Store, Crypto, Alertify, Spinner, User) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. Additionally, there is some basic functionality for import/export. */ var $ = window.jQuery; var common = {}; var store; var userProxy; var userStore; var getStore = common.getStore = function (legacy) { if (!legacy && userStore) { return userStore; } if (store) { return store; } throw new Error("Store is not ready!"); }; /* * cb(err, proxy); */ var authorize = common.authorize = function (cb) { console.log("Authorizing"); var secret = User.session(); if (!secret) { // user is not authenticated cb('user is not authenticated', void 0); } // for now we assume that things always work User.connect(secret, function (err, proxy) { cb(void 0, proxy); }); }; Store.ready(function (err, Store) { if (err) { console.error(err); return; } store = Store; }); var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; var fixHTML = common.fixHTML = function (html) { return html.replace(/ len) { return text.slice(0, len) + '…'; } return text; }; common.redirect = function (hash) { var hostname = window.location.hostname; // don't do anything funny unless you're on a cryptpad subdomain if (!/cryptpad.fr$/i.test(hostname)) { return; } if (hash.length >= 56) { // you're on the right domain return; } // old.cryptpad only supports these apps, so only redirect on a match if (['/pad/', '/p/', '/code/'].indexOf(window.location.pathname) === -1) { return; } // if you make it this far then there's something wrong with your hash // you should probably be on old.cryptpad.fr... window.location.hostname = 'old.cryptpad.fr'; }; var getSecrets = common.getSecrets = function () { var secret = {}; if (!/#/.test(window.location.href)) { secret.key = Crypto.genKey(); } else { var hash = window.location.hash.slice(1); common.redirect(hash); secret.channel = hash.slice(0, 32); secret.key = hash.slice(32); } return secret; }; var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; /* * localStorage formatting */ /* the first time this gets called, your local storage will migrate to a new format. No more indices for values, everything is named now. * href * atime (access time) * title * ??? // what else can we put in here? */ var migrateRecentPads = common.migrateRecentPads = function (pads) { return pads.map(function (pad) { if (isArray(pad)) { var href = pad[0]; var hash; href.replace(/\#(.*)$/, function (a, h) { hash = h; }); return { href: pad[0], atime: pad[1], title: pad[2] || hash && hash.slice(0,8), ctime: pad[1], }; } else if (typeof(pad) === 'object') { if (!pad.ctime) { pad.ctime = pad.atime; } if (!pad.title) { pad.href.replace(/#(.*)$/, function (x, hash) { pad.title = hash.slice(0,8); }); } pad.href = pad.href.replace(/^https:\/\/beta\.cryptpad\.fr/, 'https://cryptpad.fr'); return pad; } else { console.error("[Cryptpad.migrateRecentPads] pad had unexpected value"); console.log(pad); return {}; } }); }; var getHash = common.getHash = function () { return window.location.hash.slice(1); }; var parsePadUrl = common.parsePadUrl = function (href) { var patt = /^https*:\/\/([^\/]*)\/(.*?)\/#(.*)$/i; var ret = {}; href.replace(patt, function (a, domain, type, hash) { ret.domain = domain; ret.type = type; ret.hash = hash; return ''; }); return ret; }; var makePad = function (href, title) { var now = ''+new Date(); return { href: href, atime: now, ctime: now, title: title || window.location.hash.slice(1, 9), }; }; /* Sort pads according to how recently they were accessed */ var mostRecent = common.mostRecent = function (a, b) { return new Date(b.atime).getTime() - new Date(a.atime).getTime(); }; // STORAGE var setPadAttribute = common.setPadAttribute = function (attr, value, cb, legacy) { getStore(legacy).set([getHash(), attr].join('.'), value, function (err, data) { cb(err, data); }); }; // STORAGE var getPadAttribute = common.getPadAttribute = function (attr, cb, legacy) { getStore(legacy).get([getHash(), attr].join('.'), function (err, data) { cb(err, data); }); }; // STORAGE /* fetch and migrate your pad history from localStorage */ var getRecentPads = common.getRecentPads = function (cb, legacy) { getStore(legacy).get(storageKey, function (err, recentPads) { if (isArray(recentPads)) { cb(void 0, migrateRecentPads(recentPads)); return; } cb(void 0, []); }); }; // STORAGE /* commit a list of pads to localStorage */ var setRecentPads = common.setRecentPads = function (pads, cb, legacy) { getStore(legacy).set(storageKey, pads, function (err, data) { cb(err, data); }); }; // STORAGE var forgetPad = common.forgetPad = function (href, cb, legacy) { var parsed = parsePadUrl(href); getRecentPads(function (err, recentPads) { setRecentPads(recentPads.filter(function (pad) { var p = parsePadUrl(pad.href); // find duplicates if (parsed.hash === p.hash && parsed.type === p.type) { console.log("Found a duplicate"); return; } return true; }), function (err, data) { if (err) { cb(err); return; } getStore(legacy).keys(function (err, keys) { if (err) { cb(err); return; } var toRemove = keys.filter(function (k) { return k.indexOf(parsed.hash) === 0; }); if (!toRemove.length) { cb(); return; } getStore(legacy).removeBatch(toRemove, function (err, data) { cb(err, data); }); }); }, legacy); }, legacy); }; // STORAGE var rememberPad = common.rememberPad = window.rememberPad = function (title, cb) { // bail out early if (!/#/.test(window.location.hash)) { return; } getRecentPads(function (err, pads) { if (err) { cb(err); return; } var now = ''+new Date(); var href = window.location.href; var parsed = parsePadUrl(window.location.href); var isUpdate = false; var out = pads.map(function (pad) { var p = parsePadUrl(pad.href); if (p.hash === parsed.hash && p.type === parsed.type) { isUpdate = true; // bump the atime pad.atime = now; pad.title = title; pad.href = href; } return pad; }); if (!isUpdate) { // href, ctime, atime, title out.push(makePad(href, title)); } setRecentPads(out, function (err, data) { cb(err, data); }); }); }; // STORAGE var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); getRecentPads(function (err, recent) { if (err) { cb(err); return; } var contains; var renamed = recent.map(function (pad) { var p = parsePadUrl(pad.href); if (p.hash === parsed.hash && p.type === parsed.type) { contains = true; // update the atime pad.atime = new Date().toISOString(); // set the name pad.title = name; pad.href = href; } return pad; }); if (!contains) { renamed.push(makePad(href, name)); } setRecentPads(renamed, function (err, data) { cb(err, data); }); }); }; // STORAGE var getPadTitle = common.getPadTitle = function (cb) { var href = window.location.href; var parsed = parsePadUrl(window.location.href); var hashSlice = window.location.hash.slice(1,9); var title = ''; getRecentPads(function (err, pads) { if (err) { cb(err); return; } pads.some(function (pad) { var p = parsePadUrl(pad.href); if (p.hash === parsed.hash && p.type === parsed.type) { title = pad.title || hashSlice; return true; } }); cb(void 0, title); }); }; // STORAGE var causesNamingConflict = common.causesNamingConflict = function (title, cb) { var href = window.location.href; var parsed = parsePadUrl(href); getRecentPads(function (err, pads) { if (err) { cb(err); return; } var conflicts = pads.some(function (pad) { // another pad is already using that title if (pad.title === title) { var p = parsePadUrl(href); if (p.type === parsed.type && p.hash === parsed.hash) { // the duplicate pad has the same type and hash // allow renames } else { // it's an entirely different pad... it conflicts return true; } } }); cb(void 0, conflicts); }); }; // local name? common.ready = function (f) { var state = 0; var env = {}; var cb = function () { state--; if (!state) { f(void 0, env); } }; state = 2; Store.ready(function (err, store) { common.store = env.store = store; cb(); }); // HERE authorize(function (err, proxy) { if (err) { // not logged in } if (!proxy) { cb(); return; } userProxy = env.proxy = proxy; userStore = env.userStore = User.prepareStore(proxy); cb(); }); }; /* * Saving files */ var fixFileName = common.fixFileName = function (filename) { return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_') .replace(/_+/g, '_'); }; var importContent = common.importContent = function (type, f) { return function () { var $files = $('').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); }); }; }; /* * Alertifyjs */ var styleAlerts = common.styleAlerts = function (href) { var $link = $('link[href="/customize/alertify.css"]'); if ($link.length) { return; } href = href || '/customize/alertify.css'; $('head').append($('', { rel: 'stylesheet', id: 'alertifyCSS', href: href, })); }; var findCancelButton = common.findCancelButton = function () { return $('button.cancel'); }; var findOKButton = common.findOKButton = function () { return $('button.ok'); }; var 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 = function (handler) { $(window).off('keyup', handler); }; common.alert = function (msg, cb) { cb = cb || function () {}; var keyHandler = listenForKeys(function (e) { // yes findOKButton().click(); }); Alertify.alert(msg, function (ev) { cb(ev); stopListening(keyHandler); }); }; common.prompt = function (msg, def, cb, opt) { opt = opt || {}; cb = cb || function () {}; 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); }); }; common.confirm = function (msg, cb, opt) { opt = opt || {}; cb = cb || function () {}; 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); }); }; common.log = function (msg) { Alertify.success(msg); }; common.warn = function (msg) { Alertify.error(msg); }; /* * spinner */ common.spinner = function (parent) { var $target = $('