diff options
author | cel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519> | 2020-03-19 17:41:46 -0400 |
---|---|---|
committer | cel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519> | 2020-03-19 17:43:55 -0400 |
commit | f50d7806676b8d789e3cb387d1f52269749e960c (patch) | |
tree | f4202d6b77d7f959514e81c45715a7fc71a5f708 /lib/about.js | |
parent | 637e98b75e71e3ba16f2de5c98e2dea8f6ef62a8 (diff) | |
download | patchfoo-f50d7806676b8d789e3cb387d1f52269749e960c.tar.gz patchfoo-f50d7806676b8d789e3cb387d1f52269749e960c.zip |
Add pages to create, view, and edit Gatherings
Diffstat (limited to 'lib/about.js')
-rw-r--r-- | lib/about.js | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/lib/about.js b/lib/about.js index 1e41691..c905d61 100644 --- a/lib/about.js +++ b/lib/about.js @@ -81,6 +81,277 @@ function computeTopAbout(aboutByFeed) { } About.prototype.get = function (dest, cb) { + if (dest[0] === '@') return this.getSocial(dest, cb) + return this.getCausal(dest, cb) +} + +function copy(obj) { + if (obj === null || typeof obj !== 'object') return obj + var o = {} + for (var k in obj) o[k] = obj[k] + return o +} + +function premapAbout(msg) { + var value = { + about: {}, + mentions: {}, + branches: {}, + sources: {}, + ts: msg.ts + } + var c = msg.value.content + if (!c) return value + + if (c.branch) { + msg.branches.map(function (id) { + value.branches[id] = true + }) + } + + if (!c.about && c.root) return value + value.about = {} + var author = msg.value.author + var source = {id: msg.key, seq: msg.value.sequence} + for (var k in c) switch(k) { + case 'type': + case 'about': + case 'branch': + break + case 'recps': + // get recps from root message only + if (!c.root && !c.about) { + value.recps = {} + u.toLinkArray(c.recps).forEach(function (link) { + value.recps[link.link] = link + }) + } + break + case 'mentions': + value.mentions = {} + u.toLinkArray(c.mentions).map(function (link) { + value.mentions[link.link] = link + }) + break + case 'image': + var link = u.toLink(c.image) + if (!link) break + value.about.image = link.link + value.about.imageLink = link + var sources = value.sources.image || (value.sources.image = []) + sources[author] = source + break + case 'attendee': + var attendee = u.linkDest(c.attendee) + if (attendee && attendee === author) { + // TODO: allow users adding other users as attendees? + var attendeeLink = copy(u.toLink(c.attendee)) + attendeeLink.source = msg.key + value.attending = {} + value.attending[attendee] = attendeeLink.remove ? null : attendeeLink } + break + default: + // TODO: handle arrays + value.about[k] = c[k] + var sources = value.sources[k] || (value.sources[k] = {}) + sources[author] = source + } + return value +} + +function reduceAbout(values, lastValue) { + var newValue = { + about: {}, + mentions: {}, + branches: {}, + sources: {} + } + values.sort(compareByTs).concat(lastValue).forEach(function (value) { + if (!value) return + if (value.ts) { + if (!newValue.ts || value.ts > newValue.ts) { + newValue.ts = value.ts + } + } + if (value.attending) { + var attending = newValue.attending || (newValue.attending = {}) + for (var k in value.attending) { + attending[k] = value.attending[k] + } + } + if (value.mentions) for (var k in value.mentions) { + newValue.mentions[k] = value.mentions[k] + } + if (value.branches) for (var k in value.branches) { + newValue.branches[k] = value.branches[k] + } + if (value.recps) { + // note: zero recps is still private. truthy recps indicates private + var recps = newValue.recps || (newValue.recps = {}) + for (var k in value.recps) { + newValue.recps[k] = value.recps[k] + } + } + if (value.about) for (var k in value.about) { + // TODO: use merge heuristics + newValue.about[k] = value.about[k] + if (lastValue && lastValue.about[k]) { + if (value === lastValue) { + newValue.sources[k] = lastValue.sources[k] + } else { + // message setting a property resets the property's sources from branches + } + } else { + var newSources = newValue.sources[k] || (newValue.sources[k] = {}) + var sources = value.sources[k] + for (var feed in sources) { + if (newSources[feed] && newSources[feed].seq > sources[feed].seq) { + // assume causal order in user's own feed. + // this condition shouldn't be reached if messages are in feed order + console.error('skip', k, feed, sources[feed].id, newSources[feed].id) + continue + } + newSources[feed] = sources[feed] + } + } + } + }) + return newValue +} + +function postmapAbout(value) { + var about = { + ts: value.ts, + _sources: {} + } + for (var k in value.sources) { + var propSources = about._sources[k] = [] + for (var feed in value.sources[k]) { + propSources.push(value.sources[k][feed].id) + } + } + if (value.mentions) { + about.mentions = [] + for (var k in value.mentions) { + about.mentions.push(value.mentions[k]) + } + } + if (value.attending) { + about.attendee = [] + for (var k in value.attending) { + var link = value.attending[k] + if (link) about.attendee.push(link) + } + } + if (value.branches) about.branch = Object.keys(value.branches) + if (value.recps) { + about.recps = [] + for (var k in value.recps) { + about.recps.push(value.recps[k]) + } + } + if (value.about) for (var k in value.about) { + about[k] = value.about[k] + } + return about +} + +function compareByTs(a, b) { + return a.ts - b.ts +} + +About.prototype.getCausal = function (dest, cb) { + var self = this + var backlinks = {} + var seen = {} + var queue = [] + var aboutAtMsgs = {} + var now = Date.now() + function enqueue(msg) { + if (!seen[msg.key]) { + seen[msg.key] = true + queue.push(msg) + } + } + function isMsgIdDone(id) { + return !!aboutAtMsgs[id] + } + function isMsgReady(msg) { + return msg.branches.every(isMsgIdDone) + } + function dequeue() { + var msg = queue.filter(isMsgReady).sort(compareByTs)[0] + if (!msg) return console.error('thread error'), queue.shift() + var i = queue.indexOf(msg) + queue.splice(i, 1) + return msg + } + pull( + cat([ + dest[0] === '%' && self.app.pullGetMsg(dest), + self.app.sbot.links({ + rel: 'about', + dest: dest, + values: true, + private: true, + meta: false + }), + self.app.sbot.links({ + rel: 'root', + dest: dest, + values: true, + private: true, + meta: false + }) + ]), + pull.unique('key'), + self.app.unboxMessages(), + pull.drain(function (msg) { + var c = msg.value.content + if (!c) return + msg = { + key: msg.key, + ts: Math.min(now, + Number(msg.timestamp) || Infinity, + Number(msg.value.timestamp || c.timestamp) || Infinity), + value: msg.value, + branches: u.toLinkArray(c.branch).map(u.linkDest) + } + if (!msg.branches.length) { + enqueue(msg) + } else msg.branches.forEach(function (id) { + var linksToMsg = backlinks[id] || (backlinks[id] = []) + linksToMsg.push(msg) + }) + }, function (err) { + if (err) return cb(err) + while (queue.length) { + var msg = dequeue() + var abouts = msg.branches.map(function (id) { return aboutAtMsgs[id] }) + aboutAtMsgs[msg.key] = reduceAbout(abouts, premapAbout(msg)) + var linksToMsg = backlinks[msg.key] + if (linksToMsg) linksToMsg.forEach(enqueue) + } + var headAbouts = [] + var headIds = [] + for (var id in aboutAtMsgs) { + if (backlinks[id]) continue + headIds.push(id) + headAbouts.push(aboutAtMsgs[id]) + } + headAbouts.sort(compareByTs) + var about = postmapAbout(reduceAbout(headAbouts)) + about.branch = headIds + cb(null, about) + }) + ) +} + +function getValue(obj) { + return obj.value +} + +About.prototype.getSocial = function (dest, cb) { var self = this var myAbout = [] var aboutByFeed = {} |