From 6d775f61c683370f2c5fb7e75ff715ac32d86b55 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 29 Aug 2016 18:10:15 +0200 Subject: [PATCH 1/9] add missing functions --- www/common/cryptpad-common.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 13780995c..d6e962d9c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -35,6 +35,17 @@ define([ 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; From 43fcb294908780ace1114384c86a9f3a10cd63a3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:00:28 +0200 Subject: [PATCH 2/9] listen for changes to localStorage and execute callbacks --- customize.dist/store.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/customize.dist/store.js b/customize.dist/store.js index 287b95644..5c55f7eb2 100644 --- a/customize.dist/store.js +++ b/customize.dist/store.js @@ -71,5 +71,23 @@ define(function () { } }; + var changeHandlers = Store.changeHandlers = []; + + Store.change = function (f) { + if (typeof(f) !== 'function') { + throw new Error('[Store.change] callback must be a function'); + } + changeHandlers.push(f); + + if (changeHandlers.length === 1) { + // start listening for changes + window.addEventListener('storage', function (e) { + changeHandlers.forEach(function (f) { + f(e); + }); + }); + } + }; + return Store; }); From c64169229586b538a4bd7cc9839dac8a98dc5e59 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:02:51 +0200 Subject: [PATCH 3/9] standardize datatype for callback for storage change callack serialization --- customize.dist/store.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/customize.dist/store.js b/customize.dist/store.js index 5c55f7eb2..5b82921ed 100644 --- a/customize.dist/store.js +++ b/customize.dist/store.js @@ -83,7 +83,11 @@ define(function () { // start listening for changes window.addEventListener('storage', function (e) { changeHandlers.forEach(function (f) { - f(e); + f({ + key: e.key, + oldValue: e.oldValue, + newValue: e.newValue, + }); }); }); } From 412cb680ffc1f7dd2986f5e1a17cdc989049ebdf Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:08:36 +0200 Subject: [PATCH 4/9] prototype users --- customize.dist/user.js | 174 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 customize.dist/user.js diff --git a/customize.dist/user.js b/customize.dist/user.js new file mode 100644 index 000000000..c75a83653 --- /dev/null +++ b/customize.dist/user.js @@ -0,0 +1,174 @@ +define([ + '/api/config?cb=' + Math.random().toString().slice(2), + '/customize/messages.js', + '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/chainpad-crypto/crypto.js', + '/bower_components/scrypt-async/scrypt-async.min.js', + '/bower_components/tweetnacl/nacl-fast.min.js', +], function (Config, Messages, Listmap, Crypto) { + var Scrypt = window.scrypt; + var Nacl = window.nacl; + + var localKey = 'cryptpad_user_session'; + + var User = {}; + + var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; + + var session = User.session = function (secret) { + // TODO use store.js, not localStorage? + if (secret) { + localStorage.setItem(localKey, JSON.stringify(secret)); + return; + } + if (secret === null) { + localStorage.removeItem(localKey); + return; + } + var temp = localStorage.getItem(localKey); + try { + return JSON.parse(temp); + } catch (err) { + return null; + } + }; + + /* 64 uint8s symmetric keys + 32 b64 channel + 16 b64 key + 16 b64 junk + 32 uint8s ed signing key + 32 uint8s curve public key */ + var parse128 = function (A) { + if (A.length !== 128) { + throw new Error("Expected 128 uint8s!"); + } + var symmetric = Nacl.util.encodeBase64(A.slice(0, 36)); + return { + ed: A.slice(96), + curve: A.slice(64, 96), + channel: symmetric.slice(0, 32), + key: symmetric.slice(32), + extra: A.slice(36, 64), + }; + }; + + var initialize = User.initialize = function (proxy, secret, cb) { + proxy.on('ready', function (info) { + var now = ''+new Date(); + // old atime + var otime = proxy.atime; + + var atime = proxy.atime = now; + + // creation time + proxy.ctime = proxy.ctime || now; + + proxy.username = proxy.username || secret.username; + proxy.schema = proxy.schema || 'login_data-v0'; + + proxy.documents = proxy.documents || []; + cb(void 0, proxy); + }); + }; + + /* + cb(proxy); + */ + var connect = User.connect = function (secret, cb) { + if (!secret) { + // FIXME + return; + } + var config = { + websocketURL: Config.websocketURL, + channel: secret.channel, + data: {}, + crypto: Crypto.createEncryptor(secret.key), + logLevel: 0, + }; + var rt = Listmap.create(config); + initialize(rt.proxy, secret, cb); + }; + + /* Asynchronously derive 128 random uint8s given a uname and password + + cb(proxy, secret) + */ + var login = User.login = function (uname, pw, cb) { + Scrypt(pw, + uname, + 15, // memory cost parameter + 8, // block size parameter + 128, // derived key length + 200, // interruptStep + function (bytes) { + var secret = parse128(bytes); + secret.username = uname; + session(secret); + connect(secret, cb); + }); + }; + + var prepareStore = User.prepareStore = function (proxy) { + var store = {}; + + var ps = proxy.store = proxy.store || {}; + + var set = store.set = function (key, val, cb) { + ps[key] = val; + cb(); + }; + + var batchset = store.setBatch = function (map, cb) { + if (isArray(map) || typeof(map) !== 'object') { + cb('[setBatch.TypeError] expected key-value pairs to set'); + return; + } + Object.keys(map).forEach(function (k) { + ps[k] = map[k]; + }); + cb(); + }; + + var get = store.get = function (key, cb) { + cb(void 0, ps[key]); + }; + + var batchget = store.getBatch = function (keys, cb) { + if (!isArray(keys)) { + cb('[getBatch.TypeError] expected array of keys to return'); + return; + } + var map = {}; + keys.forEach(function (k) { + map[k] = ps[k]; + }); + cb(void 0, map); + }; + + var remove = store.remove = function (key, cb) { + ps[key] = undefined; + cb(); + }; + + var batchremove = store.removeBatch = function (keys, cb) { + if (!isArray(keys)) { + cb('[batchremove.TypeError] expected array of keys to remove'); + return; + } + keys.forEach(function (k) { + ps[k] = undefined; + }); + cb(); + }; + + var keys = store.keys = function (cb) { + cb(void 0, Object.keys(ps)); + }; + + return store; + }; + + return User; +}); From 30ea1b59b7e35b9dc21609653e5589e170e38c5b Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:09:53 +0200 Subject: [PATCH 5/9] integrate users into common utilities --- www/common/cryptpad-common.js | 154 +++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 49 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d6e962d9c..a4b949629 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -4,8 +4,11 @@ define([ '/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) { +], 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. @@ -14,13 +17,32 @@ define([ */ 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!"); + }; - var getStore = function () { - if (!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); } - return store; + + // for now we assume that things always work + User.connect(secret, function (err, proxy) { + cb(void 0, proxy); + }); }; Store.ready(function (err, Store) { @@ -31,7 +53,6 @@ define([ store = Store; }); - var common = {}; var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; @@ -83,6 +104,9 @@ define([ 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. @@ -129,21 +153,52 @@ define([ return window.location.hash.slice(1); }; - var setPadAttribute = common.setPadAttribute = function (attr, value, cb) { - getStore().set([getHash(), attr].join('.'), value, function (err, data) { + 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); }); }; - var getPadAttribute = common.getPadAttribute = function (attr, cb) { - getStore().get([getHash(), attr].join('.'), function (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) { - getStore().get(storageKey, function (err, recentPads) { + var getRecentPads = common.getRecentPads = function (cb, legacy) { + getStore(legacy).get(storageKey, function (err, recentPads) { if (isArray(recentPads)) { cb(void 0, migrateRecentPads(recentPads)); return; @@ -152,32 +207,16 @@ define([ }); }; + // STORAGE /* commit a list of pads to localStorage */ - var setRecentPads = common.setRecentPads = function (pads, cb) { - getStore().set(storageKey, pads, function (err, data) { + var setRecentPads = common.setRecentPads = function (pads, cb, legacy) { + getStore(legacy).set(storageKey, pads, function (err, data) { cb(err, data); }); }; - /* 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(); - }; - - 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 forgetPad = common.forgetPad = function (href, cb) { + // STORAGE + var forgetPad = common.forgetPad = function (href, cb, legacy) { var parsed = parsePadUrl(href); getRecentPads(function (err, recentPads) { @@ -195,7 +234,7 @@ define([ return; } - getStore().keys(function (err, keys) { + getStore(legacy).keys(function (err, keys) { if (err) { cb(err); return; @@ -208,24 +247,15 @@ define([ cb(); return; } - getStore().removeBatch(toRemove, function (err, data) { + getStore(legacy).removeBatch(toRemove, function (err, data) { cb(err, data); }); }); - }); - }); - }; - - var makePad = function (href, title) { - var now = ''+new Date(); - return { - href: href, - atime: now, - ctime: now, - title: title || window.location.hash.slice(1, 9), - }; + }, legacy); + }, legacy); }; + // STORAGE var rememberPad = common.rememberPad = window.rememberPad = function (title, cb) { // bail out early if (!/#/.test(window.location.hash)) { return; } @@ -265,6 +295,7 @@ define([ }); }; + // STORAGE var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); @@ -301,6 +332,7 @@ define([ }); }; + // STORAGE var getPadTitle = common.getPadTitle = function (cb) { var href = window.location.href; var parsed = parsePadUrl(window.location.href); @@ -324,6 +356,7 @@ define([ }); }; + // STORAGE var causesNamingConflict = common.causesNamingConflict = function (title, cb) { var href = window.location.href; @@ -364,13 +397,30 @@ define([ } }; - state++; + state = 2; Store.ready(function (err, store) { - env.store = 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, '_'); @@ -388,6 +438,9 @@ define([ }; }; + /* + * Alertifyjs + */ var styleAlerts = common.styleAlerts = function (href) { var $link = $('link[href="/customize/alertify.css"]'); if ($link.length) { return; } @@ -492,6 +545,9 @@ define([ Alertify.error(msg); }; + /* + * spinner + */ common.spinner = function (parent) { var $target = $('
', { // From ae116d47729ff1a59bd689071b8709f4d5aa32af Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:11:43 +0200 Subject: [PATCH 6/9] emit change events for remote storage. listen for those events being sent via postMessage --- customize.dist/share/frame.js | 22 ++++++++++++++++++++++ customize.dist/share/respond.js | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/customize.dist/share/frame.js b/customize.dist/share/frame.js index 0e32593c8..86147ca4b 100644 --- a/customize.dist/share/frame.js +++ b/customize.dist/share/frame.js @@ -49,6 +49,15 @@ }); }; + var changeHandlers = frame.changeHandlers = []; + + var change = frame.change = function (f) { + if (typeof(f) !== 'function') { + throw new Error('[Frame.change] expected callback'); + } + changeHandlers.push(f); + }; + var _listener = function (e) { if (!frame.accepts(e.origin)) { console.log("message from %s rejected!", e.origin); @@ -63,6 +72,14 @@ console.log("No uid!"); return; } + + if (uid === 'change' && changeHandlers.length) { + changeHandlers.forEach(function (f) { + f(data); + }); + return; + } + if (timeouts[uid]) { window.clearTimeout(timeouts[uid]); } @@ -89,6 +106,11 @@ }; var id = req._uid = uid(); + // uid must not equal 'change' + while(id === 'change') { + id = req._uid = uid(); + } + if (typeof(cb) === 'function') { //console.log("setting callback!"); listeners[id] = cb; diff --git a/customize.dist/share/respond.js b/customize.dist/share/respond.js index b5ae4671e..7001ed16e 100644 --- a/customize.dist/share/respond.js +++ b/customize.dist/share/respond.js @@ -81,3 +81,13 @@ window.addEventListener('message', function(e) { } }); +window.addEventListener('storage', function (ev) { + parent.postMessage(JSON.stringify({ + _uid: 'change', + data: { + key: ev.key, + oldValue: ev.oldValue, + newValue: ev.newValue, + } + }), '*'); +}); From f281cf898e002498a42b13279779a4c7515bf9bd Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:12:05 +0200 Subject: [PATCH 7/9] add login text to translations --- customize.dist/messages.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 79e4dd3a2..df5abbe44 100644 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -70,5 +70,8 @@ define(function () { '*/' ].join(''); + out.loginText = '

Your username and password are used to generate a unique key which is never known by our server.

\n' + + '

Be careful not to forget your credentials, as they are impossible to recover

'; + return out; }); From f403d8e2e22427436e2adbf075b015dccc6202dc Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 30 Aug 2016 18:15:43 +0200 Subject: [PATCH 8/9] remove table refresh button. listen for changes and refresh automatically --- customize.dist/index.html | 4 +--- customize.dist/main.js | 40 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/customize.dist/index.html b/customize.dist/index.html index a49dbba1e..2e2eb28bb 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -72,9 +72,7 @@ Link Created Last Accessed - - - + diff --git a/customize.dist/main.js b/customize.dist/main.js index 54c6bbfcc..2f0e70017 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -8,6 +8,10 @@ define([ ], function (Messages, DecorateToolbar, Cryptpad, LilUri, Email) { var $ = window.$; + var APP = window.APP = { + Cryptpad: Cryptpad, + }; + var email = Email.makeScrambler(1); // slip past the spammers, then unscramble mailto links @@ -22,14 +26,6 @@ define([ DecorateToolbar.main($('#bottom-bar')); Cryptpad.styleAlerts(); - var $table = $('table.scroll'); - var $tbody = $table.find('tbody'); - var $tryit = $('#tryit'); - var now = new Date(); - var hasRecent = false; - - var forgetPad = Cryptpad.forgetPad; - var padTypes = { '/pad/': 'Pad', '/code/': 'Code', @@ -37,16 +33,13 @@ define([ '/slide/': 'Presentation', }; - var truncateTitle = function (title, len) { - if (typeof(title) === 'string' && title.length > len) { - return title.slice(0, len) + '…'; - } - return title; - }; + var $table = $('table.scroll'); + var $tbody = $table.find('tbody'); + var $tryit = $('#tryit'); + var now = new Date(); + var hasRecent = false; - var fixHTML = function (html) { - return html.replace(/ Date: Thu, 1 Sep 2016 12:03:09 +0200 Subject: [PATCH 9/9] maintain aspect ratio for slides display index and slide count in title --- customize.dist/main.css | 95 +++++++++++++++++++++++++++++--- customize.dist/src/cryptpad.less | 58 ++++++++++++++++++- www/slide/main.js | 15 ++++- www/slide/slide.js | 28 +++++++++- 4 files changed, 182 insertions(+), 14 deletions(-) diff --git a/customize.dist/main.css b/customize.dist/main.css index 0206b7084..43169841c 100644 --- a/customize.dist/main.css +++ b/customize.dist/main.css @@ -384,7 +384,8 @@ form.realtime #adduser { form.realtime #addoption { border-bottom-left-radius: 5px; } -div.modal { +div.modal, +div#modal { box-sizing: border-box; z-index: 9001; position: fixed; @@ -395,7 +396,75 @@ div.modal { display: none; background-color: #302B28; } -div.modal .center { +div.modal #content, +div#modal #content { + box-sizing: border-box; + border: 1px solid white; + vertical-align: middle; + padding: 2.5vw; + width: 100vw; + height: 56.25vw; + max-height: 100vh; + max-width: 177.78vh; + margin: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +div.modal #content p, +div#modal #content p, +div.modal #content li, +div#modal #content li, +div.modal #content pre, +div#modal #content pre, +div.modal #content code, +div#modal #content code { + font-size: 2.5vw; + line-height: 3vw; +} +div.modal #content h1, +div#modal #content h1 { + font-size: 5vw; + line-height: 6vw; +} +div.modal #content h2, +div#modal #content h2 { + font-size: 4.2vw; + line-height: 5.04vw; +} +div.modal #content h3, +div#modal #content h3 { + font-size: 3.6vw; + line-height: 4.32vw; +} +div.modal #content h4, +div#modal #content h4 { + font-size: 3vw; + line-height: 3.6vw; +} +div.modal #content h5, +div#modal #content h5 { + font-size: 2.2vw; + line-height: 2.64vw; +} +div.modal #content h6, +div#modal #content h6 { + font-size: 1.6vw; + line-height: 1.92vw; +} +div.modal #content pre > code, +div#modal #content pre > code { + display: block; + position: relative; + border: 1px solid #333; + width: 90%; + margin: auto; + padding-left: .25vw; +} +div.modal .center, +div#modal .center { position: relative; width: 80%; height: 80%; @@ -403,31 +472,39 @@ div.modal .center { border: 1px solid #685d56; text-align: center; } -div.modal.shown { +div.modal.shown, +div#modal.shown { display: block; } -div.modal table { +div.modal table, +div#modal table { margin: 30px; border-collapse: collapse; } -div.modal table input { +div.modal table input, +div#modal table input { height: 100%; width: 90%; border: 3px solid #302B28; } -div.modal table tfoot tr td { +div.modal table tfoot tr td, +div#modal table tfoot tr td { z-index: 4000; cursor: pointer; } div.modal #addtime, -div.modal #adddate { +div#modal #addtime, +div.modal #adddate, +div#modal #adddate { color: #46E981; border: 1px solid #46E981; padding: 15px; } -div.modal #adddate { +div.modal #adddate, +div#modal #adddate { border-top-left-radius: 5px; } -div.modal #addtime { +div.modal #addtime, +div#modal #addtime { border-bottom-left-radius: 5px; } diff --git a/customize.dist/src/cryptpad.less b/customize.dist/src/cryptpad.less index 3304c312c..c70bb3168 100644 --- a/customize.dist/src/cryptpad.less +++ b/customize.dist/src/cryptpad.less @@ -471,7 +471,63 @@ form.realtime { #addoption { .bottom-left; } } -div.modal { +.viewportRatio (@x, @y, @p: 100) { + width: @p * 100vw; + height: @y * (@p * 100vw) / @x; + max-width: @x / @y * (@p * 100vh); + max-height: (@p * 100vh); +} + +div.modal, div#modal { + + #content { + box-sizing: border-box; + .size (@n) { + font-size: @n * 1vw; + line-height: @n * 1.2vw; + } + + border: 1px solid white; + + vertical-align: middle; + padding: 2.5vw; + + + width: 100vw; + height: 56.25vw; // height:width ratio = 9/16 = .5625 + //background: pink; + max-height: 100vh; + max-width: 177.78vh; // 16/9 = 1.778 + margin: auto; + position: absolute; + top:0;bottom:0; // vertical center + left:0;right:0; // horizontal center + + p, li, pre, code { + .size(2.5); + } + + h1 { .size(5); } + h2 { .size(4.2); } + h3 { .size(3.6); } + h4 { .size (3); } + h5 { .size(2.2); } + h6 { .size(1.6); } + + + pre > code { + + display: block; + position: relative; + border: 1px solid #333; + width: 90%; + margin: auto; + padding-left: .25vw; + + } + + } + box-sizing: border-box; z-index: 9001; position: fixed; diff --git a/www/slide/main.js b/www/slide/main.js index 86869a89b..d16316e83 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -102,7 +102,7 @@ define([ console.log("Couldn't get pad title"); return; } - document.title = title || window.location.hash.slice(1, 9); + document.title = APP.title = title || window.location.hash.slice(1, 9); Cryptpad.rememberPad(title, function (err, data) { if (err) { console.log("Couldn't remember pad"); @@ -140,7 +140,7 @@ define([ console.log(err); return; } - document.title = window.location.hash.slice(1,9); + document.title = APP.title = window.location.hash.slice(1,9); }); }); }); @@ -167,7 +167,7 @@ define([ console.error(err); return; } - document.title = title; + document.title = APP.title = title; }); }); }); @@ -267,6 +267,15 @@ define([ }); } + Slide.onChange(function (o, n, l) { + if (n !== null) { + document.title = APP.title + ' (' + (++n) + '/' + l + ')'; + return; + } + console.log("Exiting presentation mode"); + document.title = APP.title; + }); + setEditable(true); initializing = false; }; diff --git a/www/slide/slide.js b/www/slide/slide.js index 098cad315..6c473f5d6 100644 --- a/www/slide/slide.js +++ b/www/slide/slide.js @@ -12,6 +12,7 @@ define([ index: 0, lastIndex: 0, content: [], + changeHandlers: [], }; var $modal; var $content; @@ -20,6 +21,25 @@ define([ $content = Slide.$content = $c; }; + Slide.onChange = function (f) { + if (typeof(f) === 'function') { + Slide.changeHandlers.push(f); + } + }; + + var change = function (oldIndex, newIndex) { + if (oldIndex === newIndex) { + return false; + } + if (Slide.changeHandlers.length) { + Slide.changeHandlers.some(function (f, i) { + // HERE + f(oldIndex, newIndex, Slide.content.length); + }); + return true; + } + }; + var forbiddenTags = Slide.forbiddenTags = [ 'SCRIPT', 'IFRAME', @@ -87,10 +107,10 @@ define([ if (typeof(patch) === 'string') { $content.html(Marked(c)); - return; } else { DD.apply($content[0], patch); } + change(Slide.lastIndex, Slide.index); }; var show = Slide.show = function (bool, content) { @@ -99,8 +119,10 @@ define([ Slide.update(content); Slide.draw(Slide.index); $modal.addClass('shown'); + change(null, Slide.index); return; } + change(Slide.index, null); $modal.removeClass('shown'); }; @@ -115,12 +137,16 @@ define([ var left = Slide.left = function () { console.log('left'); + Slide.lastIndex = Slide.index; + var i = Slide.index = Math.max(0, Slide.index - 1); Slide.draw(i); }; var right = Slide.right = function () { console.log('right'); + Slide.lastIndex = Slide.index; + var i = Slide.index = Math.min(Slide.content.length -1, Slide.index + 1); Slide.draw(i); };