Commit f8ac0ce23d5685c236e76ded42ded5572d52f53f
First pass at tags
Josiah Witt committed on 1/15/2018, 1:22:38 AMParent: c7c7d67141ccbc5ffab412cec29c91853082a1b4
Files changed
package-lock.json | changed |
save/async/save.js | deleted |
save/obs/save.js | deleted |
save/obs/struct.js | deleted |
save/obs/tags.js | deleted |
save/pull/find.js | deleted |
tag/async/apply.js | added |
tag/async/create.js | added |
tag/async/name.js | added |
tag/obs/all.js | added |
tag/obs/tagged.js | added |
tag/pull/find.js | added |
package-lock.json | ||
---|---|---|
@@ -531,8 +531,18 @@ | ||
531 | 531 … | "browser-split": "0.0.1", |
532 | 532 … | "xtend": "4.0.1" |
533 | 533 … | } |
534 | 534 … | }, |
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 … | + }, | |
535 | 545 … | "number-is-nan": { |
536 | 546 … | "version": "1.0.1", |
537 | 547 … | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", |
538 | 548 … | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" |
@@ -574,8 +584,13 @@ | ||
574 | 584 … | "version": "0.2.2", |
575 | 585 … | "resolved": "https://registry.npmjs.org/pull-defer/-/pull-defer-0.2.2.tgz", |
576 | 586 … | "integrity": "sha1-CIew/7MK8ypW2+z6csFnInHwexM=" |
577 | 587 … | }, |
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 … | + }, | |
578 | 593 … | "pull-stream": { |
579 | 594 … | "version": "3.6.1", |
580 | 595 … | "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.6.1.tgz", |
581 | 596 … | "integrity": "sha1-xcKuSlEkbv7rzGXAQSo9clqSzgA=" |
save/async/save.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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