Commit 168a44f801f80b2e597db0f95879065823d1d9df
add link previews for channels and show following subscribers
also remove old channel subscribe hover links since not needed with previewMatt McKegg committed on 2/12/2018, 2:44:00 AM
Parent: 6d887637d16d755298b8177960c699201a62b831
Files changed
locales/en.json | ||
---|---|---|
@@ -33,9 +33,9 @@ | ||
33 | 33 | "other": "You share %s mutual friends with this person." |
34 | 34 | }, |
35 | 35 | "Followed by": "Followed by", |
36 | 36 | "You follow %s people that follow this person.": { |
37 | - "one": "You follow %s people that follow this person.", | |
37 | + "one": "You follow %s person that follows this person.", | |
38 | 38 | "other": "You follow %s people that follow this person." |
39 | 39 | }, |
40 | 40 | "Send Private Message": "Send Private Message", |
41 | 41 | "Friends": "Friends", |
@@ -159,6 +159,11 @@ | ||
159 | 159 | " via ": " via ", |
160 | 160 | "The author of this message could be outside of your follow range or they may be blocked.": "The author of this message could be outside of your follow range or they may be blocked.", |
161 | 161 | "Close": "Close", |
162 | 162 | " referenced this message:": " referenced this message:", |
163 | - "on ": "on " | |
164 | -} | |
163 | + "on ": "on ", | |
164 | + "You follow %s people that subscribe to this channel.": { | |
165 | + "one": "You follow %s person that subscribes to this channel.", | |
166 | + "other": "You follow %s people that subscribe to this channel." | |
167 | + }, | |
168 | + "People you follow that subscribe to this channel": "People you follow that subscribe to this channel" | |
169 | +} |
modules/app/link-preview.js | ||
---|---|---|
@@ -5,9 +5,10 @@ | ||
5 | 5 | var nest = require('depnest') |
6 | 6 | |
7 | 7 | exports.needs = nest({ |
8 | 8 | 'intl.sync.i18n': 'first', |
9 | - 'profile.html.preview': 'first' | |
9 | + 'profile.html.preview': 'first', | |
10 | + 'channel.html.preview': 'first' | |
10 | 11 | }) |
11 | 12 | |
12 | 13 | exports.gives = nest('app.linkPreview') |
13 | 14 | |
@@ -52,8 +53,10 @@ | ||
52 | 53 | h('strong', [i18n('External Link'), ' ๐']), h('br'), |
53 | 54 | h('code', href) |
54 | 55 | ]) |
55 | 56 | ]) |
57 | + } else if (href.startsWith('#')) { | |
58 | + preview = api.channel.html.preview(href.slice(1)) | |
56 | 59 | } |
57 | 60 | } |
58 | 61 | |
59 | 62 | if (preview) { |
modules/channel/obs/subscribers.js | ||
---|---|---|
@@ -1,0 +1,32 @@ | ||
1 | +var nest = require('depnest') | |
2 | +var MutantPullReduce = require('mutant-pull-reduce') | |
3 | + | |
4 | +exports.needs = nest({ | |
5 | + 'keys.sync.id': 'first', | |
6 | + 'sbot.pull.stream': 'first' | |
7 | +}) | |
8 | + | |
9 | +exports.gives = nest('channel.obs.subscribers') | |
10 | + | |
11 | +exports.create = function (api) { | |
12 | + return nest('channel.obs.subscribers', function (channel) { | |
13 | + var stream = api.sbot.pull.stream(sbot => sbot.patchwork.subscriptions({live: true, channel})) | |
14 | + return MutantPullReduce(stream, (state, msg) => { | |
15 | + if (msg.value) { | |
16 | + if (!state.includes(msg.from)) { | |
17 | + state.push(msg.from) | |
18 | + } | |
19 | + } else { | |
20 | + var index = state.indexOf(msg.from) | |
21 | + if (index >= 0) { | |
22 | + state.splice(index, 1) | |
23 | + } | |
24 | + } | |
25 | + return state | |
26 | + }, { | |
27 | + startValue: [], | |
28 | + nextTick: true, | |
29 | + sync: true | |
30 | + }) | |
31 | + }) | |
32 | +} |
modules/channel/html/preview.js | ||
---|---|---|
@@ -1,0 +1,70 @@ | ||
1 | +var nest = require('depnest') | |
2 | +var h = require('mutant/h') | |
3 | +var map = require('mutant/map') | |
4 | +var when = require('mutant/when') | |
5 | +var computed = require('mutant/computed') | |
6 | +var send = require('mutant/send') | |
7 | + | |
8 | +exports.needs = nest({ | |
9 | + 'about.obs.name': 'first', | |
10 | + 'about.html.image': 'first', | |
11 | + 'keys.sync.id': 'first', | |
12 | + 'sheet.display': 'first', | |
13 | + 'app.navigate': 'first', | |
14 | + 'intl.sync.i18n': 'first', | |
15 | + 'intl.sync.i18n_n': 'first', | |
16 | + 'sheet.profiles': 'first', | |
17 | + 'channel.html.subscribeToggle': 'first', | |
18 | + 'channel.sync.normalize': 'first', | |
19 | + 'channel.obs.subscribers': 'first', | |
20 | + 'contact.obs.following': 'first' | |
21 | +}) | |
22 | + | |
23 | +exports.gives = nest('channel.html.preview') | |
24 | + | |
25 | +exports.create = function (api) { | |
26 | + const i18n = api.intl.sync.i18n | |
27 | + const plural = api.intl.sync.i18n_n | |
28 | + | |
29 | + return nest('channel.html.preview', function (id) { | |
30 | + var yourId = api.keys.sync.id() | |
31 | + var channel = api.channel.sync.normalize(id) | |
32 | + var href = '#' + channel | |
33 | + var subscribers = api.channel.obs.subscribers(id) | |
34 | + var following = api.contact.obs.following(yourId) | |
35 | + var followingSubscribers = computed([subscribers, following], (a, b) => { | |
36 | + return a.filter(v => b.includes(v)) | |
37 | + }) | |
38 | + var followingSubscriberCount = computed(followingSubscribers, x => x.length) | |
39 | + | |
40 | + return h('ProfilePreview', [ | |
41 | + h('header', [ | |
42 | + h('div.main', [ | |
43 | + h('div.title', [ | |
44 | + h('h1', [ | |
45 | + h('a', {href, 'ev-click': () => api.app.navigate(href)}, [href]) | |
46 | + ]), | |
47 | + h('div.meta', [ | |
48 | + api.channel.html.subscribeToggle(channel) | |
49 | + ]) | |
50 | + ]) | |
51 | + ]) | |
52 | + ]), | |
53 | + | |
54 | + when(followingSubscriberCount, | |
55 | + h('section -mutualFriends', [ | |
56 | + h('a', { | |
57 | + href: '#', | |
58 | + 'ev-click': send(displaySubscribingFriends, followingSubscribers) | |
59 | + }, [ | |
60 | + '๐ฅ ', computed(['You follow %s people that subscribe to this channel.', followingSubscriberCount], plural) | |
61 | + ]) | |
62 | + ]) | |
63 | + ) | |
64 | + ]) | |
65 | + }) | |
66 | + | |
67 | + function displaySubscribingFriends (profiles) { | |
68 | + api.sheet.profiles(profiles, i18n('People you follow that subscribe to this channel')) | |
69 | + } | |
70 | +} |
modules/channel/html/subscribe-toggle.js | ||
---|---|---|
@@ -1,0 +1,48 @@ | ||
1 | +var nest = require('depnest') | |
2 | +var { h, when, send } = require('mutant') | |
3 | + | |
4 | +exports.gives = nest('channel.html.subscribeToggle') | |
5 | +exports.needs = nest({ | |
6 | + 'intl.sync.i18n': 'first', | |
7 | + 'keys.sync.id': 'first', | |
8 | + 'message.async.publish': 'first', | |
9 | + 'channel.obs.subscribed': 'first' | |
10 | +}) | |
11 | + | |
12 | +exports.create = function (api) { | |
13 | + var i18n = api.intl.sync.i18n | |
14 | + return nest('channel.html.subscribeToggle', function (channel, opts) { | |
15 | + var yourId = api.keys.sync.id() | |
16 | + var subscribedChannels = api.channel.obs.subscribed(yourId) | |
17 | + | |
18 | + return when(subscribedChannels.has(channel), | |
19 | + h('a.ToggleButton.-unsubscribe', { | |
20 | + 'href': '#', | |
21 | + 'title': i18n('Click to unsubscribe'), | |
22 | + 'ev-click': send(unsubscribe, channel) | |
23 | + }, i18n('Subscribed')), | |
24 | + h('a.ToggleButton.-subscribe', { | |
25 | + 'href': '#', | |
26 | + 'ev-click': send(subscribe, channel) | |
27 | + }, i18n('Subscribe')) | |
28 | + ) | |
29 | + }) | |
30 | + | |
31 | + function subscribe (id) { | |
32 | + // confirm | |
33 | + api.message.async.publish({ | |
34 | + type: 'channel', | |
35 | + channel: id, | |
36 | + subscribed: true | |
37 | + }) | |
38 | + } | |
39 | + | |
40 | + function unsubscribe (id) { | |
41 | + // confirm | |
42 | + api.message.async.publish({ | |
43 | + type: 'channel', | |
44 | + channel: id, | |
45 | + subscribed: false | |
46 | + }) | |
47 | + } | |
48 | +} |
modules/page/html/render/channel.js | ||
---|---|---|
@@ -1,11 +1,11 @@ | ||
1 | -var { h, when, send } = require('mutant') | |
1 | +var { h } = require('mutant') | |
2 | 2 | var nest = require('depnest') |
3 | 3 | |
4 | 4 | exports.needs = nest({ |
5 | - 'channel.obs.subscribed': 'first', | |
6 | 5 | 'message.html.compose': 'first', |
7 | 6 | 'channel.sync.normalize': 'first', |
7 | + 'channel.html.subscribeToggle': 'first', | |
8 | 8 | 'feed.html.rollup': 'first', |
9 | 9 | 'feed.html.followWarning': 'first', |
10 | 10 | 'feed.pull.channel': 'first', |
11 | 11 | 'sbot.pull.log': 'first', |
@@ -23,29 +23,17 @@ | ||
23 | 23 | return nest('page.html.render', function channel (path) { |
24 | 24 | if (path[0] !== '#') return |
25 | 25 | |
26 | 26 | var id = api.keys.sync.id() |
27 | + var contact = api.profile.obs.contact(id) | |
27 | 28 | |
28 | 29 | var channel = api.channel.sync.normalize(path.substr(1)) |
29 | - var subscribedChannels = api.channel.obs.subscribed(id) | |
30 | 30 | |
31 | - var contact = api.profile.obs.contact(id) | |
32 | - | |
33 | 31 | var prepend = [ |
34 | 32 | h('PageHeading', [ |
35 | 33 | h('h1', `#${channel}`), |
36 | 34 | h('div.meta', [ |
37 | - when(subscribedChannels.has(channel), | |
38 | - h('a.ToggleButton.-unsubscribe', { | |
39 | - 'href': '#', | |
40 | - 'title': i18n('Click to unsubscribe'), | |
41 | - 'ev-click': send(unsubscribe, channel) | |
42 | - }, i18n('Subscribed')), | |
43 | - h('a.ToggleButton.-subscribe', { | |
44 | - 'href': '#', | |
45 | - 'ev-click': send(subscribe, channel) | |
46 | - }, i18n('Subscribe')) | |
47 | - ) | |
35 | + api.channel.html.subscribeToggle(channel) | |
48 | 36 | ]) |
49 | 37 | ]), |
50 | 38 | api.message.html.compose({ |
51 | 39 | meta: {type: 'post', channel}, |
@@ -102,23 +90,5 @@ | ||
102 | 90 | var warning = i18n('You may not be able to see new channel content until you follow some users or pubs.') |
103 | 91 | return api.feed.html.followWarning(contact.isNotFollowingAnybody, warning) |
104 | 92 | } |
105 | 93 | }) |
106 | - | |
107 | - function subscribe (id) { | |
108 | - // confirm | |
109 | - api.message.async.publish({ | |
110 | - type: 'channel', | |
111 | - channel: id, | |
112 | - subscribed: true | |
113 | - }) | |
114 | - } | |
115 | - | |
116 | - function unsubscribe (id) { | |
117 | - // confirm | |
118 | - api.message.async.publish({ | |
119 | - type: 'channel', | |
120 | - channel: id, | |
121 | - subscribed: false | |
122 | - }) | |
123 | - } | |
124 | 94 | } |
modules/page/html/render/channels.js | ||
---|---|---|
@@ -36,17 +36,9 @@ | ||
36 | 36 | classList: [ |
37 | 37 | when(subscribed, '-subscribed') |
38 | 38 | ] |
39 | 39 | }, [ |
40 | - h('span.name', '#' + channel), | |
41 | - when(subscribed, | |
42 | - h('a.-unsubscribe', { | |
43 | - 'ev-click': send(unsubscribe, channel) | |
44 | - }, i18n('Unsubscribe')), | |
45 | - h('a.-subscribe', { | |
46 | - 'ev-click': send(subscribe, channel) | |
47 | - }, i18n('Subscribe')) | |
48 | - ) | |
40 | + h('span.name', '#' + channel) | |
49 | 41 | ]) |
50 | 42 | }, {maxTime: 5, idle: true}) |
51 | 43 | ]) |
52 | 44 | ]) |
modules/page/html/render/public.js | ||
---|---|---|
@@ -201,17 +201,9 @@ | ||
201 | 201 | classList: [ |
202 | 202 | when(subscribed, '-subscribed') |
203 | 203 | ] |
204 | 204 | }, [ |
205 | - h('span.name', '#' + channel), | |
206 | - when(subscribed, | |
207 | - h('a.-unsubscribe', { | |
208 | - 'ev-click': send(unsubscribe, channel) | |
209 | - }, i18n('Unsubscribe')), | |
210 | - h('a.-subscribe', { | |
211 | - 'ev-click': send(subscribe, channel) | |
212 | - }, i18n('Subscribe')) | |
213 | - ) | |
205 | + h('span.name', '#' + channel) | |
214 | 206 | ]) |
215 | 207 | }, {maxTime: 5}), |
216 | 208 | h('a.channel -more', {href: '/channels'}, i18n('More Channels...')) |
217 | 209 | ]) |
sbot/subscriptions.js | ||
---|---|---|
@@ -1,9 +1,34 @@ | ||
1 | 1 | var FlumeReduce = require('flumeview-reduce') |
2 | 2 | var normalizeChannel = require('ssb-ref').normalizeChannel |
3 | +var FlatMap = require('pull-flatmap') | |
4 | +var pull = require('pull-stream') | |
3 | 5 | |
4 | 6 | module.exports = function (ssb, config) { |
5 | - return ssb._flumeUse('patchwork-subscriptions', FlumeReduce(3, reduce, map)) | |
7 | + var index = ssb._flumeUse('patchwork-subscriptions', FlumeReduce(3, reduce, map)) | |
8 | + return { | |
9 | + stream: function (opts) { | |
10 | + var channel = normalizeChannel(opts.channel) | |
11 | + return pull( | |
12 | + index.stream({live: opts.live}), | |
13 | + FlatMap(items => { | |
14 | + var result = [] | |
15 | + | |
16 | + if (items) { | |
17 | + Object.keys(items).forEach(key => { | |
18 | + var parts = getParts(key) | |
19 | + if (parts && (!channel || parts[1] === channel)) { | |
20 | + result.push({from: parts[0], to: parts[1], value: items[key][1], ts: items[key][0]}) | |
21 | + } | |
22 | + }) | |
23 | + } | |
24 | + | |
25 | + return result | |
26 | + }) | |
27 | + ) | |
28 | + }, | |
29 | + get: index.get | |
30 | + } | |
6 | 31 | } |
7 | 32 | |
8 | 33 | function reduce (result, item) { |
9 | 34 | if (!result) result = {} |
@@ -16,8 +41,15 @@ | ||
16 | 41 | } |
17 | 42 | return result |
18 | 43 | } |
19 | 44 | |
45 | +function getParts (value) { | |
46 | + var splitIndex = value.indexOf(':') | |
47 | + if (splitIndex > 50) { // HACK: yup | |
48 | + return [value.slice(0, splitIndex), value.slice(splitIndex + 1)] | |
49 | + } | |
50 | +} | |
51 | + | |
20 | 52 | function map (msg) { |
21 | 53 | if (msg.value.content && msg.value.content.type === 'channel') { |
22 | 54 | if (typeof msg.value.content.subscribed === 'boolean') { |
23 | 55 | var channel = normalizeChannel(msg.value.content.channel) |
Built with git-ssb-web