git ssb

2+

mixmix / ticktack



Commit 9b889230e5350365ea198b03786a51efe3ffe969

Merge branch 'master' into feature-channel-subscriptions

Andre Alves Garzia authored on 2/2/2018, 1:25:18 AM
GitHub committed on 2/2/2018, 1:25:18 AM
Parent: 7a5359e5b8b6477de66abab7748e8f1c595a1875
Parent: 9660479589cb3e6a2d8fe371854da4ce7a38522b

Files changed

app/html/app.jschanged
app/html/comments.jschanged
app/html/header.jschanged
app/html/scroller.mcsschanged
app/html/sideNav/sideNav.mcsschanged
app/html/sideNav/sideNavDiscovery.jschanged
app/html/sideNav/sideNavDiscovery.mcsschanged
app/html/sideNav/sideNavAddressBook.jsadded
app/html/sideNav/sideNavAddressBook.mcssadded
app/html/topNav/topNavBlog.jschanged
app/html/topNav/zz_topNavBack.jschanged
app/html/topNav/topNavAddressBook.jsadded
app/html/topNav/topNavAddressBook.mcssadded
app/index.jschanged
app/page/blogIndex.jschanged
app/page/blogIndex.mcsschanged
app/page/blogNew.mcsschanged
app/page/blogSearch.mcsschanged
app/page/blogShow.jschanged
app/page/blogShow.mcsschanged
app/page/page.mcsschanged
app/page/threadShow.jschanged
app/page/userShow.jschanged
app/page/userShow.mcsschanged
app/page/addressBook.jsadded
app/page/addressBook.mcssadded
blog/html/blog.jschanged
contact/html/follow.jschanged
contact/index.jschanged
contact/obs/relationships.jsadded
message/html/compose.jschanged
package.jsonchanged
router/sync/routes.jschanged
styles/button.mcsschanged
styles/patchSuggest.mcsschanged
translations/en.jschanged
app/html/app.jsView
@@ -28,8 +28,9 @@
2828
2929 view = Value()
3030 var app = h('App', view)
3131 api.history.obs.location()(renderLocation)
32+ api.history.obs.location()(loc => console.log('location:', loc))
3233
3334 startApp()
3435
3536 return app
app/html/comments.jsView
@@ -21,10 +21,10 @@
2121 exports.create = (api) => {
2222 return nest('app.html.comments', comments)
2323
2424 function comments (thread) {
25+ const strings = api.translations.sync.strings()
2526 const { messages, channel, lastId: branch } = thread
26- const strings = api.translations.sync.strings()
2727
2828 // TODO - move this up into Patchcore
2929 const messagesTree = computed(throttle(messages, 200), msgs => {
3030 return msgs
@@ -48,13 +48,16 @@
4848 // return messages.length > 5
4949 // })
5050 const { compose } = api.message.html
5151
52+ const feedIdsInThread = computed(thread.messages, msgs => {
53+ return msgs.map(m => m.value.author)
54+ })
5255
5356 return h('Comments', [
5457 // when(twoComposers, compose({ meta, shrink: true, canAttach: false })),
5558 map(messagesTree, msg => Comment(msg, root, branch)),
56- compose({ meta, shrink: false, canAttach: true, placeholder: strings.writeComment }),
59+ compose({ meta, feedIdsInThread, shrink: false, canAttach: true, placeholder: strings.writeComment }),
5760 ])
5861 }
5962
6063 function Comment (msgObs, root, branch) {
app/html/header.jsView
@@ -24,21 +24,20 @@
2424
2525 if (loc().page === 'splash') return
2626
2727 const isSettings = computed(loc, loc => SETTINGS_PAGES.includes(loc.page))
28- const isProfile = computed(loc, loc => loc.page === 'userShow' && loc.feed == myKey)
28+ const isAddressBook = computed(loc, loc => loc.page === 'addressBook')
29+ const isFeed = computed([isAddressBook, isSettings], (p, s) => !p && !s)
2930
30- const isFeed = computed([isProfile, isSettings], (p, s) => !p && !s)
31-
3231 return h('Header', [
3332 h('nav', [
3433 h('img.feed', {
3534 src: when(isFeed, assetPath('feed_on.png'), assetPath('feed.png')),
3635 'ev-click': () => push({page: 'blogIndex'}),
3736 }),
38- h('img.profile', {
39- src: when(isProfile, assetPath('address_bk_on.png'), assetPath('address_bk.png')),
40- 'ev-click': () => push({page: 'userShow', feed: myKey})
37+ h('img.addressBook', {
38+ src: when(isAddressBook, assetPath('address_bk_on.png'), assetPath('address_bk.png')),
39+ 'ev-click': () => push({page: 'addressBook'})
4140 }),
4241 h('img.settings', {
4342 src: when(isSettings, assetPath('settings_on.png'), assetPath('settings.png')),
4443 'ev-click': () => push({page: 'settings'})
app/html/scroller.mcssView
@@ -1,34 +1,38 @@
11 Scroller {
2-
32 overflow: auto
43 width: 100%
54 height: 100%
65 min-height: 0px
76
8- /* div.wrapper { */
9- /* align-self: center */
7+ section.top {
8+ /* position: sticky */
9+ left: 0
10+ right: 0
11+ top: 0
12+ z-index: 99
1013
11- /* flex: 1 1 */
12- /* $threadWidth */
13- /* padding-top: .5rem */
14+ background-color: #fff
15+ }
1416
15- /* section.content { */
16- /* div { */
17- /* border-bottom: solid 1px gainsboro */
18- /* } */
19- /* } */
20- /* } */
21-}
17+ section.content {
18+ background-color: #fff
19+ $maxWidth
20+ margin: .8rem auto
21+ padding: .5rem 2rem
2222
23-Scroller -errors {
24- div.wrapper {
25- width: initial
26- max-width: 100%
23+ display: flex
24+ flex-wrap: wrap
2725
28- section.content div {
29- border: none
30- }
3126 }
27+
28+ section.bottom {
29+ }
3230 }
3331
32+/* Scroller -errors { */
33+/* section.content div { */
34+/* border: none */
35+/* } */
36+/* } */
3437
38+
app/html/sideNav/sideNav.mcssView
@@ -32,4 +32,95 @@
3232 -two {}
3333 }
3434 }
3535
36+Option {
37+ min-width: 8rem
38+ padding: .5rem 1rem
39+ display: flex
40+ align-items: center
41+
42+ :hover {
43+ cursor: pointer
44+ $backgroundSelected
45+ }
46+
47+ -selected {
48+ $backgroundSelected
49+ }
50+
51+ div.spacer {
52+ display: flex
53+ align-self: center
54+ div.alert {
55+ position: relative
56+ width: 1.2rem
57+ height: 1.2rem
58+ border-radius: 1rem
59+ top: -.2rem
60+ left: -.2rem
61+
62+ background-color: red
63+ color: #fff
64+ font-size: .8rem
65+
66+ display: flex
67+ justify-content: center
68+ align-items: center
69+ align-self: flex-start
70+ }
71+ }
72+
73+ div.circle {
74+ width: 3.6rem
75+ position: relative
76+
77+ div.alert {
78+ position: absolute
79+ width: 1.2rem
80+ height: 1.2rem
81+ border-radius: 1rem
82+ top: -.2rem
83+ left: -.2rem
84+
85+ background-color: red
86+ color: #fff
87+ font-size: .8rem
88+
89+ display: flex
90+ justify-content: center
91+ align-items: center
92+ }
93+
94+
95+ a img {
96+
97+ }
98+
99+ i {
100+ $circleSmall
101+ $colorPrimary
102+ font-size: 1.3rem
103+ display: flex
104+ justify-content: center
105+ align-items: center
106+ }
107+ }
108+
109+ div.label {
110+ $markdownSmall
111+ (a) {
112+ $colorFontBasic
113+ :hover { text-decoration: none }
114+ }
115+
116+ flex-grow: 1
117+
118+ display: flex
119+ align-items: center
120+ min-height: 3rem
121+
122+ div.Button {
123+ flex-grow: 1
124+ }
125+ }
126+}
app/html/sideNav/sideNavDiscovery.jsView
@@ -17,8 +17,9 @@
1717 'about.obs.name': 'first',
1818 'feed.pull.private': 'first',
1919 'keys.sync.id': 'first',
2020 'history.sync.push': 'first',
21+ 'history.obs.store': 'first',
2122 'message.html.subject': 'first',
2223 'sbot.obs.localPeers': 'first',
2324 'translations.sync.strings': 'first',
2425 'unread.sync.isUnread': 'first'
@@ -39,9 +40,23 @@
3940 },
4041 'app.html.sideNav': sideNav,
4142 })
4243
44+ function isMatch (location) {
45+ if (location.page) {
46+ if (location.page.match(/^blog/)) return true
47+ if (location.page.match(/^thread/)) return true
48+ if (location.page.match(/^user/)) return true
49+ }
50+ if (location.key) {
51+ return true
52+ }
53+ return false
54+ }
55+
4356 function sideNav (location) {
57+ if (!isMatch(location)) return
58+
4459 const strings = api.translations.sync.strings()
4560 const myKey = api.keys.sync.id()
4661
4762 var nearby = api.sbot.obs.localPeers()
@@ -76,13 +91,15 @@
7691 LevelTwoSideNav()
7792 ])
7893
7994 function LevelOneSideNav () {
80- function isDiscoverSideNav (loc) {
95+ function isDiscoverLocation (loc) {
8196 const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'userShow']
8297
83- return PAGES_UNDER_DISCOVER.includes(location.page)
84- || get(location, 'value.private') === undefined
98+ if (PAGES_UNDER_DISCOVER.includes(location.page)) return true
99+ if (location.page === 'threadNew') return false
100+ if (get(location, 'value.private') === undefined) return true
101+ return false
85102 }
86103
87104 const prepend = [
88105 // Nearby
@@ -90,9 +107,9 @@
90107 map(nearby, feedId => Option({
91108 notifications: notifications(feedId),
92109 imageEl: api.about.html.avatar(feedId, 'small'),
93110 label: api.about.obs.name(feedId),
94- selected: location.feed === feedId,
111+ selected: location.feed === feedId && !isDiscoverLocation(location),
95112 location: computed(recentMsgCache, recent => {
96113 const lastMsg = recent.find(msg => msg.value.author === feedId)
97114 return lastMsg
98115 ? Object.assign(lastMsg, { feed: feedId })
@@ -110,9 +127,9 @@
110127 imageEl: h('i', [
111128 h('img', { src: path.join(__dirname, '../../../assets', 'discover.png') })
112129 ]),
113130 label: strings.blogIndex.title,
114- selected: isDiscoverSideNav(location),
131+ selected: isDiscoverLocation(location),
115132 location: { page: 'blogIndex' },
116133 }),
117134
118135 // My subscriptions
app/html/sideNav/sideNavDiscovery.mcssView
@@ -1,7 +1,13 @@
11 SideNav -discovery {
22 div.level {
33 section {
4+ padding: 0
5+ margin: 0
6+
7+ div.Option {
8+ flex-grow: 1
9+ }
410 }
511
612 -one {}
713 -two {
@@ -13,97 +19,6 @@
1319 }
1420 }
1521 }
1622
17-Option {
18- min-width: 8rem
19- padding: .5rem 1rem
20- display: flex
21- align-items: center
2223
23- :hover {
24- cursor: pointer
25- $backgroundSelected
26- }
2724
28- -selected {
29- $backgroundSelected
30- }
31-
32- div.spacer {
33- display: flex
34- align-self: center
35- div.alert {
36- position: relative
37- width: 1.2rem
38- height: 1.2rem
39- border-radius: 1rem
40- top: -.2rem
41- left: -.2rem
42-
43- background-color: red
44- color: #fff
45- font-size: .8rem
46-
47- display: flex
48- justify-content: center
49- align-items: center
50- align-self: flex-start
51- }
52- }
53-
54- div.circle {
55- width: 3.6rem
56- position: relative
57-
58- div.alert {
59- position: absolute
60- width: 1.2rem
61- height: 1.2rem
62- border-radius: 1rem
63- top: -.2rem
64- left: -.2rem
65-
66- background-color: red
67- color: #fff
68- font-size: .8rem
69-
70- display: flex
71- justify-content: center
72- align-items: center
73- }
74-
75-
76- a img {
77-
78- }
79-
80- i {
81- $circleSmall
82- $colorPrimary
83- font-size: 1.3rem
84- display: flex
85- justify-content: center
86- align-items: center
87- }
88- }
89-
90- div.label {
91- $markdownSmall
92- (a) {
93- $colorFontBasic
94- :hover { text-decoration: none }
95- }
96-
97- flex-grow: 1
98-
99- display: flex
100- align-items: center
101- min-height: 3rem
102-
103- div.Button {
104- flex-grow: 1
105- }
106- }
107-}
108-
109-
app/html/sideNav/sideNavAddressBook.jsView
@@ -1,0 +1,87 @@
1+const nest = require('depnest')
2+const { h, computed, Struct, map, when, Dict, Array: MutantArray, Value, Set, resolve } = require('mutant')
3+const pull = require('pull-stream')
4+const next = require('pull-next-step')
5+const get = require('lodash/get')
6+const isEmpty = require('lodash/isEmpty')
7+const path = require('path')
8+
9+exports.gives = nest({
10+ 'app.html.sideNav': true,
11+})
12+
13+exports.needs = nest({
14+ // 'app.html.scroller': 'first',
15+ // 'about.html.avatar': 'first',
16+ // 'about.obs.name': 'first',
17+ // 'feed.pull.private': 'first',
18+ 'history.sync.push': 'first',
19+ // 'message.html.subject': 'first',
20+ // 'sbot.obs.localPeers': 'first',
21+ 'translations.sync.strings': 'first',
22+ // 'unread.sync.isUnread': 'first'
23+})
24+
25+exports.create = (api) => {
26+ return nest({
27+ 'app.html.sideNav': sideNav,
28+ })
29+
30+ function sideNav (location, relationships) {
31+ if (location.page !== 'addressBook') return
32+ if (!location.section) location.section = 'friends'
33+
34+ const strings = api.translations.sync.strings().addressBook
35+ const goTo = (loc) => () => api.history.sync.push(loc)
36+
37+ // TODO - show local peers?
38+ // var nearby = api.sbot.obs.localPeers()
39+
40+ return h('SideNav -addressBook', [
41+ LevelOneSideNav(),
42+ ])
43+
44+ function LevelOneSideNav () {
45+ return h('div.level.-one', [
46+ h('section', [
47+ SectionOption('search', [
48+ h('Button -primary', {}, strings.action.addUser)
49+ ]),
50+ h('hr'),
51+ ]),
52+
53+ //Friends
54+ h('section', [
55+ h('header',strings.heading.people),
56+ SectionOption('friends'),
57+ SectionOption('following'),
58+ SectionOption('followers'),
59+ ])
60+ ])
61+ }
62+
63+ function SectionOption (section, body) {
64+ const className = section === location.section
65+ ? '-selected'
66+ : ''
67+ return h('Option',
68+ { className, 'ev-click': goTo({page: 'addressBook', section }) },
69+ body || defaulBody(section)
70+ )
71+
72+ function defaulBody (section) {
73+ return [
74+ h('i.fa.fa-angle-right'),
75+ strings.section[section],
76+ h('div.count', count(section))
77+ ]
78+ }
79+ }
80+
81+ function count (relationshipType) {
82+ return computed(relationships, rels => rels[relationshipType].length)
83+ }
84+ }
85+}
86+
87+
app/html/sideNav/sideNavAddressBook.mcssView
@@ -1,0 +1,30 @@
1+SideNav -addressBook {
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/html/topNav/topNavBlog.jsView
@@ -5,17 +5,15 @@
55 exports.gives = nest('app.html.topNav')
66
77 exports.needs = nest({
88 'history.sync.push': 'first',
9- 'history.sync.back': 'first',
109 'translations.sync.strings': 'first',
1110 })
1211
1312 exports.create = (api) => {
1413 return nest('app.html.topNav', (location) => {
1514 const strings = api.translations.sync.strings()
1615 const goTo = (loc) => () => api.history.sync.push(loc)
17- const back = () => api.history.sync.back()
1816
1917 if (!['blogIndex', 'blogSearch'].includes(location.page)) return
2018
2119 return h('TopNav -blog', [
@@ -32,18 +30,7 @@
3230 h('div.right', [
3331 h('Button -strong', { 'ev-click': () => api.history.sync.push({ page: 'blogNew' }) }, strings.blogNew.actions.writeBlog),
3432 ])
3533 ])
36-
37- return h('TopNav -back', [
38- h('div.left', [
39- h('div.-back', { 'ev-click': back }, [
40- h('i.fa.fa-chevron-left'),
41- strings.blogIndex.title
42- ]),
43- ]),
44- h('div.right', [
45- ])
46- ])
4734 })
4835 }
4936
app/html/topNav/zz_topNavBack.jsView
@@ -4,9 +4,8 @@
44
55 exports.gives = nest('app.html.topNav')
66
77 exports.needs = nest({
8- 'history.sync.push': 'first',
98 'history.sync.back': 'first',
109 'translations.sync.strings': 'first',
1110 })
1211
app/html/topNav/topNavAddressBook.jsView
@@ -1,0 +1,29 @@
1+const nest = require('depnest')
2+const { h, computed, when } = require('mutant')
3+const get = require('lodash/get')
4+
5+exports.gives = nest('app.html.topNav')
6+
7+exports.needs = nest({
8+ 'translations.sync.strings': 'first',
9+})
10+
11+exports.create = (api) => {
12+ return nest('app.html.topNav', (location, input) => {
13+ if (location.page !== 'addressBook') return
14+
15+ const strings = api.translations.sync.strings()
16+
17+ return h('TopNav -addressBook', [
18+ h('div.search', [
19+ h('i.fa.fa-search'),
20+ h('input', {
21+ placeholder: strings.addressBook.action.find[location.section],
22+ autofocus: 'autofocus',
23+ 'ev-input': e => input.set(e.target.value)
24+ }),
25+ ])
26+ ])
27+ })
28+}
29+
app/html/topNav/topNavAddressBook.mcssView
@@ -1,0 +1,27 @@
1+TopNav -addressBook {
2+ div.search {
3+ flex-grow: 1
4+ $backgroundPrimaryText
5+
6+ margin-bottom: .5rem
7+
8+ display: flex
9+ align-items: center
10+
11+ i.fa-search {
12+ margin-left: .8rem
13+ }
14+
15+ input {
16+ flex-grow: 1
17+
18+ $fontBasic
19+ margin: .6rem
20+
21+ border: none
22+ outline: none
23+ }
24+ }
25+
26+}
27+
app/index.jsView
@@ -11,17 +11,20 @@
1111 lightbox: require('./html/lightbox'),
1212 blogCard: require('./html/blogCard'),
1313 channelCard: require('./html/channelCard'),
1414 topNav: {
15+ topNavAddressBook: require('./html/topNav/topNavAddressBook'),
1516 topNavBlog: require('./html/topNav/topNavBlog'),
1617 topNavBack: require('./html/topNav/zz_topNavBack'),
1718 },
1819 scroller: require('./html/scroller'),
1920 sideNav: {
21+ addressBook: require('./html/sideNav/sideNavAddressBook'),
2022 discovery: require('./html/sideNav/sideNavDiscovery'),
2123 }
2224 },
2325 page: {
26+ addressBook: require('./page/addressBook'),
2427 blogIndex: require('./page/blogIndex'),
2528 blogNew: require('./page/blogNew'),
2629 blogSearch: require('./page/blogSearch'),
2730 blogShow: require('./page/blogShow'),
app/page/blogIndex.jsView
@@ -1,15 +1,15 @@
11 const nest = require('depnest')
2-const { h } = require('mutant')
2+const { h, Value, resolve } = require('mutant')
33 const pull = require('pull-stream')
44
55 exports.gives = nest('app.page.blogIndex')
66
77 exports.needs = nest({
8- 'app.html.sideNav': 'first',
98 'app.html.blogCard': 'first',
109 'app.html.topNav': 'first',
1110 'app.html.scroller': 'first',
11+ 'app.html.sideNav': 'first',
1212 // 'feed.pull.public': 'first',
1313 'feed.pull.type': 'first',
1414 'history.sync.push': 'first',
1515 'keys.sync.id': 'first',
@@ -39,10 +39,10 @@
3939 ),
4040 // FUTURE : if we need better perf, we can add a persistent cache. At the moment this page is fast enough though.
4141 // See implementation of app.html.sideNav for example
4242 // store: recentMsgCache,
43- // updateTop: updateRecentMsgCache,
44- // updateBottom: updateRecentMsgCache,
43+ updateTop: update,
44+ updateBottom: update,
4545 render
4646 })
4747
4848 return h('Page -blogIndex', {title: strings.home}, [
@@ -51,9 +51,30 @@
5151 ])
5252 })
5353
5454
55+ function update (soFar, newBlog) {
56+ soFar.transaction(() => {
57+ const { timestamp } = newBlog.value
5558
59+ var object = newBlog // Value(newBlog)
60+
61+ // Orders by: time received
62+ const justOlderPosition = indexOf(soFar, (msg) => newBlog.timestamp > resolve(msg).timestamp)
63+
64+ // Orders by: time published BUT the messagesByType stream streams _by time received_
65+ // TODO - we need an index of all blogs otherwise the scroller doesn't work...
66+ // const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
67+
68+ if (justOlderPosition > -1) {
69+ soFar.insert(object, justOlderPosition)
70+ } else {
71+ soFar.push(object)
72+ }
73+ })
74+ }
75+
76+
5677 function render (blog) {
5778 const { recps, channel } = blog.value.content
5879 var onClick
5980 if (channel && !recps)
@@ -61,7 +82,13 @@
6182 return api.app.html.blogCard(blog, { onClick })
6283 }
6384 }
6485
86+function indexOf (array, fn) {
87+ for (var i = 0; i < array.getLength(); i++) {
88+ if (fn(array.get(i))) {
89+ return i
90+ }
91+ }
92+ return -1
93+}
6594
66-
67-
app/page/blogIndex.mcssView
@@ -1,28 +1,13 @@
11 Page -blogIndex {
2- div.content { padding: 0 }
2+ div.content.Scroller {
3+ padding: 0
34
4- div.Scroller.content {
5-
65 section.top {
76 position: sticky
8- left: 0
9- right: 0
10- top: 0
11- z-index: 99
12-
13- background-color: #fff
147 }
158
169 section.content {
17- background-color: #fff
18- $maxWidth
19- margin: .8rem auto
20- padding: .5rem 2rem
21-
22- display: flex
23- flex-wrap: wrap
24-
2510 div.BlogCard {
2611 flex-basis: 100%
2712 border-bottom: 1px solid rgba(0,0,0, .1)
2813 }
app/page/blogNew.mcssView
@@ -1,8 +1,7 @@
11 Page -blogNew {
2- div.Context {}
2+ div.SideNav {}
33
4-
54 div.content {
65 display: flex
76
87 div.container {
app/page/blogSearch.mcssView
@@ -1,18 +1,11 @@
11 Page -blogSearch {
2- div.content { padding: 0 }
2+ div.content.Scroller {
3+ padding: 0
34
4- div.Scroller.content {
5-
65 section.top {
76 position: sticky
8- left: 0
9- right: 0
10- top: 0
11- z-index: 99
127
13- background-color: #fff
14-
158 div.search {
169 border-top: 1px solid gainsboro
1710 border-bottom: 2px solid #f5f6f7
1811 padding: 1rem
@@ -50,16 +43,8 @@
5043 }
5144 }
5245
5346 section.content {
54- background-color: #fff
55- $maxWidth
56- padding: .5rem 2rem
57- margin: .8rem auto
58-
59- display: flex
60- flex-wrap: wrap
61-
6247 div.BlogCard {
6348 flex-basis: 100%
6449
6550 border-bottom: 1px solid gainsboro
app/page/blogShow.jsView
@@ -36,13 +36,13 @@
3636 const thread = api.feed.obs.thread(blogMsg.key)
3737 const comments = api.app.html.comments(thread)
3838 const branch = thread.lastId
3939
40- const { timeago, channel, markdown, compose } = api.message.html
40+ const { timeago, channel, markdown } = api.message.html
4141
4242 return h('Page -blogShow', [
43- api.app.html.sideNav({ page: 'discover' }), // HACK to highlight discover
44- h('div.content', [
43+ api.app.html.sideNav({ page: 'blogShow' }), // HACK to highlight discover
44+ h('Scroller.content', [
4545 h('section.top', [
4646 api.app.html.topNav(location)
4747 ]),
4848 h('section.content', [
app/page/blogShow.mcssView
@@ -1,35 +1,35 @@
11 Page -blogShow {
22 // div.context {}
33
4- div.content {
4+ div.Scroller.content {
55 padding: 0
66 section.top {
77 position: sticky
8- left: 0
9- right: 0
10- top: 0
11- background-color: #fff
8+ top: initial
129
1310 div.blogHeader {
1411 }
1512 }
1613
1714 section.content {
18- $maxWidth
15+ background-color: initial
1916 padding: .8rem
20- margin: 0 auto
17+ margin: 1rem auto
2118
19+ display: flex
20+
2221 header {
2322 $backgroundPrimaryText
23+ flex-basis: 100%
2424 padding: 1.5rem
2525
2626 display: flex
2727
2828 div.blog {
2929 display: flex
3030 flex-wrap: wrap
31- flex-grow: 1
31+ flex-basis: 100%
3232 align-items: center
3333
3434 h1 {
3535 flex-basis: 100%
@@ -71,8 +71,9 @@
7171 }
7272
7373 div.break {
7474 padding: 0 1.5rem
75+ flex-basis: 100%
7576 $backgroundPrimaryText
7677
7778 hr {
7879 margin: 0
@@ -81,15 +82,17 @@
8182 }
8283 }
8384
8485 section.blog {
86+ flex-basis: 100%
8587 $backgroundPrimaryText
8688 padding: 1.5rem
8789
8890 margin-bottom: 1.5rem
8991 }
9092
9193 div.Comments {
94+ flex-basis: 100%
9295 }
9396 }
9497 }
9598 }
app/page/page.mcssView
@@ -5,9 +5,9 @@
55 margin-top: 3.5rem
66
77 display: flex
88
9- div.Context {
9+ div.SideNav {
1010 min-height: calc(100% - 3rem)
1111 overflow-y: auto
1212 overflow-x: hidden
1313 }
app/page/threadShow.jsView
@@ -29,11 +29,11 @@
2929 type: 'post',
3030 root,
3131 branch: thread.lastId,
3232 channel,
33- recps: get(location, 'value.content.recps')
33+ recps: get(location, 'value.content.recps'),
3434 }
35- const composer = api.message.html.compose({ meta, shrink: false })
35+ const composer = api.message.html.compose({ meta, thread, shrink: false })
3636
3737 return h('Page -threadShow', [
3838 api.app.html.sideNav(location),
3939 h('div.content', [
app/page/userShow.jsView
@@ -9,13 +9,13 @@
99 exports.needs = nest({
1010 'about.html.avatar': 'first',
1111 'about.obs.name': 'first',
1212 'about.obs.description': 'first',
13- 'app.html.sideNav': 'first',
1413 'app.html.link': 'first',
1514 'app.html.blogCard': 'first',
1615 'app.html.topNav': 'first',
1716 'app.html.scroller': 'first',
17+ 'app.html.sideNav': 'first',
1818 'contact.html.follow': 'first',
1919 'contact.html.block': 'first',
2020 'feed.pull.profile': 'first',
2121 'feed.pull.rollup': 'first',
app/page/userShow.mcssView
@@ -1,12 +1,11 @@
11 Page -userShow {
2- div.content { padding: 0 }
2+ div.content.Scroller {
3+ padding: 0
34
4- div.Scroller.content {
55 section.top {
66 section.about {
7- margin-top: 4rem
8- margin-bottom: 4rem
7+ padding: 4rem 0
98
109 display: flex
1110 flex-direction: column
1211 align-items: center
@@ -45,20 +44,11 @@
4544 }
4645 }
4746
4847 section.content {
49- background-color: #fff
50- $maxWidth
51- margin: .8rem auto
52- padding: .5rem 2rem
53-
54- display: flex
55- flex-wrap: wrap
56-
5748 div.BlogCard {
5849 flex-basis: 100%
5950 border-bottom: 1px solid rgba(0,0,0, .1)
60- /* margin-bottom: .5rem */
6151
6252 div.context {
6353 div.Link { display: none }
6454 div.name { display: none }
app/page/addressBook.jsView
@@ -1,0 +1,82 @@
1+const nest = require('depnest')
2+const { h, Value, computed, map } = require('mutant')
3+const pull = require('pull-stream')
4+
5+exports.gives = nest('app.page.addressBook')
6+
7+//declare consts to avoid magic-string errors
8+const FRIENDS = 'friends'
9+const FOLLOWING = 'following'
10+const FOLLOWERS = 'followers'
11+const SEARCH = 'search'
12+
13+exports.needs = nest({
14+ 'about.html.avatar': 'first',
15+ 'about.async.suggest': 'first',
16+ 'about.obs.name': 'first',
17+ 'app.html.topNav': 'first',
18+ // 'app.html.scroller': 'first',
19+ 'app.html.sideNav': 'first',
20+ 'app.html.topNav': 'first',
21+ 'contact.html.follow': 'first',
22+ 'contact.obs.relationships': 'first',
23+ 'history.sync.push': 'first',
24+ 'keys.sync.id': 'first',
25+ 'translations.sync.strings': 'first',
26+})
27+
28+exports.create = (api) => {
29+ return nest('app.page.addressBook', function (location) {
30+ // location here can expected to be: { page: 'addressBook'}
31+
32+ const strings = api.translations.sync.strings()
33+ const myKey = api.keys.sync.id()
34+ const relationships = api.contact.obs.relationships(myKey)
35+
36+ const SECTIONS = [FRIENDS, FOLLOWING, FOLLOWERS, SEARCH]
37+ const section = location.section || FRIENDS
38+ if (!SECTIONS.includes(section)) throw new Error('AddressBook location must include valid section, got:', location)
39+
40+ const input = Value()
41+
42+ const suggester = api.about.async.suggest()
43+ const users = computed([relationships, input], (relationships, input) => {
44+ if (section === SEARCH)
45+ return suggester(input)
46+ else {
47+ const sectionRels = relationships[section]
48+ if (!input) {
49+ return sectionRels // show all e.g. friends
50+ .reverse()
51+ .map(id => { return { id, title: api.about.obs.name(id) } })
52+ }
53+ else { // show suggestions, and filter just the ones we want e.g. friends
54+ return suggester(input, relationships.followers) // add extraIds to suggester
55+ .filter(user => sectionRels.includes(user.id))
56+ }
57+ }
58+ })
59+
60+ const goTo = (loc) => () => api.history.sync.push(loc)
61+
62+ return h('Page -addressBook', [
63+ api.app.html.sideNav(location, relationships),
64+ h('Scroller.content', [
65+ h('section.top', [
66+ api.app.html.topNav(location, input),
67+ ]),
68+ h('section.content', [
69+ h('div.results', map(users, user => {
70+ return h('div.result', { 'ev-click': goTo({page: 'userShow', feed: user.id}) }, [
71+ api.about.html.avatar(user.id),
72+ h('div.alias', user.title),
73+ // h('pre.key', user.id),
74+ api.contact.html.follow(user.id)
75+ ])
76+ })),
77+ ])
78+ ])
79+ ])
80+ })
81+}
82+
app/page/addressBook.mcssView
@@ -1,0 +1,56 @@
1+Page -addressBook {
2+ div.Scroller.content {
3+ padding: 0
4+
5+ section.top {
6+ position: sticky
7+ }
8+
9+ section.content {
10+ $maxWidthSmaller
11+
12+ div.results {
13+ flex-grow: 1
14+
15+ $maxWidthSmaller
16+
17+ div.result {
18+ $backgroundPrimaryText
19+ cursor: pointer
20+
21+ padding: .5rem
22+
23+ display: flex
24+ align-items: center
25+
26+ img {
27+ $circleSmall
28+ margin-right: 1rem
29+ }
30+
31+ div.alias {
32+ flex-grow: 1
33+ margin-right: 1rem
34+ }
35+
36+ pre.key {
37+ color: #999
38+
39+ }
40+
41+ div.Follow {}
42+
43+ :hover {
44+ background-color: #eee
45+
46+ }
47+ }
48+ }
49+ }
50+
51+ section.bottom {
52+ }
53+ }
54+}
55+
56+
blog/html/blog.jsView
@@ -30,9 +30,9 @@
3030 api.sbot.obs.connection(),
3131 sbot => sbot.blobs.want(blog, (err, success) => {
3232 if (err) throw err
3333
34- console.log(`want blog ${blog}, callback: ${success}`)
34+ // console.log(`want blog ${blog}, callback: ${success}`)
3535 })
3636 )
3737 }
3838
contact/html/follow.jsView
@@ -23,16 +23,23 @@
2323 const { followers } = api.contact.obs
2424 const theirFollowers = followers(feed)
2525 const youFollowThem = computed(theirFollowers, followers => followers.includes(myId))
2626
27- const { unfollow, follow } = api.contact.async
2827 const className = when(youFollowThem, '-following')
28+ const follow = (feed) => ev => {
29+ ev.stopPropagation()
30+ api.contact.async.follow(feed)
31+ }
32+ const unfollow = (feed) => ev => {
33+ ev.stopPropagation()
34+ api.contact.async.unfollow(feed)
35+ }
2936
3037 return h('Follow', { className },
3138 when(theirFollowers.sync,
3239 when(youFollowThem,
33- h('Button', { 'ev-click': () => unfollow(feed) }, strings.userShow.action.unfollow),
34- h('Button', { 'ev-click': () => follow(feed) }, strings.userShow.action.follow)
40+ h('Button', { 'ev-click': unfollow(feed) }, strings.userShow.action.unfollow),
41+ h('Button -strong', { 'ev-click': follow(feed) }, strings.userShow.action.follow)
3542 ),
3643 h('Button', { disabled: 'disabled' }, strings.loading )
3744 )
3845 )
contact/index.jsView
@@ -1,8 +1,11 @@
11 module.exports = {
22 html: {
33 follow: require('./html/follow'),
44 block: require('./html/block')
5- }
5+ },
6+ obs: {
7+ relationships: require('./obs/relationships'),
8+ },
69 }
710
811
contact/obs/relationships.jsView
@@ -1,0 +1,31 @@
1+const nest = require('depnest')
2+const { h, map, computed, when } = require('mutant')
3+const { intersection, difference } = require('lodash')
4+
5+exports.gives = nest('contact.obs.relationships')
6+
7+exports.needs = nest({
8+ 'contact.obs.followers': 'first',
9+ 'contact.obs.following': 'first',
10+ 'keys.sync.id': 'first'
11+})
12+
13+exports.create = function (api) {
14+ return nest({
15+ 'contact.obs.relationships': relationships
16+ })
17+
18+ function relationships (id) {
19+ var following = api.contact.obs.following(id)
20+ var followers = api.contact.obs.followers(id)
21+
22+ return computed([following, followers], (followingRaw, followersRaw) => {
23+ const friends = intersection([...followingRaw], [...followersRaw])
24+ const followers = difference(followersRaw, friends)
25+ const following = difference(followingRaw, friends)
26+
27+ return { friends, followers, following }
28+ })
29+ }
30+}
31+
message/html/compose.jsView
@@ -1,6 +1,6 @@
11 const nest = require('depnest')
2-const { h, when, send, resolve, Value, computed } = require('mutant')
2+const { h, when, send, resolve, Value, computed, map } = require('mutant')
33 const assign = require('lodash/assign')
44 const ssbMentions = require('ssb-mentions')
55 const addSuggest = require('suggest-box')
66
@@ -24,16 +24,17 @@
2424
2525 function compose (options, cb) {
2626 var {
2727 meta, // required
28+ feedIdsInThread = [],
2829 placeholder,
2930 shrink = true,
3031 canAttach = true, canPreview = true,
31- prepublish
32+ prepublish,
3233 } = options
3334
3435 const strings = api.translations.sync.strings()
35- const getProfileSuggestions = api.about.async.suggest()
36+ const getUserSuggestions = api.about.async.suggest()
3637 const getChannelSuggestions = api.channel.async.suggest()
3738 const getEmojiSuggestions = api.emoji.async.suggest()
3839
3940 placeholder = placeholder || strings.writeMessage
@@ -71,11 +72,11 @@
7172 fileInput = api.blob.html.input(file => {
7273 files.push(file)
7374 filesById[file.link] = file
7475
75- var embed = file.type.match(/^image/) ? '!' : ''
76- var spacer = embed ? '\n' : ' '
77- var insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer
76+ var imgPrefix = file.type.match(/^image/) ? '!' : ''
77+ var spacer = imgPrefix ? '\n' : ' '
78+ var insertLink = spacer + imgPrefix + '[' + file.name + ']' + '(' + file.link + ')' + spacer
7879
7980 var pos = textArea.selectionStart
8081 var newText = textRaw().slice(0, pos) + insertLink + textRaw().slice(pos)
8182 textArea.value = newText
@@ -90,16 +91,20 @@
9091 // and we don't want that.
9192 else
9293 fileInput = h('input', { style: {visibility: 'hidden'} })
9394
94- var showPreview = Value(false)
95- var previewBtn = h('Button',
96- {
97- className: when(showPreview, '-strong', '-subtle'),
98- 'ev-click': () => showPreview.set(!showPreview())
99- },
100- when(showPreview, strings.blogNew.actions.edit, strings.blogNew.actions.preview)
101- )
95+ function PreviewSetup (strings) {
96+ var showPreview = Value(false)
97+ var previewBtn = h('Button',
98+ {
99+ className: when(showPreview, '-strong', '-subtle'),
100+ 'ev-click': () => showPreview.set(!showPreview())
101+ },
102+ when(showPreview, strings.blogNew.actions.edit, strings.blogNew.actions.preview)
103+ )
104+ return { previewBtn, showPreview }
105+ }
106+ var { previewBtn, showPreview } = PreviewSetup(strings)
102107 var preview = computed(textRaw, text => api.message.html.markdown(text))
103108
104109 var publishBtn = h('Button -primary', { 'ev-click': publish }, strings.sendMessage)
105110
@@ -119,9 +124,9 @@
119124 addSuggest(textArea, (inputText, cb) => {
120125 const char = inputText[0]
121126 const wordFragment = inputText.slice(1)
122127
123- if (char === '@') cb(null, getProfileSuggestions(wordFragment))
128+ if (char === '@') cb(null, getUserSuggestions(wordFragment, feedIdsInThread))
124129 if (char === '#') cb(null, getChannelSuggestions(wordFragment))
125130 if (char === ':') cb(null, getEmojiSuggestions(wordFragment))
126131 }, {cls: 'PatchSuggest'})
127132
package.jsonView
@@ -39,11 +39,11 @@
3939 "mutant": "^3.21.2",
4040 "mutant-scroll": "0.0.5",
4141 "open-external": "^0.1.1",
4242 "patch-history": "^1.0.0",
43- "patch-profile": "^1.0.3",
43+ "patch-profile": "^1.0.4",
4444 "patch-settings": "^1.0.0",
45- "patch-suggest": "^1.0.1",
45+ "patch-suggest": "^1.1.0",
4646 "patchcore": "^1.23.0",
4747 "pull-next": "^1.0.1",
4848 "pull-next-step": "^1.0.0",
4949 "pull-obv": "^1.3.0",
router/sync/routes.jsView
@@ -6,8 +6,9 @@
66 exports.gives = nest('router.sync.routes')
77
88 exports.needs = nest({
99 'app.page.error': 'first',
10+ 'app.page.addressBook': 'first',
1011 'app.page.blogIndex': 'first',
1112 'app.page.blogNew': 'first',
1213 'app.page.blogSearch': 'first',
1314 'app.page.blogShow': 'first',
@@ -53,8 +54,10 @@
5354 [ location => location.page === 'channelSubscriptions', pages.channelSubscriptions],
5455 [ location => location.page === 'channelShow', pages.channelShow ],
5556
5657
58+ // AddressBook pages
59+ [ location => location.page === 'addressBook', pages.addressBook ],
5760
5861 // Private Thread pages
5962 // [ location => location.page === 'threadNew' && location.channel, pages.threadNew ],
6063 [ location => location.page === 'threadNew' && isFeed(location.feed), pages.threadNew ],
styles/button.mcssView
@@ -36,9 +36,8 @@
3636 -strong {
3737 $colorPrimary
3838 $font
3939 $borderPrimary
40-
4140 }
4241
4342 -channel {
4443 $backgroundPrimary
styles/patchSuggest.mcssView
@@ -5,8 +5,9 @@
55 li {
66 img { $circleSmall }
77
88 small {
9+ div.aliases { max-width: 14.5rem }
910 div.key { font-size: 1rem }
1011 }
1112 }
1213 }
translations/en.jsView
@@ -60,8 +60,27 @@
6060 language: 'Language',
6161 zoom: 'Zoom'
6262 }
6363 },
64+ addressBook: {
65+ action: {
66+ addUser: 'Add a user',
67+ find: {
68+ friends: 'Search your friends',
69+ following: "Search people you're following",
70+ followers: 'Search your followers',
71+ search: 'Search for a user',
72+ }
73+ },
74+ heading: {
75+ people: 'People'
76+ },
77+ section: {
78+ friends: 'Friends',
79+ following: 'Following',
80+ followers: 'Followers'
81+ }
82+ },
6483 threadNew: {
6584 pageTitle: 'New Thread',
6685 field: {
6786 to: 'To',

Built with git-ssb-web