git ssb

2+

mixmix / ticktack



Commit 6cedff7c896fdc49a0f8be9b4f479c7704797374

Merge pull request #97 from ticktackim/multi_pm_sidebar

show multi-msgs in sidebar
mix irving authored on 2/12/2018, 1:05:31 AM
GitHub committed on 2/12/2018, 1:05:31 AM
Parent: 65bba059de67096d23a74b9e419174aa97fa4977
Parent: 0f137965c8946bbf7e1023f2f6d9e3168fcf88d9

Files changed

about/html/avatar.mcsschanged
app/html/sideNav/sideNav.mcsschanged
app/html/sideNav/sideNavDiscovery.jschanged
app/page/threadNew.jschanged
app/page/userShow.jschanged
app/page/userShow.mcsschanged
message/html/subject.jschanged
message/index.jschanged
message/sync/getParticipants.jsadded
router/sync/routes.jschanged
styles/mixins.jschanged
about/html/avatar.mcssView
@@ -5,8 +5,12 @@
55 -tiny {
66 $circleTiny
77 }
88
9+ -halfSmall {
10+ $circleHalfSmall
11+ }
12+
913 -small {
1014 $circleSmall
1115 }
1216
app/html/sideNav/sideNav.mcssView
@@ -56,9 +56,9 @@
5656 position: relative
5757 width: 1.2rem
5858 height: 1.2rem
5959 border-radius: 1rem
60- top: -.2rem
60+ /* top: -.2rem */
6161 left: -.2rem
6262
6363 background-color: red
6464 color: #fff
@@ -92,13 +92,23 @@
9292 justify-content: center
9393 align-items: center
9494 }
9595
96+ img {}
97+ div.many-images {
98+ /* this width refernces avatarSmall */
99+ width: 2.8rem
100+ height: 2.8rem
101+
102+ display: flex
103+ flex-wrap: wrap
104+ justify-content: center
105+ align-items: center
106+
107+ img {
108+ }
109+ }
96110
97- a img {
98-
99- }
100-
101111 i {
102112 $circleSmall
103113 $colorPrimary
104114 font-size: 1.3rem
app/html/sideNav/sideNavDiscovery.jsView
@@ -19,8 +19,9 @@
1919 'keys.sync.id': 'first',
2020 'history.sync.push': 'first',
2121 'history.obs.store': 'first',
2222 'message.html.subject': 'first',
23+ 'message.sync.getParticipants': 'first',
2324 'sbot.obs.localPeers': 'first',
2425 'translations.sync.strings': 'first',
2526 'unread.sync.isUnread': 'first'
2627 })
@@ -58,10 +59,10 @@
5859 if (!isMatch(location)) return
5960
6061 const strings = api.translations.sync.strings()
6162 const myKey = api.keys.sync.id()
62-
6363 var nearby = api.sbot.obs.localPeers()
64+ const getParticipants = api.message.sync.getParticipants
6465
6566 // Unread message counts
6667 function updateCache (cache, msg) {
6768 if(api.unread.sync.isUnread(msg))
@@ -70,10 +71,13 @@
7071 cache.delete(msg.key)
7172 }
7273
7374 function updateUnreadMsgsCache (msg) {
74- updateCache(getUnreadMsgsCache(msg.value.author), msg)
75- updateCache(getUnreadMsgsCache(msg.value.content.root || msg.key), msg)
75+ const participantsKey = getParticipants(msg).key
76+ updateCache(getUnreadMsgsCache(participantsKey), msg)
77+
78+ const rootKey = get(msg, 'value.content.root', msg.key)
79+ updateCache(getUnreadMsgsCache(rootKey), msg)
7680 }
7781
7882 pull(
7983 next(api.feed.pull.private, {reverse: true, limit: 1000, live: false, property: ['value', 'timestamp']}),
@@ -109,24 +113,22 @@
109113 map(nearby, feedId => Option({
110114 notifications: notifications(feedId),
111115 imageEl: api.about.html.avatar(feedId, 'small'),
112116 label: api.about.obs.name(feedId),
113- selected: location.feed === feedId && !isDiscoverLocation(location),
117+ selected: get(location, 'participants', []).join() === feedId && !isDiscoverLocation(location),
114118 location: computed(recentMsgCache, recent => {
115119 const lastMsg = recent.find(msg => msg.value.author === feedId)
116120 return lastMsg
117- ? Object.assign(lastMsg, { feed: feedId })
118- : { page: 'threadNew', feed: feedId }
121+ ? Object.assign(lastMsg, { participants: [feedId] })
122+ : { page: 'threadNew', participants: [feedId] }
119123 }),
120124 }), { comparer: (a, b) => a === b }),
121125
122126 // ---------------------
123127 computed(nearby, n => !isEmpty(n) ? h('hr') : null),
124128
125129 // Discover
126130 Option({
127- // notifications: '!', //TODO - count this!
128- // imageEl: h('i.fa.fa-binoculars'),
129131 imageEl: h('i', [
130132 h('img', { src: path.join(__dirname, '../../../assets', 'discover.png') })
131133 ]),
132134 label: strings.blogIndex.title,
@@ -162,28 +164,48 @@
162164 filter: privateMsgFilter,
163165 store: recentMsgCache,
164166 updateTop: updateRecentMsgCache,
165167 updateBottom: updateRecentMsgCache,
166- render: (msgObs) => {
167- const msg = resolve(msgObs)
168- const { author } = msg.value
169- if (nearby.has(author)) return
168+ render
169+ })
170+
171+ function render (msgObs) {
172+ const msg = resolve(msgObs)
173+ const participants = getParticipants(msg)
174+ // TODO msg has been decorated with a flat participantsKey, could re-hydrate
170175
176+ if (participants.length === 1 && nearby.has(participants.key)) return
177+ const locParticipantsKey = get(location, 'participants', []).join(' ') //TODO collect logic
178+
179+ if (participants.length === 1) {
180+ const author = participants[0]
171181 return Option({
172182 //the number of threads with each peer
173183 notifications: notifications(author),
174184 imageEl: api.about.html.avatar(author),
175185 label: api.about.obs.name(author),
176- selected: location.feed === author,
177- location: Object.assign({}, msg, { feed: author }) // TODO make obs?
186+ selected: locParticipantsKey === author,
187+ location: Object.assign({}, msg, { participants }) // TODO make obs?
178188 })
179189 }
180- })
190+ else {
191+ const rootMsg = get(msg, 'value.content.root', msg)
192+ return Option({
193+ //the number of threads with each peer
194+ notifications: notifications(participants),
195+ imageEl: participants.map(p => api.about.html.avatar(p, 'halfSmall')),
196+ label: api.message.html.subject(rootMsg),
197+ selected: locParticipantsKey === participants.key,
198+ location: Object.assign({}, msg, { participants }) // TODO make obs?
199+ })
200+ }
201+ }
181202
182203 function updateRecentMsgCache (soFar, newMsg) {
183204 soFar.transaction(() => {
184- const { author, timestamp } = newMsg.value
185- const index = indexOf(soFar, (msg) => author === resolve(msg).value.author)
205+ const { timestamp } = newMsg.value
206+ newMsg.participantsKey = getParticipants(newMsg).key
207+ const index = indexOf(soFar, (msg) => newMsg.participantsKey === resolve(msg).participantsKey)
186208 var object = Value()
187209
188210 if (index >= 0) {
189211 // reference already exists, lets use this instead!
@@ -218,28 +240,31 @@
218240 return cache
219241 }
220242
221243 function notifications (key) {
244+ key = typeof key === 'string'
245+ ? key
246+ : key.key // participants.key case
222247 return computed(getUnreadMsgsCache(key), cache => cache.length)
223248 }
224249
225250 function LevelTwoSideNav () {
226- const { key, value, feed: targetUser, page } = location
251+ const { key, value, participants, page } = location
227252 const root = get(value, 'content.root', key)
228- if (!targetUser) return
253+ if (isEmpty(participants)) return
229254 if (page === 'userShow') return
230255
231-
232256 const prepend = Option({
233257 selected: page === 'threadNew',
234- location: {page: 'threadNew', feed: targetUser},
258+ location: {page: 'threadNew', participants},
235259 label: h('Button', strings.threadNew.action.new),
236260 })
237261
238- var userLastMsgCache = usersLastMsgCache.get(targetUser)
262+ var participantsKey = participants.join(' ') // TODO collect this repeated logic
263+ var userLastMsgCache = usersLastMsgCache.get(participantsKey)
239264 if (!userLastMsgCache) {
240265 userLastMsgCache = MutantArray()
241- usersLastMsgCache.put(targetUser, userLastMsgCache)
266+ usersLastMsgCache.put(participantsKey, userLastMsgCache)
242267 }
243268
244269 return api.app.html.scroller({
245270 classList: [ 'level', '-two' ],
@@ -247,28 +272,27 @@
247272 stream: api.feed.pull.private,
248273 filter: () => pull(
249274 pull.filter(msg => !msg.value.content.root),
250275 pull.filter(msg => msg.value.content.type === 'post'),
251- pull.filter(msg => msg.value.content.recps),
252- pull.filter(msg => msg.value.content.recps
253- .map(recp => typeof recp === 'object' ? recp.link : recp)
254- .some(recp => recp === targetUser)
255- )
276+ pull.filter(msg => getParticipants(msg).key === participantsKey)
256277 ),
257278 store: userLastMsgCache,
258279 updateTop: updateLastMsgCache,
259280 updateBottom: updateLastMsgCache,
260- render: (rootMsgObs) => {
261- const rootMsg = resolve(rootMsgObs)
262- return Option({
263- notifications: notifications(rootMsg.key),
264- label: api.message.html.subject(rootMsg),
265- selected: rootMsg.key === root,
266- location: Object.assign(rootMsg, { feed: targetUser }),
267- })
268- }
281+ render
269282 })
270283
284+ function render (rootMsgObs) {
285+ const rootMsg = resolve(rootMsgObs)
286+ const participants = getParticipants(rootMsg)
287+ return Option({
288+ notifications: notifications(rootMsg.key),
289+ label: api.message.html.subject(rootMsg),
290+ selected: rootMsg.key === root,
291+ location: Object.assign(rootMsg, { participants }),
292+ })
293+ }
294+
271295 function updateLastMsgCache (soFar, newMsg) {
272296 soFar.transaction(() => {
273297 const { timestamp } = newMsg.value
274298 const index = indexOf(soFar, (msg) => timestamp === resolve(msg).value.timestamp)
@@ -305,18 +329,19 @@
305329
306330 return h('Option', { className }, [
307331 h('div.circle', [
308332 when(notifications, h('div.alert', notifications)),
309- imageEl
333+ Array.isArray(imageEl)
334+ ? h('div.many-images', imageEl.slice(0,4)) // not ideal? not enough space to show more though
335+ : imageEl
310336 ]),
311337 h('div.label', { 'ev-click': goToLocation }, label)
312338 ])
313339 }
314340
315341 function privateMsgFilter () {
316342 return pull(
317343 pull.filter(msg => msg.value.content.type === 'post'),
318- pull.filter(msg => msg.value.author != myKey),
319344 pull.filter(msg => msg.value.content.recps)
320345 )
321346 }
322347 }
app/page/threadNew.jsView
@@ -1,7 +1,8 @@
11 const nest = require('depnest')
22 const { h, Struct, Value, Array: MutantArray, computed, map, resolve } = require('mutant')
33 const suggestBox = require('suggest-box')
4+const isEmpty = require('lodash/isEmpty')
45
56 exports.gives = nest('app.page.threadNew')
67
78 exports.needs = nest({
@@ -21,24 +22,29 @@
2122
2223 return nest('app.page.threadNew', threadNew)
2324
2425 function threadNew (location) {
25- const { feed, channel } = location
26-
27- if (feed) return threadNewFeed(location)
26+ if (isEmpty(location.participants)) return
27+
28+ return threadNewFeed(location)
2829 }
2930
3031 function threadNewFeed (location) {
3132 const strings = api.translations.sync.strings()
3233 const myId = api.keys.sync.id()
3334
34- const { feed } = location
35+ const { participants } = location
3536
3637 const meta = Struct({
3738 type: 'post',
3839 recps: MutantArray ([
3940 myId,
40- { link: feed, name: resolve(api.about.obs.name(feed)) }
41+ ...participants.map(p => {
42+ return {
43+ link: p,
44+ name: resolve(api.about.obs.name(p))
45+ }
46+ })
4147 ]),
4248 subject: Value()
4349 })
4450
app/page/userShow.jsView
@@ -41,9 +41,9 @@
4141 { page: 'userEdit', feed },
4242 // h('i.fa.fa-pencil')
4343 h('img', { src: path.join(__dirname, '../../assets', 'edit.png') })
4444 )
45- const directMessageButton = Link({ page: 'threadNew', feed }, h('Button', strings.userShow.action.directMessage))
45+ const directMessageButton = Link({ page: 'threadNew', participants: [feed] }, h('Button', strings.userShow.action.directMessage))
4646
4747 const BLOG_TYPES = ['blog', 'post']
4848
4949 // TODO return some of this ?
@@ -69,10 +69,10 @@
6969 ]),
7070 h('div.introduction', computed(api.about.obs.description(feed), d => api.message.html.markdown(d || ''))),
7171 feed !== myId
7272 ? h('div.actions', [
73+ h('div.directMessage', directMessageButton),
7374 api.contact.html.follow(feed),
74- h('div.directMessage', directMessageButton),
7575 api.contact.html.block(feed)
7676 ])
7777 : '',
7878 ]),
app/page/userShow.mcssView
@@ -28,18 +28,20 @@
2828 }
2929
3030 div.actions {
3131 display: flex
32+
33+ div {
34+ margin: 0 .5rem
35+ }
3236
3337 div.Follow {
34- margin-right: 1rem
3538 }
3639
3740 div.directMessage {
3841 }
3942
4043 div.Block {
41- margin-left: 1rem
4244 }
4345 }
4446 }
4547 }
message/html/subject.jsView
@@ -1,23 +1,48 @@
11 const nest = require('depnest')
2-const { h, when, send, resolve, Value, computed } = require('mutant')
2+const { computed, Value } = require('mutant')
33 const { title } = require('markdown-summary')
4+const { isMsg } = require('ssb-ref')
45
56
67 exports.gives = nest('message.html.subject')
78
89 exports.needs = nest({
910 'message.html.markdown': 'first',
11+ 'message.sync.unbox': 'first',
12+ 'sbot.async.get': 'first',
1013 })
1114
1215 exports.create = function (api) {
1316 return nest('message.html.subject', subject)
1417
1518 function subject (msg) {
19+ if (msg === undefined) debugger
20+ // test if it's a message ref, or a full message object
21+ if (isMsg(msg)) {
22+ var subject = Value()
23+
24+ api.sbot.async.get(msg, (err, value) => {
25+ if (err) throw err
26+
27+ subject.set(getMsgSubject({
28+ key: msg,
29+ value: api.message.sync.unbox(value)
30+ }))
31+ })
32+
33+ return subject
34+ }
35+ else
36+ return getMsgSubject(msg)
37+ }
38+
39+ function getMsgSubject (msg) {
1640 const { subject, text } = msg.value.content
1741 if(!(subject || text)) return
1842
1943 return subject
2044 ? api.message.html.markdown(subject)
2145 : api.message.html.markdown(title(text))
2246 }
2347 }
48+
message/index.jsView
@@ -7,7 +7,10 @@
77 compose: require('./html/compose'),
88 likes: require('./html/likes'),
99 subject: require('./html/subject'),
1010 timeago: require('./html/timeago')
11- }
11+ },
12+ sync: {
13+ getParticipants: require('./sync/getParticipants'),
14+ },
1215 }
1316
message/sync/getParticipants.jsView
@@ -1,0 +1,27 @@
1+const nest = require('depnest')
2+const get = require('lodash/get')
3+
4+exports.gives = nest('message.sync.getParticipants')
5+
6+exports.needs = nest({
7+ 'keys.sync.id': 'first',
8+})
9+
10+exports.create = function (api) {
11+ return nest('message.sync.getParticipants', getParticipants)
12+
13+ function getParticipants (msg) {
14+ const myKey = api.keys.sync.id()
15+
16+ var participants = get(msg, 'value.content.recps')
17+ .map(r => typeof r === 'string' ? r : r.link)
18+ .filter(r => r != myKey)
19+ .sort()
20+
21+ participants.key = participants.join(' ')
22+ return participants
23+ }
24+}
25+
26+
27+
router/sync/routes.jsView
@@ -59,9 +59,9 @@
5959 [ location => location.page === 'addressBook', pages.addressBook ],
6060
6161 // Private Thread pages
6262 // [ location => location.page === 'threadNew' && location.channel, pages.threadNew ],
63- [ location => location.page === 'threadNew' && isFeed(location.feed), pages.threadNew ],
63+ [ location => location.page === 'threadNew' && location.participants.every(isFeed), pages.threadNew ],
6464 [ location => isMsg(location.key), pages.threadShow ],
6565
6666 // User pages
6767 // [ location => location.page === 'userFind', pages.userFind ],
styles/mixins.jsView
@@ -83,8 +83,15 @@
8383 width: 2.8rem
8484 height: 2.8rem
8585 border-radius: 4rem
8686 }
87+$circleHalfSmall {
88+ min-width: 1.4rem
89+ min-height: 1.4rem
90+ width: 1.4rem
91+ height: 1.4rem
92+ border-radius: 2rem
93+}
8794
8895 $circleMedium {
8996 min-width: 3.5rem
9097 min-height: 3.5rem

Built with git-ssb-web