git ssb

0+

Josiah / scuttle-tag



Commit f8ac0ce23d5685c236e76ded42ded5572d52f53f

First pass at tags

Josiah Witt committed on 1/15/2018, 1:22:38 AM
Parent: c7c7d67141ccbc5ffab412cec29c91853082a1b4

Files changed

package-lock.jsonchanged
save/async/save.jsdeleted
save/obs/save.jsdeleted
save/obs/struct.jsdeleted
save/obs/tags.jsdeleted
save/pull/find.jsdeleted
tag/async/apply.jsadded
tag/async/create.jsadded
tag/async/name.jsadded
tag/obs/all.jsadded
tag/obs/tagged.jsadded
tag/pull/find.jsadded
package-lock.jsonView
@@ -531,8 +531,18 @@
531531 "browser-split": "0.0.1",
532532 "xtend": "4.0.1"
533533 }
534534 },
535 + "mutant-pull-reduce": {
536 + "version": "1.1.0",
537 + "resolved": "https://registry.npmjs.org/mutant-pull-reduce/-/mutant-pull-reduce-1.1.0.tgz",
538 + "integrity": "sha1-lvdwJ7QABhNkrL8mM74ugtVEDmo=",
539 + "requires": {
540 + "mutant": "3.22.1",
541 + "pull-pause": "0.0.0",
542 + "pull-stream": "3.6.1"
543 + }
544 + },
535545 "number-is-nan": {
536546 "version": "1.0.1",
537547 "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
538548 "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
@@ -574,8 +584,13 @@
574584 "version": "0.2.2",
575585 "resolved": "https://registry.npmjs.org/pull-defer/-/pull-defer-0.2.2.tgz",
576586 "integrity": "sha1-CIew/7MK8ypW2+z6csFnInHwexM="
577587 },
588 + "pull-pause": {
589 + "version": "0.0.0",
590 + "resolved": "https://registry.npmjs.org/pull-pause/-/pull-pause-0.0.0.tgz",
591 + "integrity": "sha1-EBpijXF+Gd+/mADp3sjyXTBGGWk="
592 + },
578593 "pull-stream": {
579594 "version": "3.6.1",
580595 "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.6.1.tgz",
581596 "integrity": "sha1-xcKuSlEkbv7rzGXAQSo9clqSzgA="
save/async/save.jsView
@@ -1,25 +1,0 @@
1-const nest = require('depnest')
2-
3-exports.gives = nest('save.async.save')
4-
5-exports.needs = nest({
6- 'sbot.async.publish': 'first',
7- 'keys.sync.id': 'first'
8-})
9-
10-exports.create = function(api) {
11- return nest('save.async.save', function(data, cb) {
12- const { messageId, notes, tags } = data
13- var recps = data.recps
14- if (recps && recps.length === 0) {
15- recps = null
16- }
17- api.sbot.async.publish({
18- type: 'about',
19- about: messageId,
20- recps,
21- notes,
22- tags
23- }, cb)
24- })
25-}
save/obs/save.jsView
@@ -1,28 +1,0 @@
1-const nest = require('depnest')
2-const pull = require('pull-stream')
3-const ref = require('ssb-ref')
4-const { computed } = require('mutant')
5-
6-exports.needs = nest({
7- 'about.obs.latestValue': 'first',
8- 'about.obs.valueFrom': 'first',
9- 'save.obs.struct': 'first'
10-})
11-
12-exports.gives = nest('save.obs.save')
13-
14-exports.create = function(api) {
15- return nest('save.obs.save', function(messageId, id) {
16- if (!ref.isLink(messageId)) throw new Error('an id must be specified')
17-
18- const { latestValue, valueFrom } = api.about.obs
19-
20- const save = api.save.obs.struct({
21- notes: latestValue(messageId, 'notes'),
22- tags: valueFrom(messageId, 'tags', id),
23- recps: latestValue(messageId, 'recps')
24- })
25-
26- return save
27- })
28-}
save/obs/struct.jsView
@@ -1,36 +1,0 @@
1-const nest = require('depnest')
2-const { Value, Set, Struct, forEachPair } = require('mutant')
3-
4-exports.needs = nest({
5- 'save.async.save': 'first'
6-})
7-
8-exports.gives = nest('save.obs.struct')
9-
10-exports.create = function(api) {
11- return nest('save.obs.struct', function(opts = {}) {
12- const struct = Struct({
13- notes: Value(''),
14- tags: Set([]),
15- recps: Set([])
16- })
17-
18- Object.keys(opts).forEach(k => {
19- if (!opts[k]) return
20-
21- if (typeof opts[k] === 'function') struct[k] = opts[k]
22- else struct[k].set(opts[k])
23- })
24-
25- struct.save = id => {
26- api.save.async.save({
27- recps: struct.recps(),
28- messageId: id,
29- notes: struct.notes(),
30- tags: struct.tags()
31- }, console.log)
32- }
33-
34- return struct
35- })
36-}
save/obs/tags.jsView
@@ -1,112 +1,0 @@
1-var {Value, computed} = require('mutant')
2-var pull = require('pull-stream')
3-var nest = require('depnest')
4-var ref = require('ssb-ref')
5-
6-exports.needs = nest({
7- 'sbot.pull.stream': 'first',
8- 'keys.sync.id': 'first'
9-})
10-
11-exports.gives = nest({
12- 'save.obs': [
13- 'taggedMessages',
14- 'tagsFrom'
15- ]
16-})
17-
18-exports.create = function (api) {
19- var syncValue = Value(false)
20- var sync = computed(syncValue, x => x)
21- var cache = null
22-
23- return nest({
24- 'save.obs': {
25- taggedMessages,
26- tagsFrom
27- }
28- })
29-
30- function taggedMessages (author, key) {
31- if (!ref.isLink(author)) throw new Error('Requires an ssb ref!')
32- return withSync(computed([get(author), key], getTaggedMessages))
33- }
34-
35- function tagsFrom (author) {
36- if (!ref.isLink(author)) throw new Error('Requires an ssb ref!')
37- return withSync(computed([get(author)], getTagsFrom))
38- }
39-
40- function withSync (obs) {
41- obs.sync = sync
42- return obs
43- }
44-
45- function get (author) {
46- if (!ref.isLink(author)) throw new Error('Requires an ssb ref!')
47- load()
48- if (!cache[author]) {
49- cache[author] = Value({})
50- }
51- return cache[author]
52- }
53-
54- function load () {
55- if (!cache) {
56- cache = {}
57- pull(
58- api.sbot.pull.stream(sbot => sbot.about.tagsStream({ live: true })),
59- pull.drain(item => {
60- for (var author in item) {
61- var state = get(author)
62- var lastState = state()
63- var tags = item[author]['tags']
64- var changes = false
65- for (var tag in tags) {
66- var msgsForTag = lastState[tag] = lastState[tag] || {}
67- for (var msg in tags[tag]) {
68- var timestamp = tags[tag][msg]
69- if (!msgsForTag[msg] || timestamp > msgsForTag[msg]) {
70- msgsForTag[msg] = timestamp
71- changed = true
72- }
73- }
74- }
75- if (changed) {
76- state.set(lastState)
77- }
78- }
79- if (!syncValue()) {
80- syncValue.set(true)
81- }
82- })
83- )
84- }
85- }
86-}
87-
88-function getTaggedMessages(lookup, key) {
89- const messages = [];
90- for(const msg in lookup[key]) {
91- if (lookup[key][msg]) {
92- messages.push(msg)
93- }
94- }
95- return messages
96-}
97-
98-function getTagsFrom(lookup) {
99- const tags = []
100- for (const tag in lookup) {
101- var valid = false
102- for (const msg in lookup[tag]) {
103- if (lookup[tag][msg]) {
104- valid = true
105- }
106- }
107- if (valid) {
108- tags.push(tag)
109- }
110- }
111- return tags
112-}
save/pull/find.jsView
@@ -1,30 +1,0 @@
1-const nest = require('depnest')
2-const defer = require('pull-defer')
3-const pull = require('pull-stream')
4-const onceTrue = require('mutant/once-true')
5-
6-exports.gives = nest('save.pull.find')
7-
8-exports.needs = nest({
9- 'sbot.obs.connection': 'first'
10-})
11-
12-exports.create = function(api) {
13- return nest({ 'save.pull.find': find })
14-
15- function find(opts) {
16- return StreamWhenConnected(api.sbot.obs.connection, (sbot) => {
17- if (!sbot.about || !sbot.about.tagsStream) return pull.empty()
18- return sbot.about.tagsStream(opts)
19- })
20- }
21-}
22-
23-// COPIED from patchcore 'feed.pull.private'
24-function StreamWhenConnected (connection, fn) {
25- var stream = defer.source()
26- onceTrue(connection, function (connection) {
27- stream.resolve(fn(connection))
28- })
29- return stream
30-}
tag/async/apply.jsView
@@ -1,0 +1,32 @@
1 +const nest = require('depnest')
2 +
3 +exports.gives = nest('tag.async.apply')
4 +
5 +exports.needs = nest({
6 + 'sbot.async.publish': 'first'
7 +})
8 +
9 +exports.create = function(api) {
10 + return nest('tag.async.apply', function(data, cb) {
11 + const { tagged, message, recps, tag } = data
12 + if (recps && recps.length === 0) {
13 + api.sbot.async.publish({
14 + type: 'tag',
15 + tagged,
16 + message,
17 + root: tag,
18 + branch: tag,
19 + recps: data.recps,
20 + private: true
21 + }, cb)
22 + } else {
23 + api.sbot.async.publish({
24 + type: 'tag',
25 + tagged,
26 + message,
27 + root: tag,
28 + branch: tag
29 + }, cb)
30 + }
31 + })
32 +}
tag/async/create.jsView
@@ -1,0 +1,21 @@
1 +const nest = require('depnest')
2 +
3 +exports.gives = nest('tag.async.create')
4 +
5 +exports.needs = nest({
6 + 'sbot.async.publish': 'first'
7 +})
8 +
9 +exports.create = function(api) {
10 + return nest('tag.async.create', function(recps, cb) {
11 + if (recps && recps.length === 0) {
12 + api.sbot.async.publish({
13 + type: 'tag',
14 + recps: data.recps,
15 + private: true
16 + }, cb)
17 + } else {
18 + api.sbot.async.publish({ type: 'tag' }, cb)
19 + }
20 + })
21 +}
tag/async/name.jsView
@@ -1,0 +1,13 @@
1 +const nest = require('depnest')
2 +
3 +exports.gives = nest('tag.async.name')
4 +
5 +exports.needs = nest({
6 + 'sbot.async.publish': 'first'
7 +})
8 +
9 +exports.create = function (api) {
10 + return nest('tag.async.name', function ({ name, tag }, cb) {
11 + api.sbot.async.publish({ type: 'about', about: tag, name }, cb)
12 + })
13 +}
tag/obs/all.jsView
@@ -1,0 +1,45 @@
1 +var nest = require('depnest')
2 +var { Dict, Array } = require('mutant')
3 +var MutantPullReduce = require('mutant-pull-reduce')
4 +
5 +exports.needs = nest({
6 + 'sbot.pull.stream': 'first'
7 +})
8 +
9 +exports.gives = nest('tags.obs.all', true)
10 +
11 +exports.create = function (api) {
12 + var cached = null
13 + return nest('tags.obs.all', () => {
14 + if (!cached) {
15 + var stream = api.sbot.pull.stream(s => s.tags.stream({live: true}))
16 + cached = MutantPullReduce(stream, (result, data) => {
17 + if (data.tags) {
18 + // handle initial state
19 + for (var tag in data.tags) {
20 + var messages = new Dict()
21 + for (var message in data.tags[tag]) {
22 + if (data.tags[tag][message].tagged) {
23 + messages.put(message, data.tags[tag][message])
24 + }
25 + }
26 + result.put(tag, messages)
27 + }
28 + } else if (data.tag) {
29 + // handle realtime changes
30 + var { author, tag, message, tagged, timestamp } = data
31 + if (!result.get(tag)) result.put(tag, new Dict())
32 + if (tagged && (!result.get(tag).get(message) || timestamp > result.get(tag).get(message).timestamp)) {
33 + result.get(tag).put(message, { timestamp, tagged })
34 + } else if (!tagged && result.get(tag).get(message)) {
35 + result.get(tag).delete(message)
36 + }
37 + }
38 + return result
39 + }, {
40 + startValue: new Dict()
41 + })
42 + }
43 + return cached
44 + })
45 +}
tag/obs/tagged.jsView
@@ -1,0 +1,121 @@
1 +var { Value, computed } = require('mutant')
2 +var pull = require('pull-stream')
3 +var nest = require('depnest')
4 +var ref = require('ssb-ref')
5 +
6 +exports.needs = nest({
7 + 'sbot.pull.stream': 'first'
8 +})
9 +
10 +exports.gives = nest({
11 + 'tag.obs': [
12 + 'taggedMessages',
13 + 'messagesTagged'
14 + ]
15 +})
16 +
17 +exports.create = function(api) {
18 + var tagsCache = null
19 + var messagesCache = null
20 + var sync = Value(false)
21 +
22 + return nest({
23 + 'tag.obs': {
24 + taggedMessages,
25 + messageTags
26 + }
27 + })
28 +
29 + function taggedMessages(author, tagId) {
30 + if (!ref.isLink(author) || !ref.isLink(tagId)) throw new Error('Requires an ssb ref!')
31 + return withSync(computed([get(author, tagsCache), tagId], getTaggedMessages))
32 + }
33 +
34 + function messageTags(msgId, tagId) {
35 + if (!ref.isLink(tagId) || !ref.isLink(msgId)) throw new Error('Requires an ssb ref!')
36 + return withSync(computed([get(msgId, messagesCache), tagId], getMessageTags))
37 + }
38 +
39 + function withSync(obs) {
40 + obs.sync = sync
41 + return obs
42 + }
43 +
44 + function get(id, lookup) {
45 + if (!ref.isLink(id)) throw new Error('Requires an ssb ref!')
46 + load()
47 + if (!lookup[id]) {
48 + lookup[id] = Value({})
49 + }
50 + return lookup[id]
51 + }
52 +
53 + function load() {
54 + if (!tagsCache) {
55 + tagsCache = {}
56 + messagesCache = {}
57 + pull(
58 + api.sbot.pull.stream(sbot => sbot.tags.stream({ live: true })),
59 + pull.drain(item => {
60 + if (!sync()) {
61 + // populate observable cache
62 + for (const author in item.tags) {
63 + update(author, item.tags[author], tagsCache)
64 + }
65 + for (const message in item.messages) {
66 + update(message, item.messages[message], messagesCache)
67 + }
68 + if (!sync()) {
69 + sync.set(true)
70 + }
71 + } else if (item && ref.isLink(item.tag) && ref.isLink(item.author) && ref.isLink(item.message)) {
72 + // handle realtime updates
73 + const { tag, author, message, tagged, timestamp } = item
74 + update(author, { [tag]: { [message]: { timestamp, tagged } } }, tagsCache)
75 + update(message, { [tag]: { [author]: { timestamp, tagged } } }, messagesCache)
76 + }
77 + })
78 + )
79 + }
80 + }
81 +}
82 +
83 +function update(id, values, lookup) {
84 + const state = get(id, lookup)
85 + const lastState = state()
86 + var changed = false
87 +
88 + for (const tag in values) {
89 + for (const key in values[tag]) {
90 + if (values[tag][key] !== lastState[tag][key]) {
91 + lastState[tag][key] = values[tag][key]
92 + changed = true
93 + }
94 + }
95 + }
96 +
97 + if (changed) {
98 + state.set(lastState)
99 + }
100 +}
101 +
102 +function getTaggedMessages(lookup, key) {
103 + const messages = []
104 + for (const msg in lookup[key]) {
105 + if (lookup[key][msg].tagged) {
106 + messages.push(msg)
107 + }
108 + }
109 + return messages
110 +}
111 +
112 +function getMessageTags(lookup, tagId) {
113 + const tags = {}
114 + for (const author in lookup[tagId]) {
115 + if (lookup[tagId][author].tagged) {
116 + if (!tags[tagId]) tags[tagId] = {}
117 + tags[tagId][author] = lookup[tagId][author].timestamp
118 + }
119 + }
120 + return tags
121 +}
tag/pull/find.jsView
@@ -1,0 +1,30 @@
1 +const nest = require('depnest')
2 +const defer = require('pull-defer')
3 +const pull = require('pull-stream')
4 +const onceTrue = require('mutant/once-true')
5 +
6 +exports.gives = nest('tag.pull.find')
7 +
8 +exports.needs = nest({
9 + 'sbot.obs.connection': 'first'
10 +})
11 +
12 +exports.create = function(api) {
13 + return nest({ 'tag.pull.find': find })
14 +
15 + function find(opts) {
16 + return StreamWhenConnected(api.sbot.obs.connection, (sbot) => {
17 + if (!sbot.tags || !sbot.tags.stream) return pull.empty()
18 + return sbot.tags.stream(opts)
19 + })
20 + }
21 +}
22 +
23 +// COPIED from patchcore 'feed.pull.private'
24 +function StreamWhenConnected (connection, fn) {
25 + var stream = defer.source()
26 + onceTrue(connection, function (connection) {
27 + stream.resolve(fn(connection))
28 + })
29 + return stream
30 +}

Built with git-ssb-web