Commit 9b889230e5350365ea198b03786a51efe3ffe969
Merge branch 'master' into feature-channel-subscriptions
Andre Alves Garzia authored on 2/2/2018, 1:25:18 AMGitHub committed on 2/2/2018, 1:25:18 AM
Parent: 7a5359e5b8b6477de66abab7748e8f1c595a1875
Parent: 9660479589cb3e6a2d8fe371854da4ce7a38522b
Files changed
app/html/app.js | ||
---|---|---|
@@ -28,8 +28,9 @@ | ||
28 | 28 | |
29 | 29 | view = Value() |
30 | 30 | var app = h('App', view) |
31 | 31 | api.history.obs.location()(renderLocation) |
32 | + api.history.obs.location()(loc => console.log('location:', loc)) | |
32 | 33 | |
33 | 34 | startApp() |
34 | 35 | |
35 | 36 | return app |
app/html/comments.js | ||
---|---|---|
@@ -21,10 +21,10 @@ | ||
21 | 21 | exports.create = (api) => { |
22 | 22 | return nest('app.html.comments', comments) |
23 | 23 | |
24 | 24 | function comments (thread) { |
25 | + const strings = api.translations.sync.strings() | |
25 | 26 | const { messages, channel, lastId: branch } = thread |
26 | - const strings = api.translations.sync.strings() | |
27 | 27 | |
28 | 28 | // TODO - move this up into Patchcore |
29 | 29 | const messagesTree = computed(throttle(messages, 200), msgs => { |
30 | 30 | return msgs |
@@ -48,13 +48,16 @@ | ||
48 | 48 | // return messages.length > 5 |
49 | 49 | // }) |
50 | 50 | const { compose } = api.message.html |
51 | 51 | |
52 | + const feedIdsInThread = computed(thread.messages, msgs => { | |
53 | + return msgs.map(m => m.value.author) | |
54 | + }) | |
52 | 55 | |
53 | 56 | return h('Comments', [ |
54 | 57 | // when(twoComposers, compose({ meta, shrink: true, canAttach: false })), |
55 | 58 | 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 }), | |
57 | 60 | ]) |
58 | 61 | } |
59 | 62 | |
60 | 63 | function Comment (msgObs, root, branch) { |
app/html/header.js | ||
---|---|---|
@@ -24,21 +24,20 @@ | ||
24 | 24 | |
25 | 25 | if (loc().page === 'splash') return |
26 | 26 | |
27 | 27 | 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) | |
29 | 30 | |
30 | - const isFeed = computed([isProfile, isSettings], (p, s) => !p && !s) | |
31 | - | |
32 | 31 | return h('Header', [ |
33 | 32 | h('nav', [ |
34 | 33 | h('img.feed', { |
35 | 34 | src: when(isFeed, assetPath('feed_on.png'), assetPath('feed.png')), |
36 | 35 | 'ev-click': () => push({page: 'blogIndex'}), |
37 | 36 | }), |
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'}) | |
41 | 40 | }), |
42 | 41 | h('img.settings', { |
43 | 42 | src: when(isSettings, assetPath('settings_on.png'), assetPath('settings.png')), |
44 | 43 | 'ev-click': () => push({page: 'settings'}) |
app/html/scroller.mcss | ||
---|---|---|
@@ -1,34 +1,38 @@ | ||
1 | 1 | Scroller { |
2 | - | |
3 | 2 | overflow: auto |
4 | 3 | width: 100% |
5 | 4 | height: 100% |
6 | 5 | min-height: 0px |
7 | 6 | |
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 | |
10 | 13 | |
11 | - /* flex: 1 1 */ | |
12 | - /* $threadWidth */ | |
13 | - /* padding-top: .5rem */ | |
14 | + background-color: #fff | |
15 | + } | |
14 | 16 | |
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 | |
22 | 22 | |
23 | -Scroller -errors { | |
24 | - div.wrapper { | |
25 | - width: initial | |
26 | - max-width: 100% | |
23 | + display: flex | |
24 | + flex-wrap: wrap | |
27 | 25 | |
28 | - section.content div { | |
29 | - border: none | |
30 | - } | |
31 | 26 | } |
27 | + | |
28 | + section.bottom { | |
29 | + } | |
32 | 30 | } |
33 | 31 | |
32 | +/* Scroller -errors { */ | |
33 | +/* section.content div { */ | |
34 | +/* border: none */ | |
35 | +/* } */ | |
36 | +/* } */ | |
34 | 37 | |
38 | + |
app/html/sideNav/sideNav.mcss | ||
---|---|---|
@@ -32,4 +32,95 @@ | ||
32 | 32 | -two {} |
33 | 33 | } |
34 | 34 | } |
35 | 35 | |
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.js | ||
---|---|---|
@@ -17,8 +17,9 @@ | ||
17 | 17 | 'about.obs.name': 'first', |
18 | 18 | 'feed.pull.private': 'first', |
19 | 19 | 'keys.sync.id': 'first', |
20 | 20 | 'history.sync.push': 'first', |
21 | + 'history.obs.store': 'first', | |
21 | 22 | 'message.html.subject': 'first', |
22 | 23 | 'sbot.obs.localPeers': 'first', |
23 | 24 | 'translations.sync.strings': 'first', |
24 | 25 | 'unread.sync.isUnread': 'first' |
@@ -39,9 +40,23 @@ | ||
39 | 40 | }, |
40 | 41 | 'app.html.sideNav': sideNav, |
41 | 42 | }) |
42 | 43 | |
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 | + | |
43 | 56 | function sideNav (location) { |
57 | + if (!isMatch(location)) return | |
58 | + | |
44 | 59 | const strings = api.translations.sync.strings() |
45 | 60 | const myKey = api.keys.sync.id() |
46 | 61 | |
47 | 62 | var nearby = api.sbot.obs.localPeers() |
@@ -76,13 +91,15 @@ | ||
76 | 91 | LevelTwoSideNav() |
77 | 92 | ]) |
78 | 93 | |
79 | 94 | function LevelOneSideNav () { |
80 | - function isDiscoverSideNav (loc) { | |
95 | + function isDiscoverLocation (loc) { | |
81 | 96 | const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'userShow'] |
82 | 97 | |
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 | |
85 | 102 | } |
86 | 103 | |
87 | 104 | const prepend = [ |
88 | 105 | // Nearby |
@@ -90,9 +107,9 @@ | ||
90 | 107 | map(nearby, feedId => Option({ |
91 | 108 | notifications: notifications(feedId), |
92 | 109 | imageEl: api.about.html.avatar(feedId, 'small'), |
93 | 110 | label: api.about.obs.name(feedId), |
94 | - selected: location.feed === feedId, | |
111 | + selected: location.feed === feedId && !isDiscoverLocation(location), | |
95 | 112 | location: computed(recentMsgCache, recent => { |
96 | 113 | const lastMsg = recent.find(msg => msg.value.author === feedId) |
97 | 114 | return lastMsg |
98 | 115 | ? Object.assign(lastMsg, { feed: feedId }) |
@@ -110,9 +127,9 @@ | ||
110 | 127 | imageEl: h('i', [ |
111 | 128 | h('img', { src: path.join(__dirname, '../../../assets', 'discover.png') }) |
112 | 129 | ]), |
113 | 130 | label: strings.blogIndex.title, |
114 | - selected: isDiscoverSideNav(location), | |
131 | + selected: isDiscoverLocation(location), | |
115 | 132 | location: { page: 'blogIndex' }, |
116 | 133 | }), |
117 | 134 | |
118 | 135 | // My subscriptions |
app/html/sideNav/sideNavDiscovery.mcss | ||
---|---|---|
@@ -1,7 +1,13 @@ | ||
1 | 1 | SideNav -discovery { |
2 | 2 | div.level { |
3 | 3 | section { |
4 | + padding: 0 | |
5 | + margin: 0 | |
6 | + | |
7 | + div.Option { | |
8 | + flex-grow: 1 | |
9 | + } | |
4 | 10 | } |
5 | 11 | |
6 | 12 | -one {} |
7 | 13 | -two { |
@@ -13,97 +19,6 @@ | ||
13 | 19 | } |
14 | 20 | } |
15 | 21 | } |
16 | 22 | |
17 | -Option { | |
18 | - min-width: 8rem | |
19 | - padding: .5rem 1rem | |
20 | - display: flex | |
21 | - align-items: center | |
22 | 23 | |
23 | - :hover { | |
24 | - cursor: pointer | |
25 | - $backgroundSelected | |
26 | - } | |
27 | 24 | |
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.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -5,17 +5,15 @@ | ||
5 | 5 | exports.gives = nest('app.html.topNav') |
6 | 6 | |
7 | 7 | exports.needs = nest({ |
8 | 8 | 'history.sync.push': 'first', |
9 | - 'history.sync.back': 'first', | |
10 | 9 | 'translations.sync.strings': 'first', |
11 | 10 | }) |
12 | 11 | |
13 | 12 | exports.create = (api) => { |
14 | 13 | return nest('app.html.topNav', (location) => { |
15 | 14 | const strings = api.translations.sync.strings() |
16 | 15 | const goTo = (loc) => () => api.history.sync.push(loc) |
17 | - const back = () => api.history.sync.back() | |
18 | 16 | |
19 | 17 | if (!['blogIndex', 'blogSearch'].includes(location.page)) return |
20 | 18 | |
21 | 19 | return h('TopNav -blog', [ |
@@ -32,18 +30,7 @@ | ||
32 | 30 | h('div.right', [ |
33 | 31 | h('Button -strong', { 'ev-click': () => api.history.sync.push({ page: 'blogNew' }) }, strings.blogNew.actions.writeBlog), |
34 | 32 | ]) |
35 | 33 | ]) |
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 | - ]) | |
47 | 34 | }) |
48 | 35 | } |
49 | 36 |
app/html/topNav/zz_topNavBack.js | ||
---|---|---|
@@ -4,9 +4,8 @@ | ||
4 | 4 | |
5 | 5 | exports.gives = nest('app.html.topNav') |
6 | 6 | |
7 | 7 | exports.needs = nest({ |
8 | - 'history.sync.push': 'first', | |
9 | 8 | 'history.sync.back': 'first', |
10 | 9 | 'translations.sync.strings': 'first', |
11 | 10 | }) |
12 | 11 |
app/html/topNav/topNavAddressBook.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -11,17 +11,20 @@ | ||
11 | 11 | lightbox: require('./html/lightbox'), |
12 | 12 | blogCard: require('./html/blogCard'), |
13 | 13 | channelCard: require('./html/channelCard'), |
14 | 14 | topNav: { |
15 | + topNavAddressBook: require('./html/topNav/topNavAddressBook'), | |
15 | 16 | topNavBlog: require('./html/topNav/topNavBlog'), |
16 | 17 | topNavBack: require('./html/topNav/zz_topNavBack'), |
17 | 18 | }, |
18 | 19 | scroller: require('./html/scroller'), |
19 | 20 | sideNav: { |
21 | + addressBook: require('./html/sideNav/sideNavAddressBook'), | |
20 | 22 | discovery: require('./html/sideNav/sideNavDiscovery'), |
21 | 23 | } |
22 | 24 | }, |
23 | 25 | page: { |
26 | + addressBook: require('./page/addressBook'), | |
24 | 27 | blogIndex: require('./page/blogIndex'), |
25 | 28 | blogNew: require('./page/blogNew'), |
26 | 29 | blogSearch: require('./page/blogSearch'), |
27 | 30 | blogShow: require('./page/blogShow'), |
app/page/blogIndex.js | ||
---|---|---|
@@ -1,15 +1,15 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | -const { h } = require('mutant') | |
2 | +const { h, Value, resolve } = require('mutant') | |
3 | 3 | const pull = require('pull-stream') |
4 | 4 | |
5 | 5 | exports.gives = nest('app.page.blogIndex') |
6 | 6 | |
7 | 7 | exports.needs = nest({ |
8 | - 'app.html.sideNav': 'first', | |
9 | 8 | 'app.html.blogCard': 'first', |
10 | 9 | 'app.html.topNav': 'first', |
11 | 10 | 'app.html.scroller': 'first', |
11 | + 'app.html.sideNav': 'first', | |
12 | 12 | // 'feed.pull.public': 'first', |
13 | 13 | 'feed.pull.type': 'first', |
14 | 14 | 'history.sync.push': 'first', |
15 | 15 | 'keys.sync.id': 'first', |
@@ -39,10 +39,10 @@ | ||
39 | 39 | ), |
40 | 40 | // FUTURE : if we need better perf, we can add a persistent cache. At the moment this page is fast enough though. |
41 | 41 | // See implementation of app.html.sideNav for example |
42 | 42 | // store: recentMsgCache, |
43 | - // updateTop: updateRecentMsgCache, | |
44 | - // updateBottom: updateRecentMsgCache, | |
43 | + updateTop: update, | |
44 | + updateBottom: update, | |
45 | 45 | render |
46 | 46 | }) |
47 | 47 | |
48 | 48 | return h('Page -blogIndex', {title: strings.home}, [ |
@@ -51,9 +51,30 @@ | ||
51 | 51 | ]) |
52 | 52 | }) |
53 | 53 | |
54 | 54 | |
55 | + function update (soFar, newBlog) { | |
56 | + soFar.transaction(() => { | |
57 | + const { timestamp } = newBlog.value | |
55 | 58 | |
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 | + | |
56 | 77 | function render (blog) { |
57 | 78 | const { recps, channel } = blog.value.content |
58 | 79 | var onClick |
59 | 80 | if (channel && !recps) |
@@ -61,7 +82,13 @@ | ||
61 | 82 | return api.app.html.blogCard(blog, { onClick }) |
62 | 83 | } |
63 | 84 | } |
64 | 85 | |
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 | +} | |
65 | 94 | |
66 | - | |
67 | - |
app/page/blogIndex.mcss | ||
---|---|---|
@@ -1,28 +1,13 @@ | ||
1 | 1 | Page -blogIndex { |
2 | - div.content { padding: 0 } | |
2 | + div.content.Scroller { | |
3 | + padding: 0 | |
3 | 4 | |
4 | - div.Scroller.content { | |
5 | - | |
6 | 5 | section.top { |
7 | 6 | position: sticky |
8 | - left: 0 | |
9 | - right: 0 | |
10 | - top: 0 | |
11 | - z-index: 99 | |
12 | - | |
13 | - background-color: #fff | |
14 | 7 | } |
15 | 8 | |
16 | 9 | 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 | - | |
25 | 10 | div.BlogCard { |
26 | 11 | flex-basis: 100% |
27 | 12 | border-bottom: 1px solid rgba(0,0,0, .1) |
28 | 13 | } |
app/page/blogNew.mcss | ||
---|---|---|
@@ -1,8 +1,7 @@ | ||
1 | 1 | Page -blogNew { |
2 | - div.Context {} | |
2 | + div.SideNav {} | |
3 | 3 | |
4 | - | |
5 | 4 | div.content { |
6 | 5 | display: flex |
7 | 6 | |
8 | 7 | div.container { |
app/page/blogSearch.mcss | ||
---|---|---|
@@ -1,18 +1,11 @@ | ||
1 | 1 | Page -blogSearch { |
2 | - div.content { padding: 0 } | |
2 | + div.content.Scroller { | |
3 | + padding: 0 | |
3 | 4 | |
4 | - div.Scroller.content { | |
5 | - | |
6 | 5 | section.top { |
7 | 6 | position: sticky |
8 | - left: 0 | |
9 | - right: 0 | |
10 | - top: 0 | |
11 | - z-index: 99 | |
12 | 7 | |
13 | - background-color: #fff | |
14 | - | |
15 | 8 | div.search { |
16 | 9 | border-top: 1px solid gainsboro |
17 | 10 | border-bottom: 2px solid #f5f6f7 |
18 | 11 | padding: 1rem |
@@ -50,16 +43,8 @@ | ||
50 | 43 | } |
51 | 44 | } |
52 | 45 | |
53 | 46 | 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 | - | |
62 | 47 | div.BlogCard { |
63 | 48 | flex-basis: 100% |
64 | 49 | |
65 | 50 | border-bottom: 1px solid gainsboro |
app/page/blogShow.js | ||
---|---|---|
@@ -36,13 +36,13 @@ | ||
36 | 36 | const thread = api.feed.obs.thread(blogMsg.key) |
37 | 37 | const comments = api.app.html.comments(thread) |
38 | 38 | const branch = thread.lastId |
39 | 39 | |
40 | - const { timeago, channel, markdown, compose } = api.message.html | |
40 | + const { timeago, channel, markdown } = api.message.html | |
41 | 41 | |
42 | 42 | 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', [ | |
45 | 45 | h('section.top', [ |
46 | 46 | api.app.html.topNav(location) |
47 | 47 | ]), |
48 | 48 | h('section.content', [ |
app/page/blogShow.mcss | ||
---|---|---|
@@ -1,35 +1,35 @@ | ||
1 | 1 | Page -blogShow { |
2 | 2 | // div.context {} |
3 | 3 | |
4 | - div.content { | |
4 | + div.Scroller.content { | |
5 | 5 | padding: 0 |
6 | 6 | section.top { |
7 | 7 | position: sticky |
8 | - left: 0 | |
9 | - right: 0 | |
10 | - top: 0 | |
11 | - background-color: #fff | |
8 | + top: initial | |
12 | 9 | |
13 | 10 | div.blogHeader { |
14 | 11 | } |
15 | 12 | } |
16 | 13 | |
17 | 14 | section.content { |
18 | - $maxWidth | |
15 | + background-color: initial | |
19 | 16 | padding: .8rem |
20 | - margin: 0 auto | |
17 | + margin: 1rem auto | |
21 | 18 | |
19 | + display: flex | |
20 | + | |
22 | 21 | header { |
23 | 22 | $backgroundPrimaryText |
23 | + flex-basis: 100% | |
24 | 24 | padding: 1.5rem |
25 | 25 | |
26 | 26 | display: flex |
27 | 27 | |
28 | 28 | div.blog { |
29 | 29 | display: flex |
30 | 30 | flex-wrap: wrap |
31 | - flex-grow: 1 | |
31 | + flex-basis: 100% | |
32 | 32 | align-items: center |
33 | 33 | |
34 | 34 | h1 { |
35 | 35 | flex-basis: 100% |
@@ -71,8 +71,9 @@ | ||
71 | 71 | } |
72 | 72 | |
73 | 73 | div.break { |
74 | 74 | padding: 0 1.5rem |
75 | + flex-basis: 100% | |
75 | 76 | $backgroundPrimaryText |
76 | 77 | |
77 | 78 | hr { |
78 | 79 | margin: 0 |
@@ -81,15 +82,17 @@ | ||
81 | 82 | } |
82 | 83 | } |
83 | 84 | |
84 | 85 | section.blog { |
86 | + flex-basis: 100% | |
85 | 87 | $backgroundPrimaryText |
86 | 88 | padding: 1.5rem |
87 | 89 | |
88 | 90 | margin-bottom: 1.5rem |
89 | 91 | } |
90 | 92 | |
91 | 93 | div.Comments { |
94 | + flex-basis: 100% | |
92 | 95 | } |
93 | 96 | } |
94 | 97 | } |
95 | 98 | } |
app/page/page.mcss | ||
---|---|---|
@@ -5,9 +5,9 @@ | ||
5 | 5 | margin-top: 3.5rem |
6 | 6 | |
7 | 7 | display: flex |
8 | 8 | |
9 | - div.Context { | |
9 | + div.SideNav { | |
10 | 10 | min-height: calc(100% - 3rem) |
11 | 11 | overflow-y: auto |
12 | 12 | overflow-x: hidden |
13 | 13 | } |
app/page/threadShow.js | ||
---|---|---|
@@ -29,11 +29,11 @@ | ||
29 | 29 | type: 'post', |
30 | 30 | root, |
31 | 31 | branch: thread.lastId, |
32 | 32 | channel, |
33 | - recps: get(location, 'value.content.recps') | |
33 | + recps: get(location, 'value.content.recps'), | |
34 | 34 | } |
35 | - const composer = api.message.html.compose({ meta, shrink: false }) | |
35 | + const composer = api.message.html.compose({ meta, thread, shrink: false }) | |
36 | 36 | |
37 | 37 | return h('Page -threadShow', [ |
38 | 38 | api.app.html.sideNav(location), |
39 | 39 | h('div.content', [ |
app/page/userShow.js | ||
---|---|---|
@@ -9,13 +9,13 @@ | ||
9 | 9 | exports.needs = nest({ |
10 | 10 | 'about.html.avatar': 'first', |
11 | 11 | 'about.obs.name': 'first', |
12 | 12 | 'about.obs.description': 'first', |
13 | - 'app.html.sideNav': 'first', | |
14 | 13 | 'app.html.link': 'first', |
15 | 14 | 'app.html.blogCard': 'first', |
16 | 15 | 'app.html.topNav': 'first', |
17 | 16 | 'app.html.scroller': 'first', |
17 | + 'app.html.sideNav': 'first', | |
18 | 18 | 'contact.html.follow': 'first', |
19 | 19 | 'contact.html.block': 'first', |
20 | 20 | 'feed.pull.profile': 'first', |
21 | 21 | 'feed.pull.rollup': 'first', |
app/page/userShow.mcss | ||
---|---|---|
@@ -1,12 +1,11 @@ | ||
1 | 1 | Page -userShow { |
2 | - div.content { padding: 0 } | |
2 | + div.content.Scroller { | |
3 | + padding: 0 | |
3 | 4 | |
4 | - div.Scroller.content { | |
5 | 5 | section.top { |
6 | 6 | section.about { |
7 | - margin-top: 4rem | |
8 | - margin-bottom: 4rem | |
7 | + padding: 4rem 0 | |
9 | 8 | |
10 | 9 | display: flex |
11 | 10 | flex-direction: column |
12 | 11 | align-items: center |
@@ -45,20 +44,11 @@ | ||
45 | 44 | } |
46 | 45 | } |
47 | 46 | |
48 | 47 | 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 | - | |
57 | 48 | div.BlogCard { |
58 | 49 | flex-basis: 100% |
59 | 50 | border-bottom: 1px solid rgba(0,0,0, .1) |
60 | - /* margin-bottom: .5rem */ | |
61 | 51 | |
62 | 52 | div.context { |
63 | 53 | div.Link { display: none } |
64 | 54 | div.name { display: none } |
app/page/addressBook.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -30,9 +30,9 @@ | ||
30 | 30 | api.sbot.obs.connection(), |
31 | 31 | sbot => sbot.blobs.want(blog, (err, success) => { |
32 | 32 | if (err) throw err |
33 | 33 | |
34 | - console.log(`want blog ${blog}, callback: ${success}`) | |
34 | + // console.log(`want blog ${blog}, callback: ${success}`) | |
35 | 35 | }) |
36 | 36 | ) |
37 | 37 | } |
38 | 38 |
contact/html/follow.js | ||
---|---|---|
@@ -23,16 +23,23 @@ | ||
23 | 23 | const { followers } = api.contact.obs |
24 | 24 | const theirFollowers = followers(feed) |
25 | 25 | const youFollowThem = computed(theirFollowers, followers => followers.includes(myId)) |
26 | 26 | |
27 | - const { unfollow, follow } = api.contact.async | |
28 | 27 | 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 | + } | |
29 | 36 | |
30 | 37 | return h('Follow', { className }, |
31 | 38 | when(theirFollowers.sync, |
32 | 39 | 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) | |
35 | 42 | ), |
36 | 43 | h('Button', { disabled: 'disabled' }, strings.loading ) |
37 | 44 | ) |
38 | 45 | ) |
contact/index.js | ||
---|---|---|
@@ -1,8 +1,11 @@ | ||
1 | 1 | module.exports = { |
2 | 2 | html: { |
3 | 3 | follow: require('./html/follow'), |
4 | 4 | block: require('./html/block') |
5 | - } | |
5 | + }, | |
6 | + obs: { | |
7 | + relationships: require('./obs/relationships'), | |
8 | + }, | |
6 | 9 | } |
7 | 10 | |
8 | 11 |
contact/obs/relationships.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,6 +1,6 @@ | ||
1 | 1 | 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') | |
3 | 3 | const assign = require('lodash/assign') |
4 | 4 | const ssbMentions = require('ssb-mentions') |
5 | 5 | const addSuggest = require('suggest-box') |
6 | 6 | |
@@ -24,16 +24,17 @@ | ||
24 | 24 | |
25 | 25 | function compose (options, cb) { |
26 | 26 | var { |
27 | 27 | meta, // required |
28 | + feedIdsInThread = [], | |
28 | 29 | placeholder, |
29 | 30 | shrink = true, |
30 | 31 | canAttach = true, canPreview = true, |
31 | - prepublish | |
32 | + prepublish, | |
32 | 33 | } = options |
33 | 34 | |
34 | 35 | const strings = api.translations.sync.strings() |
35 | - const getProfileSuggestions = api.about.async.suggest() | |
36 | + const getUserSuggestions = api.about.async.suggest() | |
36 | 37 | const getChannelSuggestions = api.channel.async.suggest() |
37 | 38 | const getEmojiSuggestions = api.emoji.async.suggest() |
38 | 39 | |
39 | 40 | placeholder = placeholder || strings.writeMessage |
@@ -71,11 +72,11 @@ | ||
71 | 72 | fileInput = api.blob.html.input(file => { |
72 | 73 | files.push(file) |
73 | 74 | filesById[file.link] = file |
74 | 75 | |
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 | |
78 | 79 | |
79 | 80 | var pos = textArea.selectionStart |
80 | 81 | var newText = textRaw().slice(0, pos) + insertLink + textRaw().slice(pos) |
81 | 82 | textArea.value = newText |
@@ -90,16 +91,20 @@ | ||
90 | 91 | // and we don't want that. |
91 | 92 | else |
92 | 93 | fileInput = h('input', { style: {visibility: 'hidden'} }) |
93 | 94 | |
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) | |
102 | 107 | var preview = computed(textRaw, text => api.message.html.markdown(text)) |
103 | 108 | |
104 | 109 | var publishBtn = h('Button -primary', { 'ev-click': publish }, strings.sendMessage) |
105 | 110 | |
@@ -119,9 +124,9 @@ | ||
119 | 124 | addSuggest(textArea, (inputText, cb) => { |
120 | 125 | const char = inputText[0] |
121 | 126 | const wordFragment = inputText.slice(1) |
122 | 127 | |
123 | - if (char === '@') cb(null, getProfileSuggestions(wordFragment)) | |
128 | + if (char === '@') cb(null, getUserSuggestions(wordFragment, feedIdsInThread)) | |
124 | 129 | if (char === '#') cb(null, getChannelSuggestions(wordFragment)) |
125 | 130 | if (char === ':') cb(null, getEmojiSuggestions(wordFragment)) |
126 | 131 | }, {cls: 'PatchSuggest'}) |
127 | 132 |
package.json | ||
---|---|---|
@@ -39,11 +39,11 @@ | ||
39 | 39 | "mutant": "^3.21.2", |
40 | 40 | "mutant-scroll": "0.0.5", |
41 | 41 | "open-external": "^0.1.1", |
42 | 42 | "patch-history": "^1.0.0", |
43 | - "patch-profile": "^1.0.3", | |
43 | + "patch-profile": "^1.0.4", | |
44 | 44 | "patch-settings": "^1.0.0", |
45 | - "patch-suggest": "^1.0.1", | |
45 | + "patch-suggest": "^1.1.0", | |
46 | 46 | "patchcore": "^1.23.0", |
47 | 47 | "pull-next": "^1.0.1", |
48 | 48 | "pull-next-step": "^1.0.0", |
49 | 49 | "pull-obv": "^1.3.0", |
router/sync/routes.js | ||
---|---|---|
@@ -6,8 +6,9 @@ | ||
6 | 6 | exports.gives = nest('router.sync.routes') |
7 | 7 | |
8 | 8 | exports.needs = nest({ |
9 | 9 | 'app.page.error': 'first', |
10 | + 'app.page.addressBook': 'first', | |
10 | 11 | 'app.page.blogIndex': 'first', |
11 | 12 | 'app.page.blogNew': 'first', |
12 | 13 | 'app.page.blogSearch': 'first', |
13 | 14 | 'app.page.blogShow': 'first', |
@@ -53,8 +54,10 @@ | ||
53 | 54 | [ location => location.page === 'channelSubscriptions', pages.channelSubscriptions], |
54 | 55 | [ location => location.page === 'channelShow', pages.channelShow ], |
55 | 56 | |
56 | 57 | |
58 | + // AddressBook pages | |
59 | + [ location => location.page === 'addressBook', pages.addressBook ], | |
57 | 60 | |
58 | 61 | // Private Thread pages |
59 | 62 | // [ location => location.page === 'threadNew' && location.channel, pages.threadNew ], |
60 | 63 | [ location => location.page === 'threadNew' && isFeed(location.feed), pages.threadNew ], |
styles/button.mcss | ||
---|---|---|
@@ -36,9 +36,8 @@ | ||
36 | 36 | -strong { |
37 | 37 | $colorPrimary |
38 | 38 | $font |
39 | 39 | $borderPrimary |
40 | - | |
41 | 40 | } |
42 | 41 | |
43 | 42 | -channel { |
44 | 43 | $backgroundPrimary |
styles/patchSuggest.mcss | ||
---|---|---|
@@ -5,8 +5,9 @@ | ||
5 | 5 | li { |
6 | 6 | img { $circleSmall } |
7 | 7 | |
8 | 8 | small { |
9 | + div.aliases { max-width: 14.5rem } | |
9 | 10 | div.key { font-size: 1rem } |
10 | 11 | } |
11 | 12 | } |
12 | 13 | } |
translations/en.js | ||
---|---|---|
@@ -60,8 +60,27 @@ | ||
60 | 60 | language: 'Language', |
61 | 61 | zoom: 'Zoom' |
62 | 62 | } |
63 | 63 | }, |
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 | + }, | |
64 | 83 | threadNew: { |
65 | 84 | pageTitle: 'New Thread', |
66 | 85 | field: { |
67 | 86 | to: 'To', |
Built with git-ssb-web