From a328b91ba5662c742a1ccba77fcf9b2ec8de5a83 Mon Sep 17 00:00:00 2001 From: Ev Bogue Date: Wed, 10 Apr 2019 19:10:02 -0500 Subject: 1.1.0 -- refactor to store client data in browser with localforage.js --- app.js | 451 ++++++++++++++++++++++++++++------------------------- index.html | 1 + lib.js | 388 ++++++++++++++++++++++++++------------------- localforage.min.js | 7 + package.json | 2 +- server.js | 10 +- 6 files changed, 478 insertions(+), 381 deletions(-) create mode 100644 localforage.min.js diff --git a/app.js b/app.js index 6f1edfe..2b2f05b 100644 --- a/app.js +++ b/app.js @@ -1,18 +1,39 @@ - - - var screen = h('div', {id: 'screen'}) document.body.appendChild(screen) -var keys = getKeys() +localforage.getItem('id', function (err, value) { + // the navbar has a dual purpose of generating a key if you don't already have one -var navbar = h('div', {classList: 'navbar'}, [ - h('div', {classList: 'internal'}, [ - h('li', [h('a', {href: '/'}, ['Home'])]), - h('li', [h('a', {href: '#' + keys.publicKey}, [getName(keys.publicKey)])]), - h('li', [h('a', {href: '/#key'}, ['Key'])]) - ]) -]) + if (value) { + var keys = value + var navbar = h('div', {classList: 'navbar'}, [ + h('div', {classList: 'internal'}, [ + h('li', [h('a', {href: '/'}, ['Home'])]), + h('li', [h('a', {href: '#' + keys.publicKey}, [getName(keys.publicKey)])]), + h('li', [h('a', {href: '/#key'}, ['Key'])]) + ]) + ]) + document.body.appendChild(navbar) + } else if (value == null) { + var genkey = nacl.sign.keyPair() + if (genkey) { + var keys = { + publicKey: '@' + nacl.util.encodeBase64(genkey.publicKey), + privateKey: nacl.util.encodeBase64(genkey.secretKey) + } + // when we get our next round of funding, let's figure out how to do this without a page reload + if (keys.publicKey.includes('/')) { + console.log('TRYING AGAIN') + setTimeout(function () { + window.location.reload() + }, 10) + } else { + localforage.setItem('id', keys) + window.location.reload() + } + } + } +}) if (!localStorage['subscribees']) { var subscribees = ['@218Fd2bCrmXe4gwnMg5Gcb9qVZrjXquym2AlelbkBro='] @@ -24,262 +45,264 @@ if (!localStorage['pubs']) { localStorage['pubs'] = JSON.stringify(pubs) } -document.body.appendChild(navbar) - function compose (keys, opts) { - var header = h('div', {classList: 'message'}) - var scroller = document.getElementById('scroller') - - scroller.insertBefore(header, scroller.firstChild) + localforage.getItem('id', function (err, keys) { + if (keys) { + var header = h('div', {classList: 'message'}) + var scroller = document.getElementById('scroller') + + scroller.insertBefore(header, scroller.firstChild) - var textarea = h('textarea', {placeholder: 'Write a new bog post'}) - - header.appendChild(textarea) - - var composer = h('div', [ - h('button', { - onclick: function () { - if (textarea.value) { - var content = { - author: keys.publicKey, - type: 'post', - text: textarea.value, - timestamp: Date.now() + var textarea = h('textarea', {placeholder: 'Write a new bog post'}) + + header.appendChild(textarea) + + var composer = h('div', [ + h('button', { + onclick: function () { + if (textarea.value) { + var content = { + author: keys.publicKey, + type: 'post', + text: textarea.value, + timestamp: Date.now() + } + textarea.value = '' + publish(content, keys) + } } - textarea.value = '' - publish(content, keys) - } - } - }, ['Publish']) - ]) - - header.appendChild(composer) + }, ['Publish']) + ]) + header.appendChild(composer) + } + }) } function route () { - src = window.location.hash.substring(1) - var scroller = h('div', {id: 'scroller'}) - var screen = document.getElementById('screen') + localforage.getItem('id', function (err, keys) { + src = window.location.hash.substring(1) + var scroller = h('div', {id: 'scroller'}) + var screen = document.getElementById('screen') - screen.appendChild(scroller) - if (src === 'key') { - var keyMessage = h('div', {classList: 'message'}) + screen.appendChild(scroller) - keyMessage.appendChild(h('p', {innerHTML: marked('This is your ed25519 public/private keypair. It was generated using [Tweetnacl.js](https://tweetnacl.js.org/#/). Your public key is your identiy when using [Bogbook](http://bogbook.com/), save your key in a safe place so that you can continue to use the same identity.')})) + if (src === 'key') { + var keyMessage = h('div', {classList: 'message'}) - // print stringified keypair - keyMessage.appendChild(h('pre', {style: 'width: 80%'}, [h('code', [JSON.stringify(keys)])])) + keyMessage.appendChild(h('p', {innerHTML: marked('This is your ed25519 public/private keypair. It was generated using [Tweetnacl.js](https://tweetnacl.js.org/#/). Your public key is your identiy when using [Bogbook](http://bogbook.com/), save your key in a safe place so that you can continue to use the same identity.')})) - // delete key button - keyMessage.appendChild(h('button', { - onclick: function () { - localStorage['id'] = '' - location.reload() - } - }, ['Delete Key'])) - - var textarea = h('textarea', {placeholder: 'Import your existing ed25519 keypair'}) - keyMessage.appendChild(textarea) - keyMessage.appendChild(h('button', { - onclick: function () { - if (textarea.value) { - localStorage['id'] = textarea.value - location.reload() + // print stringified keypair + keyMessage.appendChild(h('pre', {style: 'width: 80%'}, [h('code', [JSON.stringify(keys)])])) + + // delete key button + keyMessage.appendChild(h('button', { + onclick: function () { + localStorage['id'] = '' + location.reload() } - } - }, ['Import Key'])) + }, ['Delete Key'])) - scroller.appendChild(keyMessage) + var textarea = h('textarea', {placeholder: 'Import your existing ed25519 keypair'}) + keyMessage.appendChild(textarea) + keyMessage.appendChild(h('button', { + onclick: function () { + if (textarea.value) { + localStorage['id'] = textarea.value + location.reload() + } + } + }, ['Import Key'])) - var pubMessage = h('div', {classList: 'message'}) + scroller.appendChild(keyMessage) - var newPub = h('input', {placeholder: 'Add a new pub. Ex: ws://bogbook.com/'}) + var pubMessage = h('div', {classList: 'message'}) - var pubs = JSON.parse(localStorage['pubs']) + var newPub = h('input', {placeholder: 'Add a new pub. Ex: ws://bogbook.com/'}) - pubMessage.appendChild(h('div', [ - h('p', {innerHTML: marked('These are your bogbook pubs. These servers will sync data when you publish a new post, when you subscribe to new feeds, and when you click on feed ids.')}), - newPub, - h('button', { - onclick: function () { - if (newPub.value) { - pubs.push(newPub.value) - localStorage['pubs'] = JSON.stringify(pubs) - location.reload() + var pubs = JSON.parse(localStorage['pubs']) + + pubMessage.appendChild(h('div', [ + h('p', {innerHTML: marked('These are your bogbook pubs. These servers will sync data when you publish a new post, when you subscribe to new feeds, and when you click on feed ids.')}), + newPub, + h('button', { + onclick: function () { + if (newPub.value) { + pubs.push(newPub.value) + localStorage['pubs'] = JSON.stringify(pubs) + location.reload() + } } - } - }, ['Add Pub']) - ])) - - function removeButton (pubName) { - var button = h('button', { - onclick: function () { - console.log('removing' + pubName) - for (var i = pubs.length; i--;) { - if (pubs[i] === pubName) { - pubs.splice(i, 1); - localStorage['pubs'] = JSON.stringify(pubs) - window.location.reload() + }, ['Add Pub']) + ])) + + function removeButton (pubName) { + var button = h('button', { + onclick: function () { + console.log('removing' + pubName) + for (var i = pubs.length; i--;) { + if (pubs[i] === pubName) { + pubs.splice(i, 1); + localStorage['pubs'] = JSON.stringify(pubs) + window.location.reload() + } } } - } - }, ['Remove Pub']) - return button + }, ['Remove Pub']) + return button + } + + for (i = 0; i < pubs.length; i++) { + var pubName = pubs[i] + pubMessage.appendChild(h('p', [ + pubName, + removeButton(pubName) + ])) + } + + scroller.appendChild(pubMessage) } - for (i = 0; i < pubs.length; i++) { - var pubName = pubs[i] - pubMessage.appendChild(h('p', [ - pubName, - removeButton(pubName) - ])) - } - scroller.appendChild(pubMessage) - } + else if (src[0] === '@') { + var profile = h('div', {classList: 'message'}) + scroller.appendChild(profile) + if (src == keys.publicKey) { + var nameInput = h('input', {placeholder: 'Publish a new name'}) - else if (src[0] === '@') { - var profile = h('div', {classList: 'message'}) - scroller.appendChild(profile) + var namePublisher = h('div',[ + nameInput, + h('button', { + onclick: function () { + if (nameInput.value) { - if (src == keys.publicKey) { - var nameInput = h('input', {placeholder: 'Publish a new name'}) + var content = { + author: keys.publicKey, + type: 'name', + text: nameInput.value, + timestamp: Date.now() + } - var namePublisher = h('div',[ - nameInput, - h('button', { - onclick: function () { - if (nameInput.value) { + publish(content, keys) + } + } + }, ['Publish']) + ]) + profile.appendChild(namePublisher) + + readFile() + + var imageInput = h('span', [ + h('input', {id: 'inp', type:'file'}), + h('span', {id: 'b64'}), + h('img', {id: 'img'}) + ]) + + var imagePublisher = h('div', [ + imageInput, + h('button', { + onclick: function () { var content = { author: keys.publicKey, - type: 'name', - text: nameInput.value, + type: 'image', + image: document.getElementById("img").src, timestamp: Date.now() } publish(content, keys) } - } - }, ['Publish']) - ]) - - profile.appendChild(namePublisher) + }, ['Publish']) + ]) + + profile.appendChild(imagePublisher) + + document.getElementById("inp").addEventListener("change", readFile); + + } else { + var subscribees = JSON.parse(localStorage['subscribees']) + if (subscribees.includes(src)) { + profile.appendChild(h('button', { + onclick: function () { + for (var i = subscribees.length; i--;) { + if (subscribees[i] === src) { + subscribees.splice(i, 1); + localStorage['subscribees'] = JSON.stringify(subscribees) + window.location.reload() + } + } + } + // remove subscribee + }, ['UNSUBSCRIBE'])) + } else { + profile.appendChild(h('button', { + onclick: function () { + subscribees.push(src) + localStorage['subscribees'] = JSON.stringify(subscribees) + window.location.reload() + } + }, ['SUBSCRIBE'])) + } + } - readFile() + var pubs = JSON.parse(localStorage['pubs']) - var imageInput = h('span', [ - h('input', {id: 'inp', type:'file'}), - h('span', {id: 'b64'}), - h('img', {id: 'img'}) - ]) + for (i = 0; i < pubs.length; i++) { + requestFeed(src, pubs[i]) + } + + localforage.getItem(src, function (err, log) { + if (log) { + for (var i=0; i < log.length; i++) { + var post = log[i] + scroller.appendChild(renderMessage(post)) + } + } + }) + } - var imagePublisher = h('div', [ - imageInput, - h('button', { - onclick: function () { - var content = { - author: keys.publicKey, - type: 'image', - image: document.getElementById("img").src, - timestamp: Date.now() - } + else if (src[0] === '%') { - publish(content, keys) + localforage.getItem('log', function (err, log) { + for (var i = log.length - 1; i >= 0; --i) { + if (log[i].key === src) { + var post = log[i] + scroller.appendChild(renderMessage(post)) } - }, ['Publish']) - ]) - - profile.appendChild(imagePublisher) + } + }) + } - document.getElementById("inp").addEventListener("change", readFile); + else { + compose(keys) - } else { var subscribees = JSON.parse(localStorage['subscribees']) - if (subscribees.includes(src)) { - profile.appendChild(h('button', { - onclick: function () { - for (var i = subscribees.length; i--;) { - if (subscribees[i] === src) { - subscribees.splice(i, 1); - localStorage['subscribees'] = JSON.stringify(subscribees) - window.location.reload() - } - } - } - // remove subscribee - }, ['UNSUBSCRIBE'])) - } else { - profile.appendChild(h('button', { - onclick: function () { - subscribees.push(src) - localStorage['subscribees'] = JSON.stringify(subscribees) - window.location.reload() - } - }, ['SUBSCRIBE'])) - } - } - var pubs = JSON.parse(localStorage['pubs']) - - for (i = 0; i < pubs.length; i++) { - requestFeed(src, pubs[i]) - } - - if (localStorage[src]) { - var log = JSON.parse(localStorage[src]) - for (var i=0; i < log.length; i++) { - var post = log[i] - scroller.appendChild(renderMessage(post)) + console.log(subscribees) + for (i = 0; i < subscribees.length; i++) { + var pubs = JSON.parse(localStorage['pubs']) + for (n = 0; n < pubs.length; n++) { + requestFeed(subscribees[i], pubs[n]) + } } - } - } - else if (src[0] === '%') { - if (localStorage['log']) { - var log = JSON.parse(localStorage['log']) - console.log(log.length) - for (var i = log.length - 1; i >= 0; --i) { - if (log[i].key === src) { + localforage.getItem('log', function (err, log) { + for (var i=0; i < log.length; i++) { var post = log[i] scroller.appendChild(renderMessage(post)) } - } - } - } - - else { - compose(keys) - - var subscribees = JSON.parse(localStorage['subscribees']) - - console.log(subscribees) - for (i = 0; i < subscribees.length; i++) { - var pubs = JSON.parse(localStorage['pubs']) - for (n = 0; n < pubs.length; n++) { - requestFeed(subscribees[i], pubs[n]) - } - } - - if (localStorage['log']) { - var log = JSON.parse(localStorage['log']) - for (var i=0; i < log.length; i++) { - var post = log[i] - scroller.appendChild(renderMessage(post)) - } - // sort log by timestamp and save over -- I don't know how expensive this is, we probably don't want to do this every single time we load the log? - var newLog = log.sort(function (a, b) { - return b.content.timestamp - a.content.timestamp + var newLog = log.sort(function (a, b) { + return b.content.timestamp - a.content.timestamp + }) + if (newLog) { + localforage.setItem('log', log) + } }) - if (newLog) { - localStorage['log'] = JSON.stringify(newLog) - } } - } + }) } diff --git a/index.html b/index.html index 59cf4b5..5060787 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@ + diff --git a/lib.js b/lib.js index 659976f..6a48143 100644 --- a/lib.js +++ b/lib.js @@ -1,135 +1,193 @@ // generate a public.private keypair with TweetNaCl.js function getKeys () { - if (localStorage['id']) { - var keys = JSON.parse(localStorage['id']) - return keys - } else { - - var genkey = nacl.sign.keyPair() - if (genkey) { - var keys = { - publicKey: '@' + nacl.util.encodeBase64(genkey.publicKey), - privateKey: nacl.util.encodeBase64(genkey.secretKey), + localforage.getItem('id', function(err, value) { + if (value) { + var keys = value + return keys + } else if (value == null) { + var genkey = nacl.sign.keyPair() + if (genkey) { + var keys = { + publicKey: '@' + nacl.util.encodeBase64(genkey.publicKey), + privateKey: nacl.util.encodeBase64(genkey.secretKey) + } + // when we get our next round of funding, let's figure out how to do this without a page reload + if (keys.publicKey.includes('/')) { + console.log('TRYING AGAIN') + setTimeout(function () { + window.location.reload() + }, 10) + } else { + localforage.setItem('id', keys) + return keys + } } + } + }) + return keys +} - console.log(genkey) +function requestFeed (src, server) { + var ws = new WebSocket(server + src) - // for some reason keys with /'s in them mess up node, so we'll try generating keys again if they contain slashes - if (keys.publicKey.includes('/')) { - console.log('TRYING AGAIN') - setTimeout(function () { - window.location.reload() - }, 10) - } else { - localStorage['id'] = JSON.stringify(keys) - return keys + localforage.getItem(src, function (err, log) { + if (log) { + // update the log + + console.log('LOG DOES EXIST, asking') + ws.onopen = function () { + // req feed + var clientLog = { + publicKey: src, + log: log + } + console.log(clientLog) + ws.send(JSON.stringify(clientLog)) + } + ws.onmessage = function (ev) { + console.log(ev.data) + var serverLog = JSON.parse(ev.data) + + if (serverLog.log.length > log.length) { + // update the log of the id + localforage.setItem(src, serverLog.log) + + // concat new items from the log onto the client's public log + localforage.getItem('log', function (err, feed) { + if (feed) { + var num = serverLog.log.length - log.length + var diff = serverLog.log.slice(0, num) + oldLog = feed + newLog = diff.concat(oldLog) + localforage.setItem('log', newLog) + } + }) + } + } + } else { + ws.onopen = function () { + // req feed + var clientLog = { + publicKey: src, + log: [] + } + ws.send(JSON.stringify(clientLog)) } + // request the log (because we don't have it) + console.log('LOG DOES NOT EXIST, asking') + ws.onmessage = function (ev) { + serverLog = JSON.parse(ev.data) + localforage.setItem(src, serverLog.log) + + // concat new items from the log onto the client's public log + localforage.getItem('log', function (err, feed) { + if (feed) { + newLog = serverLog.log.concat(feed) + localforage.setItem('log', newLog) + } else { + localforage.setItem('log', serverLog.log) + } + }) + } } - } + }) } -function requestFeed (src, server) { - var ws = new WebSocket(server + src) +// publish new messages to your log +function publish (content, keys) { - var clientLog = { - publicKey: src - } + console.log(content) + console.log(keys) + localforage.getItem(keys.publicKey, function (err, log) { + if (log) { + var lastPost = log[0] + var seq = lastPost.content.sequence + content.sequence = ++seq + content.previous = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(log[0])))) - if (localStorage[src]) { - clientLog.log = JSON.parse(localStorage[src]) - } else { - clientLog.log = [] - } + var post = { + content: content, + signature: nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(content)), nacl.util.decodeBase64(keys.privateKey))) + } - ws.onopen = function () { - ws.send(JSON.stringify(clientLog)) - } + // add key (which is a hash of the stringified object post) + post.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post)))) - ws.onmessage = function (ev) { - var serverData = JSON.parse(ev.data) - if (serverData.log.length > clientLog.log.length) { + // update the log + updateLog(keys.publicKey, post) - // update the log of the id - localStorage[src] = JSON.stringify(serverData.log) + var pubs = JSON.parse(localStorage['pubs']) - // contact new items from the log onto the client's log of everything - var num = serverData.log.length - clientLog.log.length - var diff = serverData.log.slice(0, num) + for (i = 0; i < pubs.length; i++) { + requestFeed(keys.publicKey, pubs[i]) + } - if (localStorage['log']) { - var oldLog = JSON.parse(localStorage['log']) + var scroller = document.getElementById('scroller') + if (scroller.firstChild) { + scroller.insertBefore(renderMessage(post), scroller.childNodes[1]) } else { - var oldLog = [] + scroller.appendChild(renderMessage(post)) } - var newLog = diff.concat(oldLog) - localStorage['log'] = JSON.stringify(newLog) - location.reload() - } - } -} - -// publish new messages to your log -function publish (content, keys) { + } else { + content.sequence = 0 + var post = { + content: content, + signature: nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(content)), nacl.util.decodeBase64(keys.privateKey))) + } - if (localStorage[keys.publicKey]) { - var log = JSON.parse(localStorage[keys.publicKey]) - var lastPost = log[0] - var seq = lastPost.content.sequence - content.sequence = ++seq - content.previous = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(log[0])))) - console.log(content.previous) - } else { - console.log('SEQUENCE 0') - content.sequence = 0 - } + // add key (which is a hash of the stringified object post) + post.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post)))) - var post = { - content: content, - signature: nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(content)), nacl.util.decodeBase64(keys.privateKey))) - } + // update the log + updateLog(keys.publicKey, post) - // add key (which is a hash of the stringified object post) - post.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post)))) + var pubs = JSON.parse(localStorage['pubs']) - // update the log - updateLog(keys.publicKey, post) - - var pubs = JSON.parse(localStorage['pubs']) + for (i = 0; i < pubs.length; i++) { + requestFeed(keys.publicKey, pubs[i]) + } - for (i = 0; i < pubs.length; i++) { - requestFeed(keys.publicKey, pubs[i]) - } + var scroller = document.getElementById('scroller') + if (scroller.firstChild) { + scroller.insertBefore(renderMessage(post), scroller.childNodes[1]) + } else { + scroller.appendChild(renderMessage(post)) + } - var scroller = document.getElementById('scroller') - if (scroller.firstChild) { - scroller.insertBefore(renderMessage(post), scroller.childNodes[1]) - } else { - scroller.appendChild(renderMessage(post)) - } + } + }) } // update your log in the browser function updateLog (feed, post) { - if (localStorage[feed]) { - var log = JSON.parse(localStorage[feed]) - log.unshift(post) - localStorage[feed] = JSON.stringify(log) - } else { - var log = [post] - localStorage[feed] = JSON.stringify(log) - } + console.log('UPDATE LOG') + console.log(feed) + console.log(post) + localforage.getItem(feed, function (err, log) { + if (log) { + log.unshift(post) + localforage.setItem(feed, log) + } else { + log = [] + log.unshift(post) + localforage.setItem(feed, log) + } + }) - if (localStorage['log']) { - var log = JSON.parse(localStorage['log']) - log.unshift(post) - localStorage['log'] = JSON.stringify(log) - } else { - var log = [post] - localStorage['log'] = JSON.stringify(log) - } + localforage.getItem('log', function (err, log) { + if (log) { + log.unshift(post) + localforage.setItem('log', log) + } else { + log = [] + log.unshift(post) + localforage.setItem('log', log) + } + + }) } // file uploaders for user images @@ -181,23 +239,22 @@ function renderMessage (post) { if (post.content.type == 'post') { - var log = JSON.parse(localStorage['log']) - setTimeout(function () { - for (var i = log.length - 1; i >= 0; --i) { - //console.log(i) - if (log[i].content.reply == post.key) { - var nextPost = log[i] - console.log(nextPost) - var messageExists = (document.getElementById(nextPost.key) !== null); - if (!messageExists) { - messageDiv.appendChild(h('div', {classList: 'submessage'}, [ - renderMessage(nextPost) - ])) + localforage.getItem('log', function (err, log) { + if (log) { + for (var i = log.length - 1; i >= 0; --i) { + if (log[i].content.reply == post.key) { + var nextPost = log[i] + var messageExists = (document.getElementById(nextPost.key) !== null); + if (!messageExists) { + messageDiv.appendChild(h('div', {classList: 'submessage'}, [ + renderMessage(nextPost) + ])) + } } } } - }, 10) - + }) + var renderer = new marked.Renderer(); renderer.link = function(href, title, text) { if ((href[0] == '@') || (href[0] == '%')) { @@ -222,7 +279,6 @@ function renderMessage (post) { message.appendChild(h('div', {innerHTML: marked(post.content.text)})) - message.appendChild(h('span', {id: post.key + 'src', classList: 'right'}, [ h('a', { onclick: function () { @@ -235,37 +291,41 @@ function renderMessage (post) { var gotName = getName(post.content.author) - var publishButton = h('button', { - onclick: function () { - if (textarea.value) { - var content = { - author: keys.publicKey, - type: 'post', - text: textarea.value, - reply: post.key, - timestamp: Date.now() + localforage.getItem('id', function (err, keys) { + + var publishButton = h('button', { + onclick: function () { + if (textarea.value) { + var content = { + author: keys.publicKey, + type: 'post', + text: textarea.value, + reply: post.key, + timestamp: Date.now() + } + publish(content, keys) + message.removeChild(textarea) + message.removeChild(publishButton) } - console.log(content) - publish(content, keys) - message.removeChild(textarea) - message.removeChild(publishButton) } - } - }, ['Publish']) + }, ['Publish']) - var textarea = h('textarea', {placeholder: 'Reply to this bog post'}, ['['+ gotName.textContent + '](' + post.content.author + ')']) - var replyButton = h('button', { - classList: 'replyButton:' + post.key, - onclick: function () { - message.removeChild(replyButton) - message.appendChild(textarea) - message.appendChild(publishButton) - - } - }, ['Reply']) + var textarea = h('textarea', {placeholder: 'Reply to this bog post'}, ['['+ gotName.textContent + '](' + post.content.author + ')']) + + var replyButton = h('button', { + classList: 'replyButton:' + post.key, + onclick: function () { + message.removeChild(replyButton) + message.appendChild(textarea) + message.appendChild(publishButton) + + } + }, ['Reply']) + + message.appendChild(replyButton) + }) - message.appendChild(replyButton) messageDiv.appendChild(message) } @@ -274,34 +334,40 @@ function renderMessage (post) { function getImage (id) { - var image = h('img', {classList: 'small'}) - - if (localStorage[id]) { - var log = JSON.parse(localStorage[id]) - for (var i=0; i < log.length; i++) { - var imagePost = log[i] - if (imagePost.content.type == 'image') { - image = h('img', {classList: 'small', src: imagePost.content.image}) - return image - } + var image = h('span') + + //image.appendChild(h('img', {classList: 'small'})) + + localforage.getItem(id, function (err, log) { + if (log) { + for (var i=0; i < log.length; i++) { + var imagePost = log[i] + if (imagePost.content.type == 'image') { + image.appendChild(h('img', {classList: 'small', src: imagePost.content.image})) + return + } + } } - } + }) return image } function getName (id) { - var name = h('span', [id.substring(0, 10) + '...']) - if (localStorage[id]) { - var log = JSON.parse(localStorage[id]) - for (var i=0; i < log.length; i++) { - var namePost = log[i] - if (namePost.content.type == 'name') { - name = h('span', ['@' + namePost.content.text]) - return name + var name = h('span') + name.textContent = id.substring(0, 10) + '...' + + localforage.getItem(id, function (err, log) { + if (log) { + for (var i=0; i < log.length; i++) { + var namePost = log[i] + if (namePost.content.type == 'name') { + name.textContent = '@' + namePost.content.text + //return name + } } } - } + }) return name } diff --git a/localforage.min.js b/localforage.min.js new file mode 100644 index 0000000..eb87534 --- /dev/null +++ b/localforage.min.js @@ -0,0 +1,7 @@ +/*! + localForage -- Offline Storage, Improved + Version 1.7.3 + https://localforage.github.io/localForage + (c) 2013-2017 Mozilla, Apache License 2.0 +*/ +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.localforage=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof xa?va.resolve(xa):m(a).then(function(a){return xa=a})}function o(a){var b=ya[a.name],c={};c.promise=new va(function(a,b){c.resolve=a,c.reject=b}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=ya[a.name],c=b.deferredOperations.pop();if(c)return c.resolve(),c.promise}function q(a,b){var c=ya[a.name],d=c.deferredOperations.pop();if(d)return d.reject(b),d.promise}function r(a,b){return new va(function(c,d){if(ya[a.name]=ya[a.name]||B(),a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ua.open.apply(ua,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(wa)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function s(a){return r(a,!1)}function t(a){return r(a,!0)}function u(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.versiona.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function v(a){return new va(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function w(a){return g([l(atob(a.data))],{type:a.type})}function x(a){return a&&a.__local_forage_encoded_blob}function y(a){var b=this,c=b._initReady().then(function(){var a=ya[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return i(c,a,a),c}function z(a){o(a);for(var b=ya[a.name],c=b.forages,d=0;d0&&(!a.db||"InvalidStateError"===e.name||"NotFoundError"===e.name))return va.resolve().then(function(){if(!a.db||"NotFoundError"===e.name&&!a.db.objectStoreNames.contains(a.storeName)&&a.version<=a.db.version)return a.db&&(a.version=a.db.version+1),t(a)}).then(function(){return z(a).then(function(){A(a,b,c,d-1)})}).catch(c);c(e)}}function B(){return{forages:[],db:null,dbReady:null,deferredOperations:[]}}function C(a){function b(){return va.resolve()}var c=this,d={db:null};if(a)for(var e in a)d[e]=a[e];var f=ya[d.name];f||(f=B(),ya[d.name]=f),f.forages.push(c),c._initReady||(c._initReady=c.ready,c.ready=y);for(var g=[],h=0;h>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function O(a){var b,c=new Uint8Array(a),d="";for(b=0;b>2],d+=Da[(3&c[b])<<4|c[b+1]>>4],d+=Da[(15&c[b+1])<<2|c[b+2]>>6],d+=Da[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function P(a,b){var c="";if(a&&(c=Ua.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ua.call(a.buffer))){var d,e=Ga;a instanceof ArrayBuffer?(d=a,e+=Ia):(d=a.buffer,"[object Int8Array]"===c?e+=Ka:"[object Uint8Array]"===c?e+=La:"[object Uint8ClampedArray]"===c?e+=Ma:"[object Int16Array]"===c?e+=Na:"[object Uint16Array]"===c?e+=Pa:"[object Int32Array]"===c?e+=Oa:"[object Uint32Array]"===c?e+=Qa:"[object Float32Array]"===c?e+=Ra:"[object Float64Array]"===c?e+=Sa:b(new Error("Failed to get type for BinaryArray"))),b(e+O(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=Ea+a.type+"~"+O(this.result);b(Ga+Ja+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function Q(a){if(a.substring(0,Ha)!==Ga)return JSON.parse(a);var b,c=a.substring(Ta),d=a.substring(Ha,Ta);if(d===Ja&&Fa.test(c)){var e=c.match(Fa);b=e[1],c=c.substring(e[0].length)}var f=N(c);switch(d){case Ia:return f;case Ja:return g([f],{type:b});case Ka:return new Int8Array(f);case La:return new Uint8Array(f);case Ma:return new Uint8ClampedArray(f);case Na:return new Int16Array(f);case Pa:return new Uint16Array(f);case Oa:return new Int32Array(f);case Qa:return new Uint32Array(f);case Ra:return new Float32Array(f);case Sa:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function R(a,b,c,d){a.executeSql("CREATE TABLE IF NOT EXISTS "+b.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],c,d)}function S(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new va(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){R(e,c,function(){b._dbInfo=c,a()},function(a,b){d(b)})},d)});return c.serializer=Va,e}function T(a,b,c,d,e,f){a.executeSql(c,d,e,function(a,g){g.code===g.SYNTAX_ERR?a.executeSql("SELECT name FROM sqlite_master WHERE type='table' AND name = ?",[b.storeName],function(a,h){h.rows.length?f(a,g):R(a,b,function(){a.executeSql(c,d,e,f)},f)},f):f(a,g)},f)}function U(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function V(a,b){var c=this,d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h0)return void f(W.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return h(f,c),f}function X(a,b,c){return W.apply(this,[a,b,c,1])}function Y(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function Z(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return h(c,a),c}function $(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return h(c,a),c}function _(a,b){var c=this,d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function aa(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e '__WebKitDatabaseInfoTable__'",[],function(c,d){for(var e=[],f=0;f0}function ha(a){var b=this,c={};if(a)for(var d in a)c[d]=a[d];return c.keyPrefix=ea(a,b._defaultConfig),ga()?(b._dbInfo=c,c.serializer=Va,va.resolve()):va.reject()}function ia(a){var b=this,c=b.ready().then(function(){for(var a=b._dbInfo.keyPrefix,c=localStorage.length-1;c>=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return h(c,a),c}function ja(a,b){var c=this;a=j(a);var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return h(d,b),d}function ka(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h=0;b--){var c=localStorage.key(b);0===c.indexOf(a)&&localStorage.removeItem(c)}}):va.reject("Invalid arguments"),h(d,b),d}function ra(a,b){a[b]=function(){var c=arguments;return a.ready().then(function(){return a[b].apply(a,c)})}}function sa(){for(var a=1;a