diff --git a/dist/index.css b/dist/index.css
index 8811f7c..f58da7f 100644
--- a/dist/index.css
+++ b/dist/index.css
@@ -26,6 +26,30 @@ body {
height: 100%;
}
+.avatarcontainer {
+ height: 30px;
+ padding: 3px;
+}
+
+.avatarlink {
+ cursor: pointer;
+ position: absolute;
+}
+
+.avatarlink > .img {
+ width: 30px;
+ height: 30px;
+ padding-right: 5px;
+}
+
+.avatarlink > .name {
+ position: absolute;
+ top: 5px;
+ display: block;
+ height: 30px;
+ left: 35px;
+}
+
a {
color: #000;
text-decoration: underline;
@@ -102,8 +126,9 @@ ul.dashed {
flex-grow: 4;
}
-#profile > span > .description h2 {
- margin-top: 0;
+#profile > span > .description > .feedId {
+ margin-bottom: 0.5rem;
+ font-size: .8rem;
}
#profile > h2 {
diff --git a/dist/index.html b/dist/index.html
index 91fe813..5df00ce 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -7,9 +7,9 @@
diff --git a/dist/noavatar.svg b/dist/noavatar.svg
new file mode 100644
index 0000000..c198ed7
--- /dev/null
+++ b/dist/noavatar.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/index.js b/index.js
index f980107..ed6e54d 100644
--- a/index.js
+++ b/index.js
@@ -68,6 +68,9 @@ function getGroupKeysFeed(SSB, cb) {
})
}
+// load ssb-profile-link
+require('./ssb-profile-link')
+
const menu = new Vue({
el: '#menu',
@@ -277,14 +280,6 @@ function setupApp(SSB) {
}
}, 1000)
- // main feed replicated on rpc connect
- SSB.on('rpc:connect', function (rpc, isClient) {
- if (rpc.id !== roomKey) {
- console.log("request connect", rpc.id)
- SSB.ebt.request(rpc.id, true)
- }
- })
-
// find all meta feeds & children and replicate those
pull(
SSB.db.query(
diff --git a/profile.js b/profile.js
new file mode 100644
index 0000000..88ebe1b
--- /dev/null
+++ b/profile.js
@@ -0,0 +1,444 @@
+module.exports = function (feedId) {
+ const ssbSingleton = require('ssb-browser-core/ssb-singleton')
+ const mnemonic = require('ssb-keys-mnemonic')
+
+ let initialState = function(self) {
+ return {
+ componentStillLoaded: false,
+ isSelf: false,
+ following: false,
+ blocking: false,
+ name: '',
+ image: 'noavatar.svg',
+ imageBlobId: '',
+ showFriends: true,
+ showBlocked: false,
+ showFollowers: false,
+ showBlockingUs: false,
+ friends: [],
+ followers: [],
+ blocked: [],
+ blockingUs: [],
+ waitingForBlobURLs: 0,
+
+ showExportKey: false,
+ showImportKey: false,
+ mnemonic: '',
+ }
+ }
+
+ return {
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
🚫
+
+
+
{{ feedId }}
+
{{ name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A mnemonic code has been generated from your private feed key.
+
+ This code can be used to restore your identity later. Store this somewhere safe.
+
+
+
+
+ {{ mnemonic }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Enter mnemonic code below to restore your feed key.
+
+ WARNING: this will overwrite your current feed key!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`,
+
+ data: function() {
+ return initialState(this)
+ },
+
+ computed: {
+ followText: function() { return this.following ? 'Unfollow' : 'Follow' },
+ blockText: function() { return this.blocking ? 'Unblock' : 'Block' },
+ },
+
+ methods: {
+ cacheImageURLForPreview: function(blobId, cb) {
+ var self = this;
+ [ err, SSB ] = ssbSingleton.getSSB()
+ if (!SSB || !SSB.blobs) {
+ // Not going to hurt anything to try again later.
+ setTimeout(function() {
+ self.cacheImageURLForPreview(blobId, cb)
+ }, 3000)
+ return
+ }
+
+ ++this.waitingForBlobURLs
+ SSB.blobs.fsURL(blobId, (err, blobURL) => {
+ // If this is the last blob we were waiting for, call the callback.
+ --self.waitingForBlobURLs
+ if (self.waitingForBlobURLs == 0)
+ cb(null, true)
+ })
+ },
+
+ onFileSelect: function(ev) {
+ const file = ev.target.files[0]
+
+ if (!file) return
+
+ var self = this;
+ [ err, SSB ] = ssbSingleton.getSSB()
+ if (!SSB || !SSB.blobs) {
+ alert("Can't add file right now. Database couldn't be locked. Please make sure you only have one running instance of ssb-browser.")
+ return
+ }
+
+ file.arrayBuffer().then(function (buffer) {
+ SSB.blobs.hash(new Uint8Array(buffer), (err, digest) => {
+ var blobId = "&" + digest
+ SSB.blobs.add(blobId, file, (err) => {
+ if (!err) {
+ SSB.blobs.push(blobId, (err) => {
+ SSB.blobs.localGet(blobId, (err, url) => {
+ if (!err) {
+ self.image = url
+ self.imageBlobId = blobId
+ }
+ })
+ })
+ } else
+ alert("failed to add img", err)
+ })
+ })
+ })
+ },
+
+ exportKey: function() {
+ this.mnemonic = mnemonic.keysToWords(JSON.parse(localStorage["/.groupies/secret"]))
+ this.showExportKey = true
+ },
+
+ restoreKey: function() {
+ const key = mnemonic.wordsToKeys(this.mnemonic)
+ localStorage["/.groupies/secret"] = JSON.stringify(key)
+ this.showImportKey = false
+
+ localStorage["/.groupies/restoreFeed"] = "true"
+
+ alert("Please reload")
+ },
+
+ saveProfile: function() {
+ [ err, SSB ] = ssbSingleton.getSSB()
+ if (!SSB || !SSB.db) {
+ alert("Can't save right now. Database couldn't be locked. Please make sure you only have one running instance of ssb-browser.")
+ return
+ }
+
+ var msg = { type: 'about', about: SSB.id }
+ if (this.name)
+ msg.name = this.name
+ if (this.imageBlobId != '') {
+ msg.image = {
+ link: this.imageBlobId
+ }
+ }
+
+ // Make sure the full post (including headers) is not larger than the 8KiB limit.
+ if (JSON.stringify(msg).length > 8192) {
+ alert('Your post is too large. Each post can only be 8KiB. Please shorten your post or split it into multiple posts.')
+ return
+ }
+
+ SSB.db.publish(msg, (err) => {
+ if (err) return alert(err)
+
+ alert("Saved!")
+ })
+ },
+
+ changeFollowStatus: function() {
+ [ err, SSB ] = ssbSingleton.getSSB()
+ var self = this
+ if (!SSB || !SSB.db) {
+ alert("Can't change follow status right now. Database couldn't be locked. Please make sure you only have one running instance of ssb-browser.")
+ return
+ }
+
+ if (this.following) {
+ SSB.db.publish({
+ type: 'contact',
+ contact: this.feedId,
+ following: false
+ }, () => {
+ alert('Unfollowed') // FIXME: proper UI
+ })
+ } else {
+ SSB.db.publish({
+ type: 'contact',
+ contact: this.feedId,
+ following: true
+ }, () => {
+ alert('Followed') // FIXME: proper UI
+ })
+ }
+ },
+
+ changeBlockStatus: function() {
+ [ err, SSB ] = ssbSingleton.getSSB()
+ if (!SSB || !SSB.db) {
+ alert("Can't change blocking status right now. Database couldn't be locked. Please make sure you only have one running instance of ssb-browser.")
+ return
+ }
+
+ var self = this
+ if (this.blocking) {
+ SSB.db.publish({
+ type: 'contact',
+ contact: this.feedId,
+ blocking: false
+ }, () => {
+ self.blocking = false
+ alert('Unblocked') // FIXME: proper UI
+ })
+ } else {
+ SSB.db.publish({
+ type: 'contact',
+ contact: this.feedId,
+ blocking: true
+ }, () => {
+ SSB.db.deleteFeed(this.feedId, (err) => {
+ if (err) {
+ self.blocking = true
+ alert('Failed to delete messages, but user is blocked.')
+ } else {
+ alert('Blocked') // FIXME: proper UI
+ }
+ })
+ })
+ }
+ },
+
+ deleteFeed: function() {
+ [ err, SSB ] = ssbSingleton.getSSB()
+ if (!SSB || !SSB.db) {
+ alert("Can't delete feed right now. Database couldn't be locked. Please make sure you only have one running instance of ssb-browser.")
+ return
+ }
+
+ var self = this
+ SSB.db.deleteFeed(this.feedId, (err) => {
+ if (err) return alert('Failed to remove feed', err)
+
+ this.$router.push({ path: '/public'})
+ })
+ },
+
+ updateFollowers: function(err, SSB) {
+ var self = this
+ var opts = {
+ start: this.feedId,
+ max: 1,
+ reverse: true
+ }
+ SSB.friends.hops(opts, (err, feeds) => {
+ var newFollowers = []
+ for(f in feeds) {
+ if (feeds[f] > 0)
+ newFollowers.push(f)
+ }
+ self.followers = newFollowers
+ })
+ },
+
+ updateBlockingUs: function(err, SSB) {
+ var self = this
+ var opts = {
+ start: this.feedId,
+ max: 0,
+ reverse: true
+ }
+ SSB.friends.hops(opts, (err, feeds) => {
+ var newBlocks = []
+ for(f in feeds) {
+ if (Math.round(feeds[f]) == -1)
+ newBlocks.push(f)
+ }
+ self.blockingUs = newBlocks
+ })
+ },
+
+ renderFollowsCallback: function (err, SSB) {
+ var self = this
+
+ self.isSelf = (SSB.id == this.feedId)
+
+ SSB.helpers.getGraphForFeed(self.feedId, (err, graph) => {
+ self.friends = graph.following
+ self.blocked = graph.blocking
+
+ SSB.friends.isFollowing({ source: SSB.id, dest: self.feedId }, (err, result) => {
+ self.following = result
+ })
+
+ SSB.friends.isBlocking({ source: SSB.id, dest: self.feedId }, (err, result) => {
+ self.blocking = result
+ })
+ })
+
+ this.updateFollowers(err, SSB)
+ this.updateBlockingUs(err, SSB)
+ },
+
+ renderProfileCallback: function (err, SSB) {
+ var self = this
+ const profile = SSB.db.getIndex("aboutSelf").getProfile(self.feedId)
+ if (profile.name)
+ self.name = profile.name
+
+ if (profile.imageURL) {
+ self.image = profile.imageURL
+ self.imageBlobId = profile.image
+ } else if (profile.image) {
+ SSB.blobs.localGet(profile.image, (err, url) => {
+ if (!err) {
+ self.image = url
+ self.imageBlobId = profile.image
+ }
+ })
+ }
+ },
+
+ renderProfile() {
+ var self = this
+
+ ssbSingleton.getSSBEventually(
+ -1,
+ () => { return self.componentStillLoaded },
+ (SSB) => { return SSB && SSB.db },
+ self.renderFollowsCallback
+ )
+
+ ssbSingleton.getSSBEventually(
+ -1,
+ () => { return self.componentStillLoaded },
+ (SSB) => {
+ if (!SSB || !SSB.db) return false
+
+ let profile = SSB.db.getIndex("aboutSelf").getProfile(self.feedId)
+ return Object.keys(profile).length > 0
+ },
+ self.renderProfileCallback
+ )
+ }
+ },
+
+ created: function () {
+ this.feedId = feedId
+ Object.assign(this.$data, initialState(this))
+ this.componentStillLoaded = true
+
+ this.renderProfile()
+ },
+
+ destroyed: function () {
+ this.componentStillLoaded = false
+ }
+ }
+}
diff --git a/ssb-profile-link.js b/ssb-profile-link.js
new file mode 100644
index 0000000..45c67cb
--- /dev/null
+++ b/ssb-profile-link.js
@@ -0,0 +1,110 @@
+const ssbSingleton = require('ssb-browser-core/ssb-singleton')
+
+Vue.component('ssb-profile-link', {
+ template: `
+
+
+
+
🚫
+
{{ name }}
+
+
`,
+
+ props: ['feed'],
+
+ data: function() {
+ return {
+ componentStillLoaded: false,
+ imgURL: '',
+ isBlocked: false,
+ name: ''
+ }
+ },
+
+ methods: {
+ renderProfile: function(profile) {
+ var self = this
+ ssbSingleton.getSSBEventually(
+ -1,
+ () => { return self.componentStillLoaded },
+ (SSB) => { return SSB },
+ (err, SSB) => {
+ self.renderProfileCallback(err, SSB, profile)
+ }
+ )
+ },
+
+ renderProfileCallback: function (err, SSB, existingProfile) {
+ const self = this
+ const profile = existingProfile || SSB.db.getIndex("aboutSelf").getProfile(self.feed)
+
+ // set a default image to be overridden if there is an actual avatar to show.
+ self.imgURL = "noavatar.svg"
+
+ if (self.feed == SSB.id)
+ self.name = 'You'
+ else
+ self.name = profile.name
+
+ if (profile.imageURL) self.imgURL = profile.imageURL
+ else if (profile.image) {
+ SSB.blobs.localProfileGet(profile.image, (err, url) => {
+ if (err) return console.error("failed to get img", err)
+
+ profile.imageURL = self.imgURL = url
+ })
+ }
+ },
+
+ openProfile: function() {
+ const profile = require('./profile')
+ new Vue(profile(this.feed)).$mount("#app")
+ },
+
+ loadBlocking: function (err, SSB) {
+ SSB.friends.isBlocking({ source: SSB.id, dest: self.feed }, (err, result) => {
+ if (!err) self.isBlocked = result
+ })
+ },
+
+ refresh: function() {
+ var self = this
+
+ // set a default image while we wait for an SSB.
+ self.imgURL = "noavatar.svg"
+
+ ssbSingleton.getSSBEventually(
+ -1,
+ () => { return self.componentStillLoaded },
+ (SSB) => { return SSB },
+ self.loadBlocking
+ )
+
+ ssbSingleton.getSSBEventually(
+ -1,
+ () => { return self.componentStillLoaded },
+ (SSB) => {
+ if (!SSB || !SSB.db) return false
+
+ let profile = SSB.db.getIndex("aboutSelf").getProfile(self.feed)
+ return Object.keys(profile).length > 0
+ },
+ self.renderProfileCallback)
+ }
+ },
+
+ created: function() {
+ this.componentStillLoaded = true
+ this.refresh()
+ },
+
+ destroyed: function() {
+ this.componentStillLoaded = false
+ },
+
+ watch: {
+ feed: function (oldValue, newValue) {
+ this.refresh()
+ }
+ }
+})