Commit ee8e82af460b4067e37ca225319fad117e33b584
add notifications page, notifications sideNav, wip comments source
mix irving committed on 4/30/2018, 6:39:33 AMParent: 290057bfce673063c5629dd99de5b67dab52483a
Files changed
app/html/sideNav/sideNavNotifications.js | added |
app/html/sideNav/sideNavNotifications.mcss | added |
app/index.js | changed |
app/page/addressBook.js | changed |
app/page/statsShow.js | changed |
app/page/notifications.js | added |
package.json | changed |
router/sync/routes.js | changed |
ssb-server-blog-stats.js | changed |
translations/en.js | changed |
app/html/sideNav/sideNavNotifications.js | ||
---|---|---|
@@ -1,0 +1,69 @@ | ||
1 | +const nest = require('depnest') | |
2 | +const { h, computed } = require('mutant') | |
3 | + | |
4 | +exports.gives = nest({ | |
5 | + 'app.html.sideNav': true | |
6 | +}) | |
7 | + | |
8 | +exports.needs = nest({ | |
9 | + 'history.sync.push': 'first', | |
10 | + 'translations.sync.strings': 'first' | |
11 | +}) | |
12 | + | |
13 | +const SECTIONS = ['comments', 'likes', 'shares'] | |
14 | + | |
15 | +const ICONS = { | |
16 | + stats: 'bar-chart', | |
17 | + comments: 'commenting-o', | |
18 | + likes: 'heart-o', | |
19 | + shares: 'share-alt' | |
20 | +} | |
21 | + | |
22 | +exports.create = (api) => { | |
23 | + return nest({ | |
24 | + 'app.html.sideNav': sideNav | |
25 | + }) | |
26 | + | |
27 | + function sideNav (location) { | |
28 | + if (location.page !== 'statsShow' && location.page !== 'notifications') return | |
29 | + if (location.page === 'notifications' && !SECTIONS.includes(location.section)) return | |
30 | + | |
31 | + const strings = api.translations.sync.strings() | |
32 | + const goTo = (loc) => () => api.history.sync.push(loc) | |
33 | + | |
34 | + return h('SideNav -notifications', [ | |
35 | + LevelOneSideNav() | |
36 | + ]) | |
37 | + | |
38 | + function LevelOneSideNav () { | |
39 | + return h('div.level.-one', [ | |
40 | + h('section', [ | |
41 | + h('Option', | |
42 | + { | |
43 | + className: location.page === 'statsShow' ? '-selected' : '', | |
44 | + 'ev-click': goTo({page: 'statsShow'}) | |
45 | + }, | |
46 | + [ | |
47 | + h('i.fa', { className: `fa-${ICONS['stats']}` }), | |
48 | + strings['stats'] | |
49 | + ] | |
50 | + ), | |
51 | + SECTIONS.map(section => SectionOption(section)) | |
52 | + ]) | |
53 | + ]) | |
54 | + } | |
55 | + | |
56 | + function SectionOption (section) { | |
57 | + return h('Option', | |
58 | + { | |
59 | + className: location.section === section ? '-selected' : '', | |
60 | + 'ev-click': goTo({page: 'notifications', section}) | |
61 | + }, | |
62 | + [ | |
63 | + h('i.fa', { className: `fa-${ICONS[section]}` }), | |
64 | + strings[section] | |
65 | + ] | |
66 | + ) | |
67 | + } | |
68 | + } | |
69 | +} |
app/html/sideNav/sideNavNotifications.mcss | ||
---|---|---|
@@ -1,0 +1,30 @@ | ||
1 | +SideNav -notifications { | |
2 | + div.level { | |
3 | + section{ | |
4 | + header { | |
5 | + font-size: .8rem | |
6 | + } | |
7 | + | |
8 | + div.Option { | |
9 | + display: flex | |
10 | + | |
11 | + div.Button { | |
12 | + flex-grow: 1 | |
13 | + margin: .5rem 0 | |
14 | + } | |
15 | + | |
16 | + i.fa { | |
17 | + margin-right: .5rem | |
18 | + } | |
19 | + | |
20 | + div.count { | |
21 | + flex-grow: 1 | |
22 | + | |
23 | + display: flex | |
24 | + justify-content: flex-end | |
25 | + } | |
26 | + } | |
27 | + } | |
28 | + } | |
29 | +} | |
30 | + |
app/index.js | ||
---|---|---|
@@ -18,9 +18,10 @@ | ||
18 | 18 | }, |
19 | 19 | scroller: require('./html/scroller'), |
20 | 20 | sideNav: { |
21 | 21 | addressBook: require('./html/sideNav/sideNavAddressBook'), |
22 | - discovery: require('./html/sideNav/sideNavDiscovery') | |
22 | + discovery: require('./html/sideNav/sideNavDiscovery'), | |
23 | + notifications: require('./html/sideNav/sideNavNotifications') | |
23 | 24 | }, |
24 | 25 | warning: require('./html/warning'), |
25 | 26 | }, |
26 | 27 | obs: { |
@@ -32,21 +33,22 @@ | ||
32 | 33 | blogNew: require('./page/blogNew'), |
33 | 34 | blogSearch: require('./page/blogSearch'), |
34 | 35 | blogShow: require('./page/blogShow'), |
35 | 36 | channelShow: require('./page/channelShow'), |
36 | - error: require('./page/error'), | |
37 | - settings: require('./page/settings'), | |
38 | 37 | channelSubscriptions: require('./page/channelSubscriptions'), |
39 | 38 | // channel: require('./page/channel'), |
40 | 39 | // image: require('./page/image'), |
41 | 40 | // groupFind: require('./page/groupFind'), |
42 | 41 | // groupIndex: require('./page/groupIndex'), |
43 | 42 | // groupNew: require('./page/groupNew'), |
44 | 43 | // groupShow: require('./page/groupShow'), |
45 | 44 | // threadShow: require('./page/threadShow'), |
45 | + notifications: require('./page/notifications'), | |
46 | + error: require('./page/error'), | |
46 | 47 | userEdit: require('./page/userEdit'), |
47 | 48 | // userFind: require('./page/userFind'), |
48 | 49 | userShow: require('./page/userShow'), |
50 | + settings: require('./page/settings'), | |
49 | 51 | splash: require('./page/splash'), |
50 | 52 | statsShow: require('./page/statsShow'), |
51 | 53 | threadNew: require('./page/threadNew'), |
52 | 54 | threadShow: require('./page/threadShow') |
app/page/addressBook.js | ||
---|---|---|
@@ -1,7 +1,6 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | 2 | const { h, Value, computed, map } = require('mutant') |
3 | -const pull = require('pull-stream') | |
4 | 3 | |
5 | 4 | exports.gives = nest('app.page.addressBook') |
6 | 5 | |
7 | 6 | // declare consts to avoid magic-string errors |
@@ -13,10 +12,8 @@ | ||
13 | 12 | exports.needs = nest({ |
14 | 13 | 'about.html.avatar': 'first', |
15 | 14 | 'about.async.suggest': 'first', |
16 | 15 | 'about.obs.name': 'first', |
17 | - 'app.html.topNav': 'first', | |
18 | - // 'app.html.scroller': 'first', | |
19 | 16 | 'app.html.sideNav': 'first', |
20 | 17 | 'app.html.topNav': 'first', |
21 | 18 | 'contact.html.follow': 'first', |
22 | 19 | 'contact.obs.relationships': 'first', |
@@ -26,9 +23,9 @@ | ||
26 | 23 | }) |
27 | 24 | |
28 | 25 | exports.create = (api) => { |
29 | 26 | return nest('app.page.addressBook', function (location) { |
30 | - // location here can expected to be: { page: 'addressBook'} | |
27 | + // location here can expected to be: { page: 'addressBook', section: '...'} | |
31 | 28 | |
32 | 29 | const strings = api.translations.sync.strings() |
33 | 30 | const myKey = api.keys.sync.id() |
34 | 31 | const relationships = api.contact.obs.relationships(myKey) |
app/page/statsShow.js | ||
---|---|---|
@@ -9,11 +9,12 @@ | ||
9 | 9 | |
10 | 10 | exports.gives = nest('app.page.statsShow') |
11 | 11 | |
12 | 12 | exports.needs = nest({ |
13 | - 'sbot.obs.connection': 'first', | |
13 | + 'app.html.sideNav': 'first', | |
14 | 14 | 'history.sync.push': 'first', |
15 | 15 | 'message.html.markdown': 'first', |
16 | + 'sbot.obs.connection': 'first', | |
16 | 17 | 'translations.sync.strings': 'first' |
17 | 18 | }) |
18 | 19 | |
19 | 20 | const COMMENTS = 'comments' |
@@ -91,8 +92,9 @@ | ||
91 | 92 | |
92 | 93 | const canvas = h('canvas', { height: 200, width: 600, style: { height: '200px', width: '600px' } }) |
93 | 94 | |
94 | 95 | const page = h('Page -statsShow', [ |
96 | + api.app.html.sideNav(location), | |
95 | 97 | h('Scroller.content', [ |
96 | 98 | h('div.content', [ |
97 | 99 | h('h1', t.title), |
98 | 100 | h('section.totals', [COMMENTS, LIKES, SHARES].map(focus => { |
app/page/notifications.js | ||
---|---|---|
@@ -1,0 +1,140 @@ | ||
1 | +const nest = require('depnest') | |
2 | +const { h, onceTrue, throttle, Value, Array: MutantArray, map, resolve } = require('mutant') | |
3 | +const pull = require('pull-stream') | |
4 | + | |
5 | +exports.gives = nest('app.page.notifications') | |
6 | + | |
7 | +exports.needs = nest({ | |
8 | + // 'app.html.blogCard': 'first', | |
9 | + // 'app.html.topNav': 'first', | |
10 | + // 'app.html.scroller': 'first', | |
11 | + 'app.html.sideNav': 'first', | |
12 | + // 'blog.sync.isBlog': 'first', | |
13 | + // 'feed.pull.public': 'first', | |
14 | + // 'feed.pull.type': 'first', | |
15 | + // 'history.sync.push': 'first', | |
16 | + // 'keys.sync.id': 'first', | |
17 | + // 'message.sync.isBlocked': 'first', | |
18 | + 'sbot.obs.connection': 'first', | |
19 | + 'translations.sync.strings': 'first' | |
20 | + // 'unread.sync.isUnread': 'first' | |
21 | +}) | |
22 | + | |
23 | +exports.create = (api) => { | |
24 | + // var blogsCache = MutantArray() | |
25 | + | |
26 | + return nest('app.page.notifications', function (location) { | |
27 | + // location here can expected to be: { page: 'notifications'} | |
28 | + | |
29 | + var strings = api.translations.sync.strings() | |
30 | + | |
31 | + var commentsStore = MutantArray([]) | |
32 | + | |
33 | + onceTrue(api.sbot.obs.connection, server => { | |
34 | + console.log('methods', server.blogStats) | |
35 | + pull( | |
36 | + server.blogStats.readAllComments(), | |
37 | + pull.drain(m => { | |
38 | + commentsStore.push(m) | |
39 | + }) | |
40 | + ) | |
41 | + }) | |
42 | + | |
43 | + // server.blogStats.getBlogs({ keys: true, values: false }, (err, data) => { | |
44 | + // if (err) throw err | |
45 | + | |
46 | + // const blogIds = data.map(d => d[1]) | |
47 | + | |
48 | + // var source = server.blogStats.read({ | |
49 | + // // live: true, | |
50 | + // gte: [ 'C', undefined, undefined ], | |
51 | + // lte: [ 'C~', null, null ], | |
52 | + // reverse: true, | |
53 | + // values: true, | |
54 | + // keys: true, | |
55 | + // seqs: false | |
56 | + // }) | |
57 | + // console.log(blogIds) | |
58 | + | |
59 | + // pull( | |
60 | + // source, | |
61 | + // pull.filter(result => { | |
62 | + // return blogIds.includes(result.key[1]) | |
63 | + // }), | |
64 | + // pull.map(result => result.value), | |
65 | + // pull.drain(m => { | |
66 | + // commentsStore.push(m) | |
67 | + // }) | |
68 | + // ) | |
69 | + // }) | |
70 | + // }) | |
71 | + | |
72 | + // var blogs = api.app.html.scroller({ | |
73 | + // classList: ['content'], | |
74 | + // prepend: api.app.html.topNav(location), | |
75 | + // // stream: api.feed.pull.public, | |
76 | + // stream: api.feed.pull.type('blog'), | |
77 | + // filter: () => pull( | |
78 | + // // pull.filter(api.blog.sync.isBlog), | |
79 | + // pull.filter(msg => !msg.value.content.root), // show only root messages | |
80 | + // pull.filter(msg => !api.message.sync.isBlocked(msg)) | |
81 | + // ), | |
82 | + // // FUTURE : if we need better perf, we can add a persistent cache. At the moment this page is fast enough though. | |
83 | + // // See implementation of app.html.sideNav for example | |
84 | + // store: blogsCache, | |
85 | + // updateTop: update, | |
86 | + // updateBottom: update, | |
87 | + // render | |
88 | + // }) | |
89 | + | |
90 | + return h('Page -notifications', [ | |
91 | + api.app.html.sideNav(location), | |
92 | + h('div.content', map(throttle(commentsStore, 300), comment => { | |
93 | + const text = comment.value.content.text | |
94 | + | |
95 | + return h('p', { style: { margin: '1rem' } }, text) | |
96 | + })) | |
97 | + ]) | |
98 | + }) | |
99 | + | |
100 | +/* function update (soFar, newBlog) { */ | |
101 | +// soFar.transaction(() => { | |
102 | +// const { timestamp } = newBlog.value | |
103 | + | |
104 | +// var object = newBlog // Value(newBlog) | |
105 | + | |
106 | +// const index = indexOf(soFar, (blog) => newBlog.key === resolve(blog).key) | |
107 | +// // if blog already in cache, not needed again | |
108 | +// if (index >= 0) return | |
109 | + | |
110 | +// // Orders by: time received | |
111 | +// const justOlderPosition = indexOf(soFar, (msg) => newBlog.timestamp > resolve(msg).timestamp) | |
112 | + | |
113 | +// // Orders by: time published BUT the messagesByType stream streams _by time received_ | |
114 | +// // TODO - we need an index of all blogs otherwise the scroller doesn't work... | |
115 | +// // const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp) | |
116 | + | |
117 | +// if (justOlderPosition > -1) { | |
118 | +// soFar.insert(object, justOlderPosition) | |
119 | +// } else { | |
120 | +// soFar.push(object) | |
121 | +// } | |
122 | +// }) | |
123 | +// } | |
124 | + | |
125 | +// function render (blog) { | |
126 | +// const { recps, channel } = blog.value.content | |
127 | +// var onClick | |
128 | +// if (channel && !recps) { onClick = (ev) => api.history.sync.push(Object.assign({}, blog, { page: 'blogShow' })) } | |
129 | +// return api.app.html.blogCard(blog, { onClick }) | |
130 | +// } | |
131 | +// } | |
132 | + | |
133 | +// function indexOf (array, fn) { | |
134 | +// for (var i = 0; i < array.getLength(); i++) { | |
135 | +// if (fn(array.get(i))) { | |
136 | +// return i | |
137 | +// } | |
138 | +// } | |
139 | +// return -1 | |
140 | +} |
package.json | ||
---|---|---|
@@ -49,8 +49,9 @@ | ||
49 | 49 | "patch-profile": "^1.0.4", |
50 | 50 | "patch-settings": "^1.0.0", |
51 | 51 | "patch-suggest": "^1.1.0", |
52 | 52 | "patchcore": "^1.23.3", |
53 | + "pull-defer": "^0.2.2", | |
53 | 54 | "pull-next": "^1.0.1", |
54 | 55 | "pull-next-step": "^1.0.0", |
55 | 56 | "pull-obv": "^1.3.0", |
56 | 57 | "pull-stream": "^3.6.0", |
router/sync/routes.js | ||
---|---|---|
@@ -14,8 +14,9 @@ | ||
14 | 14 | 'app.page.blogShow': 'first', |
15 | 15 | 'app.page.settings': 'first', |
16 | 16 | 'app.page.channelSubscriptions': 'first', |
17 | 17 | 'app.page.channelShow': 'first', |
18 | + 'app.page.notifications': 'first', | |
18 | 19 | // 'app.page.channel': 'first', |
19 | 20 | // 'app.page.groupFind': 'first', |
20 | 21 | // 'app.page.groupIndex': 'first', |
21 | 22 | // 'app.page.groupNew': 'first', |
@@ -55,10 +56,11 @@ | ||
55 | 56 | [ location => location.page === 'channelSubscriptions', pages.channelSubscriptions ], |
56 | 57 | [ location => location.page === 'channelShow', pages.channelShow ], |
57 | 58 | [ location => location.channel, pages.channelShow ], |
58 | 59 | |
59 | - // Stats pages | |
60 | + // Stats / Notifications pages | |
60 | 61 | [ location => location.page === 'statsShow', pages.statsShow ], |
62 | + [ location => location.page === 'notifications', pages.notifications ], | |
61 | 63 | |
62 | 64 | // AddressBook pages |
63 | 65 | [ location => location.page === 'addressBook', pages.addressBook ], |
64 | 66 |
ssb-server-blog-stats.js | ||
---|---|---|
@@ -1,7 +1,8 @@ | ||
1 | 1 | const FlumeView = require('flumeview-level') |
2 | 2 | const get = require('lodash/get') |
3 | 3 | const pull = require('pull-stream') |
4 | +const defer = require('pull-defer') | |
4 | 5 | const isBlog = require('scuttle-blog/isBlog') |
5 | 6 | const { isMsg: isMsgRef } = require('ssb-ref') |
6 | 7 | |
7 | 8 | const getType = (msg) => get(msg, 'value.content.type') |
@@ -20,8 +21,9 @@ | ||
20 | 21 | read: 'source', |
21 | 22 | readBlogs: 'source', |
22 | 23 | getBlogs: 'async', |
23 | 24 | readComments: 'source', |
25 | + readAllComments: 'source', // TEMP | |
24 | 26 | readLikes: 'source' |
25 | 27 | }, |
26 | 28 | init: (server, config) => { |
27 | 29 | console.log('initialising blog-stats plugin') |
@@ -37,8 +39,9 @@ | ||
37 | 39 | read: view.read, |
38 | 40 | readBlogs, |
39 | 41 | getBlogs, |
40 | 42 | readComments, |
43 | + readAllComments, | |
41 | 44 | readLikes |
42 | 45 | // readShares |
43 | 46 | } |
44 | 47 | |
@@ -95,8 +98,13 @@ | ||
95 | 98 | return view.read(query) |
96 | 99 | } |
97 | 100 | |
98 | 101 | function getBlogs (options, cb) { |
102 | + if (typeof options === 'function') { | |
103 | + cb = options | |
104 | + options = {} | |
105 | + } | |
106 | + | |
99 | 107 | pull( |
100 | 108 | readBlogs(options), |
101 | 109 | pull.collect(cb) |
102 | 110 | ) |
@@ -117,8 +125,45 @@ | ||
117 | 125 | |
118 | 126 | return view.read(query) |
119 | 127 | } |
120 | 128 | |
129 | + function readAllComments () { | |
130 | + var source = defer.source() | |
131 | + | |
132 | + getBlogs({ keys: true, values: false }, (err, data) => { | |
133 | + if (err) throw err | |
134 | + | |
135 | + const blogIds = data.map(d => d[1]) | |
136 | + | |
137 | + const _source = pull( | |
138 | + view.read({ | |
139 | + // live: true, | |
140 | + gt: [ 'B~', undefined, undefined ], | |
141 | + lt: [ 'C~', null, null ], | |
142 | + reverse: true, | |
143 | + values: true, | |
144 | + keys: true, | |
145 | + seqs: false | |
146 | + }), | |
147 | + pull.filter(result => { | |
148 | + return blogIds.includes(result.key[1]) | |
149 | + }), | |
150 | + pull.map(result => result.value) | |
151 | + ) | |
152 | + | |
153 | + // pull( | |
154 | + // _source, | |
155 | + // pull.log() | |
156 | + // ) | |
157 | + source.resolve(_source) | |
158 | + // source.resolve(pull.values([1, 2, 3, 4, ':)'])) | |
159 | + }) | |
160 | + | |
161 | + return pull( | |
162 | + source | |
163 | + ) | |
164 | + } | |
165 | + | |
121 | 166 | function readLikes (blog, options = {}) { |
122 | 167 | var key = getBlogKey(blog) |
123 | 168 | |
124 | 169 | const query = Object.assign({}, { |
Built with git-ssb-web