git ssb

7+

dinoworm 🐛 / patchcore



Commit c18b233c6d62741496978cd8dc1f34a093946f8b

first pass

Michael Williams committed on 2/13/2017, 12:37:05 PM
Parent: 8261134eb7daa7f16ad7f83ebb1549e60b37b70d

Files changed

config.jschanged
keys.jschanged
package.jsonchanged
sbot.jschanged
components/markdown.jsdeleted
components/message/author.jsdeleted
components/message/backlinks.jsdeleted
components/message/link.jsdeleted
components/message/name.jsdeleted
components/message/timestamp.jsdeleted
components/render_feed.jsdeleted
about/obs.jsadded
blob.jsadded
helpers/blob_url.jsdeleted
helpers/emoji.jsdeleted
emoji.jsadded
feed/html.jsadded
feed/pull/private.jsadded
feed/pull/public.jsadded
observables/about.jsdeleted
observables/timeAgo.jsdeleted
lib/timeAgo.jsadded
plugs/message_action/reply.jsdeleted
plugs/message_decorate/data-id.jsdeleted
plugs/message_layout/default.jsdeleted
plugs/message_layout/mini.jsdeleted
plugs/message_meta/channel.jsdeleted
plugs/message_render/post.jsdeleted
plugs/message_render/vote.jsdeleted
plugs/message_render/zz_fallback.jsdeleted
message/html/action/reply.jsadded
message/html/author.jsadded
message/html/backlinks.jsadded
message/html/decorate/data-id.jsadded
message/html/layout/default.jsadded
message/html/layout/mini.jsadded
message/html/link.jsadded
message/html/markdown.jsadded
message/html/meta/channel.jsadded
message/html/name.jsadded
message/html/render/post.jsadded
message/html/render/vote.jsadded
message/html/render/zz_fallback.jsadded
message/html/timestamp.jsadded
streams/private.jsdeleted
streams/public.jsdeleted
config.jsView
@@ -1,14 +1,15 @@
11 const Config = require('ssb-config/inject')
2 +const nest = require('depnest')
23
3-module.exports = {
4- gives: 'config',
5- create: () => {
4 +exports.gives = nest('config.sync.load')
5 +exports.create = (api) => {
6 + return nest('config.sync.load', () => {
67 var config
78 return () => {
89 if (!config) {
910 config = Config(process.env.ssb_appname)
1011 }
1112 return config
1213 }
13- }
14 + })
1415 }
keys.jsView
@@ -1,18 +1,17 @@
11 const Path = require('path')
22 const Keys = require('ssb-keys')
3 +const nest = require('depnest')
34
4-module.exports = {
5- needs: { config: 'first' },
6- gives: 'keys',
7- create: (api) => {
5 +exports.needs = nest('config.sync.load', 'first')
6 +exports.gives = nest('keys.sync.load')
7 +exports.create = (api) => {
8 + return nest('keys.sync.load', () => {
89 var keys
9- return () => {
10- if (!keys) {
11- const config = api.config()
12- const keyPath = Path.join(config.path, 'secret')
13- keys = Keys.loadOrCreateSync(keyPath)
14- }
15- return keys
10 + if (!keys) {
11 + const config = api.config.sync.load()
12 + const keyPath = Path.join(config.path, 'secret')
13 + keys = Keys.loadOrCreateSync(keyPath)
1614 }
17- }
15 + return keys
16 + })
1817 }
package.jsonView
@@ -5,9 +5,9 @@
55 "main": "index.js",
66 "scripts": {
77 "start": "electro example",
88 "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell",
9- "test": "tape test"
9 + "test": "standard"
1010 },
1111 "repository": {
1212 "type": "git",
1313 "url": "git+https://github.com/ssbc/patchcore.git"
@@ -49,7 +49,8 @@
4949 },
5050 "devDependencies": {
5151 "depject": "github:dominictarr/depject#reduce-with-context",
5252 "electro": "^2.0.3",
53- "insert-css": "^2.0.0"
53 + "insert-css": "^2.0.0",
54 + "standard": "^8.6.0"
5455 }
5556 }
sbot.jsView
@@ -5,29 +5,35 @@
55 var createFeed = require('ssb-feed')
66
77 var cache = CACHE = {}
88
9-exports.needs = {
10- config: 'first',
11- keys: 'first',
12- connection_status: 'map'
13-}
9 +exports.needs = nest({
10 + 'config.sync.load': 'first',
11 + 'keys.sync.load': 'first',
12 + 'sbot.obs.connectionStatus': 'first'
13 +})
1414
1515 exports.gives = {
16- sbot_log: true,
17- sbot_get: true,
18- sbot_user_feed: true,
19- sbot_query: true,
20- sbot_publish: true,
21- connection_status: true
16 + sbot: {
17 + pull: {
18 + log: true,
19 + get: true,
20 + userFeed: true,
21 + query: true,
22 + publish: true,
23 + },
24 + obs: {
25 + connectionStatus: true
26 + }
27 + }
2228 }
2329
2430 exports.create = function (api) {
25- const config = api.config()
26- const keys = api.keys()
31 + const config = api.config.sync.load()
32 + const keys = api.keys.sync.load()
2733
2834 var sbot = null
29- var connection_status = []
35 + var connectionStatus = Value()
3036
3137 var rec = {
3238 sync: () => {},
3339 async: () => {},
@@ -35,18 +41,19 @@
3541 }
3642
3743 var rec = Reconnect(function (isConn) {
3844 function notify (value) {
39- isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) })
45 + isConn(value); connectionStatus.set(value)
4046 }
4147
4248 createClient(keys, {
4349 manifest: require('./manifest.json'),
4450 remote: config.remote,
4551 caps: config.caps
4652 }, function (err, _sbot) {
47- if(err)
53 + if (err) {
4854 return notify(err)
55 + }
4956
5057 sbot = _sbot
5158 sbot.on('closed', function () {
5259 sbot = null
@@ -68,50 +75,60 @@
6875
6976 var feed = createFeed(internal, keys, {remote: true})
7077
7178 return {
72- connection_status: () => connection_status,
73- sbot_query: rec.source(query => {
74- return sbot.query.read(query)
75- }),
76- sbot_user_feed: rec.source(opts => {
77- return sbot.createUserStream(opts)
78- }),
79- sbot_get: rec.async(function (key, cb) {
80- if('function' !== typeof cb)
81- throw new Error('cb must be function')
82- if(CACHE[key]) cb(null, CACHE[key])
83- else sbot.get(key, function (err, value) {
84- if(err) return cb(err)
85- cb(null, CACHE[key] = value)
86- })
87- }),
88- sbot_publish: rec.async((content, cb) => {
89- if(content.recps)
90- content = ssbKeys.box(content, content.recps.map(e => {
91- return ref.isFeed(e) ? e : e.link
92- }))
93- else if(content.mentions)
94- content.mentions.forEach(mention => {
95- if(ref.isBlob(mention.link)) {
96- sbot.blobs.push(mention.link, err => {
97- if(err) console.error(err)
79 + sbot: {
80 + pull: {
81 + query: rec.source(query => {
82 + return sbot.query.read(query)
83 + }),
84 + userFeed: rec.source(opts => {
85 + return sbot.createUserStream(opts)
86 + }),
87 + get: rec.async(function (key, cb) {
88 + if (typeof cb !== 'function') {
89 + throw new Error('cb must be function')
90 + }
91 + if (CACHE[key]) cb(null, CACHE[key])
92 + else {
93 + sbot.get(key, function (err, value) {
94 + if (err) return cb(err)
95 + cb(null, CACHE[key] = value)
9896 })
9997 }
100- })
98 + }),
99 + publish: rec.async((content, cb) => {
100 + if (content.recps) {
101 + content = ssbKeys.box(content, content.recps.map(e => {
102 + return ref.isFeed(e) ? e : e.link
103 + }))
104 + } else if (content.mentions) {
105 + content.mentions.forEach(mention => {
106 + if (ref.isBlob(mention.link)) {
107 + sbot.blobs.push(mention.link, err => {
108 + if (err) console.error(err)
109 + })
110 + }
111 + })
112 + }
101113
102- feed.add(content, (err, msg) => {
103- if(err) console.error(err)
104- else if(!cb) console.log(msg)
105- cb && cb(err, msg)
106- })
107- }),
108- sbot_log: rec.source(opts => {
109- return pull(
110- sbot.createLogStream(opts),
111- pull.through(e => {
112- CACHE[e.key] = CACHE[e.key] || e.value
114 + feed.add(content, (err, msg) => {
115 + if (err) console.error(err)
116 + else if (!cb) console.log(msg)
117 + cb && cb(err, msg)
118 + })
119 + }),
120 + log: rec.source(opts => {
121 + return pull(
122 + sbot.createLogStream(opts),
123 + pull.through(e => {
124 + CACHE[e.key] = CACHE[e.key] || e.value
125 + })
126 + )
113127 })
114- )
115- })
128 + },
129 + obs: {
130 + connectionStatus: (listener) => connectionStatus(listener)
131 + }
132 + }
116133 }
117134 }
components/markdown.jsView
@@ -1,55 +1,0 @@
1-const renderer = require('ssb-markdown')
2-const fs = require('fs')
3-const h = require('mutant/h')
4-const ref = require('ssb-ref')
5-
6-exports.needs = {
7- blob_url: 'first',
8- emoji_url: 'first'
9-}
10-
11-exports.gives = {
12- markdown: true,
13- mcss: true
14-}
15-
16-exports.create = function (api) {
17- return {
18- markdown,
19- mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
20- }
21-
22- function markdown (content) {
23- if('string' === typeof content)
24- content = {text: content}
25- //handle patchwork style mentions.
26- var mentions = {}
27- if(Array.isArray(content.mentions))
28- content.mentions.forEach(function (link) {
29- if(link.name) mentions["@"+link.name] = link.link
30- })
31-
32- var md = h('Markdown')
33- md.innerHTML = renderer.block(content.text, {
34- emoji: renderEmoji,
35- toUrl: (id) => {
36- if(ref.isBlob(id)) return api.blob_url(id)
37- return '#'+(mentions[id]?mentions[id]:id)
38- },
39- imageLink: (id) => '#' + id
40- })
41-
42- return md
43-
44- }
45-
46- function renderEmoji(emoji) {
47- var url = api.emoji_url(emoji)
48- if (!url) return ':' + emoji + ':'
49- return '<img src="' + encodeURI(url) + '"'
50- + ' alt=":' + escape(emoji) + ':"'
51- + ' title=":' + escape(emoji) + ':"'
52- + ' class="emoji">'
53- }
54-
55-}
components/message/author.jsView
@@ -1,21 +1,0 @@
1-const h = require('mutant/h')
2-
3-exports.needs = {
4- obs_about_name: 'first'
5-}
6-
7-exports.gives = {
8- message_author: true
9-}
10-
11-exports.create = function (api) {
12- return {
13- message_author
14- }
15-
16- function message_author (msg) {
17- return h('div', {title: msg.value.author}, [
18- '@', api.obs_about_name(msg.value.author)
19- ])
20- }
21-}
components/message/backlinks.jsView
@@ -1,40 +1,0 @@
1-const fs = require('fs')
2-const h = require('mutant/h')
3-
4-exports.needs = {
5- message_name: 'first'
6-}
7-
8-exports.gives = 'message_backlinks'
9-
10-exports.create = function (api) {
11- return function (msg) {
12- var links = []
13- for(var k in CACHE) {
14- var _msg = CACHE[k]
15- var mentions = _msg.content.mentions
16-
17- if(Array.isArray(mentions)) {
18- for(var i = 0; i < mentions.length; i++)
19- if(mentions[i].link == msg.key)
20- links.push(k)
21- }
22- }
23-
24- if (links.length === 0) return null
25-
26- var hrefList = h('ul')
27- links.forEach(link => {
28- api.message_name(link, (err, name) => {
29- if (err) throw err
30- hrefList.appendChild(h('li', [
31- h('a -backlink', { href: `#${link}` }, name)
32- ]))
33- })
34- })
35- return h('MessageBacklinks', [
36- h('header', 'backlinks:'),
37- hrefList
38- ])
39- }
40-}
components/message/link.jsView
@@ -1,27 +1,0 @@
1-var h = require('mutant/h')
2-var ref = require('ssb-ref')
3-
4-exports.needs = {
5- message_name: 'first'
6-}
7-
8-exports.gives = 'message_link'
9-
10-exports.create = function (api) {
11-
12- return function (id) {
13-
14- if('string' !== typeof id)
15- throw new Error('link must be to message id')
16-
17- var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...')
18-
19- if(ref.isMsg(id))
20- api.message_name(id, function (err, name) {
21- if(err) console.error(err)
22- else link.textContent = name
23- })
24-
25- return link
26- }
27-}
components/message/name.jsView
@@ -1,25 +1,0 @@
1-
2-function title (s) {
3- var m = /^\n*([^\n]{0,40})/.exec(s)
4- return m && (m[1].length == 40 ? m[1]+'...' : m[1])
5-}
6-
7-exports.needs = { sbot_get: 'first' }
8-exports.gives = 'message_name'
9-
10-//TODO: rewrite as observable?
11-
12-exports.create = function (api) {
13- return function (id, cb) {
14- api.sbot_get(id, function (err, value) {
15- if(err && err.name == 'NotFoundError')
16- return cb(null, id.substring(0, 10)+'...(missing)')
17- if(value.content.type === 'post' && 'string' === typeof value.content.text)
18- return cb(null, title(value.content.text))
19- else if('string' === typeof value.content.text)
20- return cb(null, value.content.type + ':'+title(value.content.text))
21- else
22- return cb(null, id.substring(0, 10)+'...')
23- })
24- }
25-}
components/message/timestamp.jsView
@@ -1,17 +1,0 @@
1-const h = require('mutant/h')
2-const nest = require('depnest')
3-
4-exports.gives = nest('message.timestamp')
5-exports.needs = nest('obs.timeAgo', 'first')
6-
7-exports.create = function (api) {
8- return nest('message.timestamp', timestamp)
9-
10- function timestamp (msg) {
11- return h('a.Timestamp', {
12- href: msg.key,
13- title: new Date(msg.value.timestamp)
14- }, api.obs.timeAgo(msg.value.timestamp))
15- }
16-}
17-
components/render_feed.jsView
@@ -1,28 +1,0 @@
1-const pull = require('pull-stream')
2-const h = require('mutant/h')
3-
4-exports.needs = {
5- message_render: 'first',
6- sbot_log: 'first'
7-}
8-
9-exports.gives = {
10- render_feed: true
11-}
12-
13-exports.create = function (api) {
14- return {
15- render_feed
16- }
17-
18- function render_feed (stream) {
19- const container = h('div')
20-
21- pull(
22- stream({reverse: true, limit: 100}),
23- pull.drain(msg => container.appendChild(api.message_render(msg)))
24- )
25-
26- return container
27- }
28-}
about/obs.jsView
@@ -1,0 +1,98 @@
1 +var {Value, Struct, computed} = require('mutant')
2 +var Abortable = require('pull-abortable')
3 +var pull = require('pull-stream')
4 +var msgs = require('ssb-msgs')
5 +var nest = require('depnest')
6 +
7 +exports.needs = nest({
8 + 'sbot.pull.userFeed': 'first',
9 + 'blob.sync.url': 'first'
10 +})
11 +exports.gives = nest({
12 + 'about.obs': [
13 + 'name',
14 + 'image',
15 + 'imageUrl'
16 + ]
17 +})
18 +
19 +exports.create = function (api) {
20 + var cache = {}
21 +
22 + return nest({
23 + 'about.obs': {
24 + name: (id) => get(id).displayName,
25 + image: (id) => get(id).image,
26 + imageUrl: (id) => {
27 + return computed(get(id).image, (image) => {
28 + var obj = msgs.link(image, 'blob')
29 + if (obj) {
30 + return api.blob_url(obj.link)
31 + }
32 + })
33 + }
34 + }
35 + })
36 +
37 + }
38 +
39 + function get (id) {
40 + if (!cache[id]) {
41 + cache[id] = About(api, id)
42 + }
43 + return cache[id]
44 + }
45 +}
46 +
47 +function About (api, id) {
48 + // naive about that only looks at what a feed asserts about itself
49 +
50 + var obs = Struct({
51 + displayName: Value(id.slice(1, 10)),
52 + image: Value()
53 + })
54 +
55 + var hasName = false
56 + var hasImage = false
57 +
58 + var abortable = Abortable()
59 +
60 + // search history
61 + pull(
62 + api.sbot.pull.userFeed({reverse: true, id}),
63 + abortable,
64 + pull.drain(function (item) {
65 + update(item)
66 + if (hasName && obs.image()) {
67 + abortable.abort()
68 + }
69 + })
70 + )
71 +
72 + // get live changes
73 + pull(
74 + api.sbot.pull.userFeed({old: false, id}),
75 + pull.drain(update)
76 + )
77 +
78 + return obs
79 +
80 + // scoped
81 +
82 + function update (item) {
83 + if (item.value && item.value.content.type === 'about' && item.value.content.about === id) {
84 + if (item.value.content.name) {
85 + if (!hasName || hasName < item.value.timestamp) {
86 + hasName = item.value.timestamp
87 + obs.displayName.set(item.value.content.name)
88 + }
89 + }
90 + if (item.value.content.image) {
91 + if (!hasImage || hasImage < item.value.timestamp) {
92 + hasImage = item.value.timestamp
93 + obs.image.set(item.value.content.image)
94 + }
95 + }
96 + }
97 + }
98 +}
blob.jsView
@@ -1,0 +1,9 @@
1 +const nest = require('depnest')
2 +
3 +exports.gives = nest('blob.sync.url')
4 +
5 +exports.create = function (api) {
6 + return nest('blob.sync.url', function (id) {
7 + return id
8 + }
9 +}
helpers/blob_url.jsView
@@ -1,7 +1,0 @@
1-exports.gives = 'blob_url'
2-
3-exports.create = function (api) {
4- return function (id) {
5- return id
6- }
7-}
helpers/emoji.jsView
@@ -1,17 +1,0 @@
1-var emojis = require('emoji-named-characters')
2-var emojiNames = Object.keys(emojis)
3-
4-exports.needs = { blob_url: 'first' }
5-exports.gives = { emoji_names: true, emoji_url: true }
6-
7-exports.create = function (api) {
8- return {
9- emoji_names: function () {
10- return emojiNames
11- },
12- emoji_url: function (emoji) {
13- return emoji in emojis &&
14- api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png'
15- }
16- }
17-}
emoji.jsView
@@ -1,0 +1,28 @@
1 +const emojis = require('emoji-named-characters')
2 +const emojiNames = Object.keys(emojis)
3 +const nest = require('depnest')
4 +
5 +exports.needs = nest('blob.sync.url', 'first')
6 +exports.gives = nest({
7 + 'emoji.sync': [
8 + 'names',
9 + 'url'
10 + ]
11 +
12 +exports.create = function (api) {
13 + return nest({
14 + 'emoji.sync': {
15 + names,
16 + url
17 + }
18 + })
19 +
20 + function names () {
21 + return emojiNames
22 + }
23 +
24 + function url (emoji) {
25 + return emoji in emojis &&
26 + api.blob.sync.url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png'
27 + }
28 +}
feed/html.jsView
@@ -1,0 +1,28 @@
1 +const pull = require('pull-stream')
2 +const h = require('mutant/h')
3 +
4 +exports.needs = {
5 + message_render: 'first',
6 + sbot_log: 'first'
7 +}
8 +
9 +exports.gives = {
10 + render_feed: true
11 +}
12 +
13 +exports.create = function (api) {
14 + return {
15 + render_feed
16 + }
17 +
18 + function render_feed (stream) {
19 + const container = h('div')
20 +
21 + pull(
22 + stream({reverse: true, limit: 100}),
23 + pull.drain(msg => container.appendChild(api.message_render(msg)))
24 + )
25 +
26 + return container
27 + }
28 +}
feed/pull/private.jsView
@@ -1,0 +1,30 @@
1 +const pull = require('pull-stream')
2 +const nest = require('depnest')
3 +
4 +exports.gives = nest('feed.pull.private')
5 +exports.needs = nest({
6 + 'sbot.log': 'first',
7 + // 'message.sync.unbox': 'first'
8 +})
9 +exports.create = function (api) {
10 + return nest('feed.pull.private': function (opts) {
11 + pull(
12 + api.sbot_log(opts)//,
13 + // unbox()
14 + )
15 + }
16 +
17 + // scoped
18 +
19 + function unbox () {
20 + return pull(
21 + pull.filter(function (msg) {
22 + return typeof msg.value.content === 'string'
23 + }),
24 + pull.map(function (msg) {
25 + return api.message.sync.unbox(msg)
26 + }),
27 + pull.filter(Boolean)
28 + )
29 + }
30 +}
feed/pull/public.jsView
@@ -1,0 +1,7 @@
1 +const nest = require('depnest')
2 +
3 +exports.gives = nest('feed.pull.public')
4 +exports.needs = nest('sbot.log', 'first')
5 +exports.create = function (api) {
6 + return nest('feed.pull.public', api.sbot.pull.log)
7 +}
observables/about.jsView
@@ -1,91 +1,0 @@
1-var {Value, Struct, computed} = require('mutant')
2-var Abortable = require('pull-abortable')
3-var pull = require('pull-stream')
4-var msgs = require('ssb-msgs')
5-
6-exports.needs = {
7- sbot_user_feed: 'first',
8- blob_url: 'first'
9-}
10-exports.gives = {
11- obs_about_name: true,
12- obs_about_image: true,
13- obs_about_image_url: true
14-}
15-
16-exports.create = function (api) {
17- var cache = {}
18-
19- return {
20- obs_about_name: (id) => get(id).displayName,
21- obs_about_image: (id) => get(id).image,
22- obs_about_image_url: (id) => {
23- return computed(get(id).image, (image) => {
24- var obj = msgs.link(image, 'blob')
25- if (obj) {
26- return api.blob_url(obj.link)
27- }
28- })
29- }
30- }
31-
32- function get (id) {
33- if (!cache[id]) {
34- cache[id] = About(api, id)
35- }
36- return cache[id]
37- }
38-}
39-
40-function About (api, id) {
41- // naive about that only looks at what a feed asserts about itself
42-
43- var obs = Struct({
44- displayName: Value(id.slice(1, 10)),
45- image: Value()
46- })
47-
48- var hasName = false
49- var hasImage = false
50-
51- var abortable = Abortable()
52-
53- // search history
54- pull(
55- api.sbot_user_feed({reverse: true, id}),
56- abortable,
57- pull.drain(function (item) {
58- update(item)
59- if (hasName && obs.image()) {
60- abortable.abort()
61- }
62- })
63- )
64-
65- // get live changes
66- pull(
67- api.sbot_user_feed({old: false, id}),
68- pull.drain(update)
69- )
70-
71- return obs
72-
73- // scoped
74-
75- function update (item) {
76- if (item.value && item.value.content.type === 'about' && item.value.content.about === id) {
77- if (item.value.content.name) {
78- if (!hasName || hasName < item.value.timestamp) {
79- hasName = item.value.timestamp
80- obs.displayName.set(item.value.content.name)
81- }
82- }
83- if (item.value.content.image) {
84- if (!hasImage || hasImage < item.value.timestamp) {
85- hasImage = item.value.timestamp
86- obs.image.set(item.value.content.image)
87- }
88- }
89- }
90- }
91-}
observables/timeAgo.jsView
@@ -1,34 +1,0 @@
1-const Value = require('mutant/value')
2-const computed = require('mutant/computed')
3-const nest = require('depnest')
4-const human = require('human-time')
5-
6-exports.gives = nest('obs.timeAgo')
7-
8-exports.create = function (api) {
9- return nest('obs.timeAgo', timeAgo)
10-
11- function timeAgo (timestamp) {
12- var timer
13- var value = Value(Time(timestamp))
14- return computed([value], (a) => a, {
15- onListen: () => {
16- timer = setInterval(refresh, 5e3)
17- refresh()
18- },
19- onUnlisten: () => {
20- clearInterval(timer)
21- }
22- })
23-
24- function refresh () {
25- value.set(Time(timestamp))
26- }
27- }
28-}
29-
30-function Time (timestamp) {
31- return human(new Date(timestamp))
32- .replace(/minute/, 'min')
33- .replace(/second/, 'sec')
34-}
lib/timeAgo.jsView
@@ -1,0 +1,34 @@
1 +const Value = require('mutant/value')
2 +const computed = require('mutant/computed')
3 +const nest = require('depnest')
4 +const human = require('human-time')
5 +
6 +exports.gives = nest('lib.obs.timeAgo')
7 +
8 +exports.create = function (api) {
9 + return nest('lib.obs.timeAgo', timeAgo)
10 +
11 + function timeAgo (timestamp) {
12 + var timer
13 + var value = Value(Time(timestamp))
14 + return computed([value], (a) => a, {
15 + onListen: () => {
16 + timer = setInterval(refresh, 5e3)
17 + refresh()
18 + },
19 + onUnlisten: () => {
20 + clearInterval(timer)
21 + }
22 + })
23 +
24 + function refresh () {
25 + value.set(Time(timestamp))
26 + }
27 + }
28 +}
29 +
30 +function Time (timestamp) {
31 + return human(new Date(timestamp))
32 + .replace(/minute/, 'min')
33 + .replace(/second/, 'sec')
34 +}
plugs/message_action/reply.jsView
@@ -1,9 +1,0 @@
1-var h = require('mutant/h')
2-
3-exports.gives = 'message_action'
4-
5-exports.create = function () {
6- return function reply (msg) {
7- return h('a', { href: '#' + msg.key }, 'Reply')
8- }
9-}
plugs/message_decorate/data-id.jsView
@@ -1,8 +1,0 @@
1-exports.gives = 'message_decorate'
2-
3-exports.create = function (api) {
4- return function (element, { msg }) {
5- element.dataset.id = msg.key
6- return element
7- }
8-}
plugs/message_layout/default.jsView
@@ -1,37 +1,0 @@
1-const h = require('mutant/h')
2-
3-exports.needs = {
4- message_backlinks: 'first',
5- message_author: 'first',
6- message_meta: 'map',
7- message_action: 'map',
8- message: {
9- timestamp: 'first'
10- }
11-}
12-
13-exports.gives = {
14- message_layout: true
15-}
16-
17-exports.create = function (api) {
18- return {
19- message_layout
20- }
21-
22- function message_layout (msg, opts) {
23- if (!(opts.layout === undefined || opts.layout === 'default')) return
24- return h('div', {
25- classList: 'Message'
26- }, [
27- h('header.author', {}, api.message_author(msg)),
28- h('section.timestamp', {}, api.message.timestamp(msg)),
29- h('section.title', {}, opts.title),
30- h('section.meta', {}, api.message_meta(msg)),
31- h('section.content', {}, opts.content),
32- h('section.raw-content'),
33- h('section.action', {}, api.message_action(msg)),
34- h('footer.backlinks', {}, api.message_backlinks(msg))
35- ])
36- }
37-}
plugs/message_layout/mini.jsView
@@ -1,33 +1,0 @@
1-const h = require('mutant/h')
2-
3-exports.needs = {
4- message_backlinks: 'first',
5- message_author: 'first',
6- message_meta: 'map',
7- message: {
8- timestamp: 'first'
9- }
10-}
11-
12-exports.gives = {
13- message_layout: true
14-}
15-
16-exports.create = function (api) {
17- return {
18- message_layout
19- }
20-
21- function message_layout (msg, opts) {
22- if (opts.layout !== 'mini') return
23- return h('div', {
24- classList: 'Message -mini'
25- }, [
26- h('header.author', {}, api.message_author(msg, { size: 'mini' })),
27- h('section.timestamp', {}, api.message.timestamp(msg)),
28- h('section.meta', {}, api.message_meta(msg)),
29- h('section.content', {}, opts.content),
30- h('section.raw-content')
31- ])
32- }
33-}
plugs/message_meta/channel.jsView
@@ -1,10 +1,0 @@
1-const h = require('mutant/h')
2-
3-exports.gives = 'message_meta'
4-
5-exports.create = function (api) {
6- return function channel (msg) {
7- const { channel } = msg.value.content
8- if (channel) return h('span', {}, ['#' + channel])
9- }
10-}
plugs/message_render/post.jsView
@@ -1,39 +1,0 @@
1-var h = require('mutant/h')
2-
3-exports.needs = {
4- message_decorate: 'reduce',
5- message_layout: 'first',
6- message_link: 'first',
7- markdown: 'first'
8-}
9-
10-exports.gives = {
11- message_render: true
12-}
13-
14-exports.create = function (api) {
15- return {
16- message_render
17- }
18-
19- function message_render (msg) {
20- if (msg.value.content.type !== 'post') return
21- var element = api.message_layout(msg, {
22- title: message_title(msg),
23- content: message_content(msg),
24- layout: 'default'
25- })
26-
27- return api.message_decorate(element, { msg })
28- }
29-
30- function message_content (data) {
31- if (!data.value.content || !data.value.content.text) return
32- return h('div', {}, api.markdown(data.value.content))
33- }
34-
35- function message_title (data) {
36- var root = data.value.content && data.value.content.root
37- return !root ? null : h('span', ['re: ', api.message_link(root)])
38- }
39-}
plugs/message_render/vote.jsView
@@ -1,35 +1,0 @@
1-var h = require('mutant/h')
2-
3-exports.needs = {
4- message_layout: 'first',
5- message_decorate: 'reduce',
6- message_link: 'first',
7- markdown: 'first'
8-}
9-
10-exports.gives = {
11- message_render: true
12-}
13-
14-exports.create = function (api) {
15- return {
16- message_render
17- }
18-
19- function message_render (msg) {
20- if (msg.value.content.type !== 'vote') return
21- var element = api.message_layout(msg, {
22- content: render_vote(msg),
23- layout: 'mini'
24- })
25-
26- return api.message_decorate(element, { msg })
27- }
28-
29- function render_vote (msg) {
30- var link = msg.value.content.vote.link
31- return [
32- msg.value.content.vote.value > 0 ? 'dug' : 'undug', ' ', api.message_link(link)
33- ]
34- }
35-}
plugs/message_render/zz_fallback.jsView
@@ -1,33 +1,0 @@
1-var h = require('mutant/h')
2-
3-exports.needs = {
4- message_layout: 'first',
5- message_decorate: 'reduce'
6-}
7-
8-exports.gives = {
9- message_render: true
10-}
11-
12-exports.create = function (api) {
13- return {
14- message_render
15- }
16-
17- function message_render (msg) {
18- var element = api.message_layout(msg, {
19- content: message_content(msg),
20- layout: 'mini'
21- })
22-
23- return api.message_decorate(element, { msg })
24- }
25-
26- function message_content (msg) {
27- if (typeof msg.value.content === 'string') {
28- return h('code', {}, 'PRIVATE')
29- } else {
30- return h('code', {}, msg.value.content.type)
31- }
32- }
33-}
message/html/action/reply.jsView
@@ -1,0 +1,9 @@
1 +var h = require('mutant/h')
2 +
3 +exports.gives = 'message_action'
4 +
5 +exports.create = function () {
6 + return function reply (msg) {
7 + return h('a', { href: '#' + msg.key }, 'Reply')
8 + }
9 +}
message/html/author.jsView
@@ -1,0 +1,20 @@
1 +const h = require('mutant/h')
2 +const nest = require('depnest')
3 +
4 +exports.needs = nest('obs.about.name', 'first')
5 +
6 +exports.gives = nest('obs.'){
7 + message_author: true
8 +}
9 +
10 +exports.create = function (api) {
11 + return {
12 + message_author
13 + }
14 +
15 + function message_author (msg) {
16 + return h('div', {title: msg.value.author}, [
17 + '@', api.obs_about_name(msg.value.author)
18 + ])
19 + }
20 +}
message/html/backlinks.jsView
@@ -1,0 +1,42 @@
1 +const fs = require('fs')
2 +const h = require('mutant/h')
3 +
4 +exports.needs = {
5 + message_name: 'first'
6 +}
7 +
8 +exports.gives = 'message_backlinks'
9 +
10 +exports.create = function (api) {
11 + return function (msg) {
12 + var links = []
13 + for (var k in CACHE) {
14 + var _msg = CACHE[k]
15 + var mentions = _msg.content.mentions
16 +
17 + if (Array.isArray(mentions)) {
18 + for (var i = 0; i < mentions.length; i++) {
19 + if (mentions[i].link == msg.key) {
20 + links.push(k)
21 + }
22 + }
23 + }
24 + }
25 +
26 + if (links.length === 0) return null
27 +
28 + var hrefList = h('ul')
29 + links.forEach(link => {
30 + api.message_name(link, (err, name) => {
31 + if (err) throw err
32 + hrefList.appendChild(h('li', [
33 + h('a -backlink', { href: `#${link}` }, name)
34 + ]))
35 + })
36 + })
37 + return h('MessageBacklinks', [
38 + h('header', 'backlinks:'),
39 + hrefList
40 + ])
41 + }
42 +}
message/html/decorate/data-id.jsView
@@ -1,0 +1,8 @@
1 +exports.gives = 'message_decorate'
2 +
3 +exports.create = function (api) {
4 + return function (element, { msg }) {
5 + element.dataset.id = msg.key
6 + return element
7 + }
8 +}
message/html/layout/default.jsView
@@ -1,0 +1,37 @@
1 +const h = require('mutant/h')
2 +
3 +exports.needs = {
4 + message_backlinks: 'first',
5 + message_author: 'first',
6 + message_meta: 'map',
7 + message_action: 'map',
8 + message: {
9 + timestamp: 'first'
10 + }
11 +}
12 +
13 +exports.gives = {
14 + message_layout: true
15 +}
16 +
17 +exports.create = function (api) {
18 + return {
19 + message_layout
20 + }
21 +
22 + function message_layout (msg, opts) {
23 + if (!(opts.layout === undefined || opts.layout === 'default')) return
24 + return h('div', {
25 + classList: 'Message'
26 + }, [
27 + h('header.author', {}, api.message_author(msg)),
28 + h('section.timestamp', {}, api.message.timestamp(msg)),
29 + h('section.title', {}, opts.title),
30 + h('section.meta', {}, api.message_meta(msg)),
31 + h('section.content', {}, opts.content),
32 + h('section.raw-content'),
33 + h('section.action', {}, api.message_action(msg)),
34 + h('footer.backlinks', {}, api.message_backlinks(msg))
35 + ])
36 + }
37 +}
message/html/layout/mini.jsView
@@ -1,0 +1,33 @@
1 +const h = require('mutant/h')
2 +
3 +exports.needs = {
4 + message_backlinks: 'first',
5 + message_author: 'first',
6 + message_meta: 'map',
7 + message: {
8 + timestamp: 'first'
9 + }
10 +}
11 +
12 +exports.gives = {
13 + message_layout: true
14 +}
15 +
16 +exports.create = function (api) {
17 + return {
18 + message_layout
19 + }
20 +
21 + function message_layout (msg, opts) {
22 + if (opts.layout !== 'mini') return
23 + return h('div', {
24 + classList: 'Message -mini'
25 + }, [
26 + h('header.author', {}, api.message_author(msg, { size: 'mini' })),
27 + h('section.timestamp', {}, api.message.timestamp(msg)),
28 + h('section.meta', {}, api.message_meta(msg)),
29 + h('section.content', {}, opts.content),
30 + h('section.raw-content')
31 + ])
32 + }
33 +}
message/html/link.jsView
@@ -1,0 +1,25 @@
1 +var h = require('mutant/h')
2 +var ref = require('ssb-ref')
3 +
4 +exports.needs = {
5 + message_name: 'first'
6 +}
7 +
8 +exports.gives = 'message_link'
9 +
10 +exports.create = function (api) {
11 + return function (id) {
12 + if (typeof id !== 'string') { throw new Error('link must be to message id') }
13 +
14 + var link = h('a', {href: '#' + id}, id.substring(0, 10) + '...')
15 +
16 + if (ref.isMsg(id)) {
17 + api.message_name(id, function (err, name) {
18 + if (err) console.error(err)
19 + else link.textContent = name
20 + })
21 + }
22 +
23 + return link
24 + }
25 +}
message/html/markdown.jsView
@@ -1,0 +1,55 @@
1 +const renderer = require('ssb-markdown')
2 +const fs = require('fs')
3 +const h = require('mutant/h')
4 +const ref = require('ssb-ref')
5 +
6 +exports.needs = {
7 + blob_url: 'first',
8 + emoji_url: 'first'
9 +}
10 +
11 +exports.gives = {
12 + markdown: true
13 +}
14 +
15 +exports.create = function (api) {
16 + return {
17 + markdown
18 + }
19 +
20 + function markdown (content) {
21 + if (typeof content === 'string') { content = {text: content} }
22 + // handle patchwork style mentions.
23 + var mentions = {}
24 + if (Array.isArray(content.mentions)) {
25 + content.mentions.forEach(function (link) {
26 + if (link.name) mentions['@' + link.name] = link.link
27 + })
28 + }
29 +
30 + var md = h('Markdown')
31 + md.innerHTML = renderer.block(content.text, {
32 + emoji: renderEmoji,
33 + toUrl: (id) => {
34 + if (ref.isBlob(id)) return api.blob_url(id)
35 + return '#' + (mentions[id] ? mentions[id] : id)
36 + },
37 + imageLink: (id) => '#' + id
38 + })
39 +
40 + return md
41 + }
42 +
43 + function renderEmoji (emoji) {
44 + var url = api.emoji_url(emoji)
45 + if (!url) return ':' + emoji + ':'
46 + return `
47 + <img
48 + src="${encodeURI(url)}"
49 + alt=":${escape(emoji)}:"
50 + title=":${escape(emoji)}:"
51 + class="emoji"
52 + >
53 + `
54 + }
55 +}
message/html/meta/channel.jsView
@@ -1,0 +1,10 @@
1 +const h = require('mutant/h')
2 +
3 +exports.gives = 'message_meta'
4 +
5 +exports.create = function (api) {
6 + return function channel (msg) {
7 + const { channel } = msg.value.content
8 + if (channel) return h('span', {}, ['#' + channel])
9 + }
10 +}
message/html/name.jsView
@@ -1,0 +1,23 @@
1 +
2 +function title (s) {
3 + var m = /^\n*([^\n]{0,40})/.exec(s)
4 + return m && (m[1].length == 40 ? m[1] + '...' : m[1])
5 +}
6 +
7 +exports.needs = { sbot_get: 'first' }
8 +exports.gives = 'message_name'
9 +
10 +// TODO: rewrite as observable?
11 +
12 +exports.create = function (api) {
13 + return function (id, cb) {
14 + api.sbot_get(id, function (err, value) {
15 + if (err && err.name == 'NotFoundError') { return cb(null, id.substring(0, 10) + '...(missing)') }
16 + if (value.content.type === 'post' && typeof value.content.text === 'string') {
17 + return cb(null, title(value.content.text))
18 + } else if (typeof value.content.text === 'string') { return cb(null, value.content.type + ':' + title(value.content.text)) } else {
19 + return cb(null, id.substring(0, 10) + '...')
20 + }
21 + })
22 + }
23 +}
message/html/render/post.jsView
@@ -1,0 +1,39 @@
1 +var h = require('mutant/h')
2 +
3 +exports.needs = {
4 + message_decorate: 'reduce',
5 + message_layout: 'first',
6 + message_link: 'first',
7 + markdown: 'first'
8 +}
9 +
10 +exports.gives = {
11 + message_render: true
12 +}
13 +
14 +exports.create = function (api) {
15 + return {
16 + message_render
17 + }
18 +
19 + function message_render (msg) {
20 + if (msg.value.content.type !== 'post') return
21 + var element = api.message_layout(msg, {
22 + title: message_title(msg),
23 + content: message_content(msg),
24 + layout: 'default'
25 + })
26 +
27 + return api.message_decorate(element, { msg })
28 + }
29 +
30 + function message_content (data) {
31 + if (!data.value.content || !data.value.content.text) return
32 + return h('div', {}, api.markdown(data.value.content))
33 + }
34 +
35 + function message_title (data) {
36 + var root = data.value.content && data.value.content.root
37 + return !root ? null : h('span', ['re: ', api.message_link(root)])
38 + }
39 +}
message/html/render/vote.jsView
@@ -1,0 +1,35 @@
1 +var h = require('mutant/h')
2 +
3 +exports.needs = {
4 + message_layout: 'first',
5 + message_decorate: 'reduce',
6 + message_link: 'first',
7 + markdown: 'first'
8 +}
9 +
10 +exports.gives = {
11 + message_render: true
12 +}
13 +
14 +exports.create = function (api) {
15 + return {
16 + message_render
17 + }
18 +
19 + function message_render (msg) {
20 + if (msg.value.content.type !== 'vote') return
21 + var element = api.message_layout(msg, {
22 + content: render_vote(msg),
23 + layout: 'mini'
24 + })
25 +
26 + return api.message_decorate(element, { msg })
27 + }
28 +
29 + function render_vote (msg) {
30 + var link = msg.value.content.vote.link
31 + return [
32 + msg.value.content.vote.value > 0 ? 'dug' : 'undug', ' ', api.message_link(link)
33 + ]
34 + }
35 +}
message/html/render/zz_fallback.jsView
@@ -1,0 +1,33 @@
1 +var h = require('mutant/h')
2 +
3 +exports.needs = {
4 + message_layout: 'first',
5 + message_decorate: 'reduce'
6 +}
7 +
8 +exports.gives = {
9 + message_render: true
10 +}
11 +
12 +exports.create = function (api) {
13 + return {
14 + message_render
15 + }
16 +
17 + function message_render (msg) {
18 + var element = api.message_layout(msg, {
19 + content: message_content(msg),
20 + layout: 'mini'
21 + })
22 +
23 + return api.message_decorate(element, { msg })
24 + }
25 +
26 + function message_content (msg) {
27 + if (typeof msg.value.content === 'string') {
28 + return h('code', {}, 'PRIVATE')
29 + } else {
30 + return h('code', {}, msg.value.content.type)
31 + }
32 + }
33 +}
message/html/timestamp.jsView
@@ -1,0 +1,17 @@
1 +const h = require('mutant/h')
2 +const nest = require('depnest')
3 +
4 +exports.gives = nest('message.timestamp')
5 +exports.needs = nest('obs.timeAgo', 'first')
6 +
7 +exports.create = function (api) {
8 + return nest('message.timestamp', timestamp)
9 +
10 + function timestamp (msg) {
11 + return h('a.Timestamp', {
12 + href: msg.key,
13 + title: new Date(msg.value.timestamp)
14 + }, api.obs.timeAgo(msg.value.timestamp))
15 + }
16 +}
17 +
streams/private.jsView
@@ -1,39 +1,0 @@
1-const pull = require('pull-stream')
2-
3-exports.gives = {
4- streams: {
5- private: true
6- }
7-}
8-
9-exports.needs = {
10- sbot_log: 'first',
11- //message_unbox: true
12-}
13-
14-exports.create = function (api) {
15- return {
16- streams: {
17- 'private': function (opts) {
18- pull(
19- api.sbot_log(opts)//,
20- //unbox()
21- )
22- }
23- }
24- }
25-
26- // scoped
27-
28- function unbox () {
29- return pull(
30- pull.filter(function (msg) {
31- return typeof msg.value.content === 'string'
32- }),
33- pull.map(function (msg) {
34- return api.message_unbox(msg)
35- }),
36- pull.filter(Boolean)
37- )
38- }
39-}
streams/public.jsView
@@ -1,17 +1,0 @@
1-exports.gives = {
2- streams: {
3- public: true
4- }
5-}
6-
7-exports.needs = {
8- sbot_log: 'first'
9-}
10-
11-exports.create = function (api) {
12- return {
13- streams: {
14- public: api.sbot_log
15- }
16- }
17-}

Built with git-ssb-web