git ssb

2+

mixmix / ticktack



Commit d6d6eae9c07646a979ad0e986e126984fbd0f2cd

rename app.html.context app.html.sideBar

mix authored on 1/24/2018, 2:15:36 AM
mix irving committed on 1/24/2018, 10:39:07 AM
Parent: 26360d1629d97859509df5393f68412875874595

Files changed

app/html/context.jsdeleted
app/html/context.mcssdeleted
app/html/sideNav/sideNav.mcssadded
app/html/sideNav/sideNavDiscovery.jsadded
app/html/sideNav/sideNavDiscovery.mcssadded
app/index.jschanged
app/page/blogIndex.jschanged
app/page/blogNew.jschanged
app/page/blogSearch.jschanged
app/page/blogShow.jschanged
app/page/threadNew.jschanged
app/page/threadShow.jschanged
app/page/userShow.jschanged
app/html/context.jsView
@@ -1,305 +1,0 @@
1-const nest = require('depnest')
2-const { h, computed, 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.context': true,
11- 'unread.sync.markUnread': true
12-})
13-
14-exports.needs = nest({
15- 'app.html.scroller': 'first',
16- 'about.html.avatar': 'first',
17- 'about.obs.name': 'first',
18- 'feed.pull.private': 'first',
19- 'feed.pull.rollup': 'first',
20- 'feed.pull.public': 'first',
21- 'keys.sync.id': 'first',
22- 'history.sync.push': 'first',
23- 'message.html.subject': 'first',
24- 'sbot.obs.localPeers': 'first',
25- 'translations.sync.strings': 'first',
26- 'unread.sync.isUnread': 'first'
27-})
28-
29-exports.create = (api) => {
30- var recentMsgCache = MutantArray()
31- var usersLastMsgCache = Dict() // { id: [ msgs ] }
32- var unreadMsgsCache = Dict() // { id: [ msgs ] }
33-
34- return nest({
35- //intercept markUnread and remove them from the cache.
36- 'unread.sync.markUnread': function (msg) {
37- unreadMsgsCache.get(msg.value.content.root || msg.key)
38- .delete(msg.key)
39- unreadMsgsCache.get(msg.value.author)
40- .delete(msg.key)
41- },
42- 'app.html.context': context,
43- })
44-
45- function context (location) {
46- const strings = api.translations.sync.strings()
47- const myKey = api.keys.sync.id()
48-
49- var nearby = api.sbot.obs.localPeers()
50-
51- // Unread message counts
52- function updateCache (cache, msg) {
53- if(api.unread.sync.isUnread(msg))
54- cache.add(msg.key)
55- else
56- cache.delete(msg.key)
57- }
58-
59- function updateUnreadMsgsCache (msg) {
60- updateCache(getUnreadMsgsCache(msg.value.author), msg)
61- updateCache(getUnreadMsgsCache(msg.value.content.root || msg.key), msg)
62- }
63-
64- pull(
65- next(api.feed.pull.private, {reverse: true, limit: 1000, live: false, property: ['value', 'timestamp']}),
66- privateMsgFilter(),
67- pull.drain(updateUnreadMsgsCache)
68- )
69-
70- pull(
71- next(api.feed.pull.private, {old: false, live: true, property: ['value', 'timestamp']}),
72- privateMsgFilter(),
73- pull.drain(updateUnreadMsgsCache)
74- )
75-
76- //TODO: calculate unread state for public threads/blogs
77- // pull(
78- // next(api.feed.pull.public, {reverse: true, limit: 100, live: false, property: ['value', 'timestamp']}),
79- // pull.drain(msg => {
80- //
81- // })
82- // )
83-
84- return h('Context -feed', [
85- LevelOneContext(),
86- LevelTwoContext()
87- ])
88-
89- function LevelOneContext () {
90- function isDiscoverContext (loc) {
91- const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'userShow']
92-
93- return PAGES_UNDER_DISCOVER.includes(location.page)
94- || get(location, 'value.private') === undefined
95- }
96-
97- const prepend = [
98- // Nearby
99- computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null),
100- map(nearby, feedId => Option({
101- notifications: notifications(feedId),
102- imageEl: api.about.html.avatar(feedId, 'small'),
103- label: api.about.obs.name(feedId),
104- selected: location.feed === feedId,
105- location: computed(recentMsgCache, recent => {
106- const lastMsg = recent.find(msg => msg.value.author === feedId)
107- return lastMsg
108- ? Object.assign(lastMsg, { feed: feedId })
109- : { page: 'threadNew', feed: feedId }
110- }),
111- }), { comparer: (a, b) => a === b }),
112-
113- // ---------------------
114- computed(nearby, n => !isEmpty(n) ? h('hr') : null),
115-
116- // Discover
117- Option({
118- // notifications: '!', //TODO - count this!
119- // imageEl: h('i.fa.fa-binoculars'),
120- imageEl: h('i', [
121- h('img', { src: path.join(__dirname, '../../assets', 'discover.png') })
122- ]),
123- label: strings.blogIndex.title,
124- selected: isDiscoverContext(location),
125- location: { page: 'blogIndex' },
126- })
127- ]
128-
129- return api.app.html.scroller({
130- classList: [ 'level', '-one' ],
131- prepend,
132- stream: api.feed.pull.private,
133- filter: privateMsgFilter,
134- store: recentMsgCache,
135- updateTop: updateRecentMsgCache,
136- updateBottom: updateRecentMsgCache,
137- render: (msgObs) => {
138- const msg = resolve(msgObs)
139- const { author } = msg.value
140- if (nearby.has(author)) return
141-
142- return Option({
143- //the number of threads with each peer
144- notifications: notifications(author),
145- imageEl: api.about.html.avatar(author),
146- label: api.about.obs.name(author),
147- selected: location.feed === author,
148- location: Object.assign({}, msg, { feed: author }) // TODO make obs?
149- })
150- }
151- })
152-
153- function updateRecentMsgCache (soFar, newMsg) {
154- soFar.transaction(() => {
155- const { author, timestamp } = newMsg.value
156- const index = indexOf(soFar, (msg) => author === resolve(msg).value.author)
157- var object = Value()
158-
159- if (index >= 0) {
160- // reference already exists, lets use this instead!
161- const existingMsg = soFar.get(index)
162-
163- if (resolve(existingMsg).value.timestamp > timestamp) return
164- // but abort if the existing reference is newer
165-
166- object = existingMsg
167- soFar.deleteAt(index)
168- }
169-
170- object.set(newMsg)
171-
172- const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
173- if (justOlderPosition > -1) {
174- soFar.insert(object, justOlderPosition)
175- } else {
176- soFar.push(object)
177- }
178- })
179- }
180-
181- }
182-
183- function getUnreadMsgsCache (key) {
184- var cache = unreadMsgsCache.get(key)
185- if (!cache) {
186- cache = Set ()
187- unreadMsgsCache.put(key, cache)
188- }
189- return cache
190- }
191-
192- function notifications (key) {
193- return computed(getUnreadMsgsCache(key), cache => cache.length)
194- }
195-
196- function LevelTwoContext () {
197- const { key, value, feed: targetUser, page } = location
198- const root = get(value, 'content.root', key)
199- if (!targetUser) return
200- if (page === 'userShow') return
201-
202-
203- const prepend = Option({
204- selected: page === 'threadNew',
205- location: {page: 'threadNew', feed: targetUser},
206- label: h('Button', strings.threadNew.action.new),
207- })
208-
209- var userLastMsgCache = usersLastMsgCache.get(targetUser)
210- if (!userLastMsgCache) {
211- userLastMsgCache = MutantArray()
212- usersLastMsgCache.put(targetUser, userLastMsgCache)
213- }
214-
215- return api.app.html.scroller({
216- classList: [ 'level', '-two' ],
217- prepend,
218- stream: api.feed.pull.private,
219- filter: () => pull(
220- pull.filter(msg => !msg.value.content.root),
221- pull.filter(msg => msg.value.content.type === 'post'),
222- pull.filter(msg => msg.value.content.recps),
223- pull.filter(msg => msg.value.content.recps
224- .map(recp => typeof recp === 'object' ? recp.link : recp)
225- .some(recp => recp === targetUser)
226- )
227- ),
228- store: userLastMsgCache,
229- updateTop: updateLastMsgCache,
230- updateBottom: updateLastMsgCache,
231- render: (rootMsgObs) => {
232- const rootMsg = resolve(rootMsgObs)
233- return Option({
234- notifications: notifications(rootMsg.key),
235- label: api.message.html.subject(rootMsg),
236- selected: rootMsg.key === root,
237- location: Object.assign(rootMsg, { feed: targetUser }),
238- })
239- }
240- })
241-
242- function updateLastMsgCache (soFar, newMsg) {
243- soFar.transaction(() => {
244- const { timestamp } = newMsg.value
245- const index = indexOf(soFar, (msg) => timestamp === resolve(msg).value.timestamp)
246-
247- if (index >= 0) return
248- // if reference already exists, abort
249-
250- var object = Value(newMsg)
251-
252- const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
253- if (justOlderPosition > -1) {
254- soFar.insert(object, justOlderPosition)
255- } else {
256- soFar.push(object)
257- }
258- })
259- }
260- }
261-
262- function Option ({ notifications = 0, imageEl, label, location, selected }) {
263- const className = selected ? '-selected' : ''
264- function goToLocation (e) {
265- e.preventDefault()
266- e.stopPropagation()
267- api.history.sync.push(resolve(location))
268- }
269-
270- if (!imageEl) {
271- return h('Option', { className, 'ev-click': goToLocation }, [
272- when(notifications, h('div.spacer', h('div.alert', notifications))),
273- h('div.label', label)
274- ])
275- }
276-
277- return h('Option', { className }, [
278- h('div.circle', [
279- when(notifications, h('div.alert', notifications)),
280- imageEl
281- ]),
282- h('div.label', { 'ev-click': goToLocation }, label)
283- ])
284- }
285-
286- function privateMsgFilter () {
287- return pull(
288- pull.filter(msg => msg.value.content.type === 'post'),
289- pull.filter(msg => msg.value.author != myKey),
290- pull.filter(msg => msg.value.content.recps)
291- )
292- }
293- }
294-}
295-
296-function indexOf (array, fn) {
297- for (var i = 0; i < array.getLength(); i++) {
298- if (fn(array.get(i))) {
299- return i
300- }
301- }
302- return -1
303-}
304-
305-
app/html/context.mcssView
@@ -1,139 +1,0 @@
1-Context {
2- flex-shrink: 0
3- flex-grow: 0
4- overflow: hidden
5- $backgroundPrimaryText
6-
7- display: flex
8-
9- div.level {
10- width: 13rem
11- overflow-y: auto
12- overflow-x: hidden
13-
14- border-right: 1px gainsboro solid
15-
16- section {
17- header {
18- $colorFontSubtle
19- padding: .5rem 1rem
20- }
21-
22- div.Option {}
23-
24- hr {
25- border: 1px solid gainsboro
26- border-bottom: none
27- margin: 0
28- }
29- }
30-
31- -one {}
32- -two {
33- section {
34- div.Option {
35- padding: 0 1rem
36- }
37- }
38- }
39- }
40-}
41-
42-Option {
43- min-width: 8rem
44- padding: .5rem 1rem
45- display: flex
46- align-items: center
47-
48- :hover {
49- cursor: pointer
50- $backgroundSelected
51- }
52-
53- -selected {
54- $backgroundSelected
55- }
56-
57- div.spacer {
58- display: flex
59- align-self: center
60- div.alert {
61- position: relative
62- width: 1.2rem
63- height: 1.2rem
64- border-radius: 1rem
65- top: -.2rem
66- left: -.2rem
67-
68- background-color: red
69- color: #fff
70- font-size: .8rem
71-
72- display: flex
73- justify-content: center
74- align-items: center
75- align-self: flex-start
76- }
77- }
78-
79- div.circle {
80- width: 3.6rem
81- position: relative
82-
83- div.alert {
84- position: absolute
85- width: 1.2rem
86- height: 1.2rem
87- border-radius: 1rem
88- top: -.2rem
89- left: -.2rem
90-
91- background-color: red
92- color: #fff
93- font-size: .8rem
94-
95- display: flex
96- justify-content: center
97- align-items: center
98- }
99-
100-
101- a img {
102-
103- }
104-
105- i {
106- $circleSmall
107- $colorPrimary
108- font-size: 1.3rem
109- display: flex
110- justify-content: center
111- align-items: center
112- }
113- }
114-
115- div.label {
116- $markdownSmall
117- (a) {
118- $colorFontBasic
119- :hover { text-decoration: none }
120- }
121-
122- flex-grow: 1
123-
124- display: flex
125- align-items: center
126- min-height: 3rem
127-
128- div.Button {
129- flex-grow: 1
130- }
131- }
132-}
133-
134-
135-
136-
137-
138-
139-
app/html/sideNav/sideNav.mcssView
@@ -1,0 +1,35 @@
1+SideNav {
2+ flex-shrink: 0
3+ flex-grow: 0
4+ overflow: hidden
5+ $backgroundPrimaryText
6+
7+ display: flex
8+
9+ div.level {
10+ width: 13rem
11+ overflow-y: auto
12+ overflow-x: hidden
13+
14+ border-right: 1px gainsboro solid
15+
16+ section {
17+ header {
18+ $colorFontSubtle
19+ padding: .5rem 1rem
20+ }
21+
22+ div.Option {}
23+
24+ hr {
25+ border: 1px solid gainsboro
26+ border-bottom: none
27+ margin: 0
28+ }
29+ }
30+
31+ -one {}
32+ -two {}
33+ }
34+}
35+
app/html/sideNav/sideNavDiscovery.jsView
@@ -1,0 +1,295 @@
1+const nest = require('depnest')
2+const { h, computed, 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+ 'unread.sync.markUnread': true
12+})
13+
14+exports.needs = nest({
15+ 'app.html.scroller': 'first',
16+ 'about.html.avatar': 'first',
17+ 'about.obs.name': 'first',
18+ 'feed.pull.private': 'first',
19+ 'keys.sync.id': 'first',
20+ 'history.sync.push': 'first',
21+ 'message.html.subject': 'first',
22+ 'sbot.obs.localPeers': 'first',
23+ 'translations.sync.strings': 'first',
24+ 'unread.sync.isUnread': 'first'
25+})
26+
27+exports.create = (api) => {
28+ var recentMsgCache = MutantArray()
29+ var usersLastMsgCache = Dict() // { id: [ msgs ] }
30+ var unreadMsgsCache = Dict() // { id: [ msgs ] }
31+
32+ return nest({
33+ //intercept markUnread and remove them from the cache.
34+ 'unread.sync.markUnread': function (msg) {
35+ unreadMsgsCache.get(msg.value.content.root || msg.key)
36+ .delete(msg.key)
37+ unreadMsgsCache.get(msg.value.author)
38+ .delete(msg.key)
39+ },
40+ 'app.html.sideNav': sideNav,
41+ })
42+
43+ function sideNav (location) {
44+ const strings = api.translations.sync.strings()
45+ const myKey = api.keys.sync.id()
46+
47+ var nearby = api.sbot.obs.localPeers()
48+
49+ // Unread message counts
50+ function updateCache (cache, msg) {
51+ if(api.unread.sync.isUnread(msg))
52+ cache.add(msg.key)
53+ else
54+ cache.delete(msg.key)
55+ }
56+
57+ function updateUnreadMsgsCache (msg) {
58+ updateCache(getUnreadMsgsCache(msg.value.author), msg)
59+ updateCache(getUnreadMsgsCache(msg.value.content.root || msg.key), msg)
60+ }
61+
62+ pull(
63+ next(api.feed.pull.private, {reverse: true, limit: 1000, live: false, property: ['value', 'timestamp']}),
64+ privateMsgFilter(),
65+ pull.drain(updateUnreadMsgsCache)
66+ )
67+
68+ pull(
69+ next(api.feed.pull.private, {old: false, live: true, property: ['value', 'timestamp']}),
70+ privateMsgFilter(),
71+ pull.drain(updateUnreadMsgsCache)
72+ )
73+
74+ return h('SideNav -discovery', [
75+ LevelOneSideNav(),
76+ LevelTwoSideNav()
77+ ])
78+
79+ function LevelOneSideNav () {
80+ function isDiscoverSideNav (loc) {
81+ const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'userShow']
82+
83+ return PAGES_UNDER_DISCOVER.includes(location.page)
84+ || get(location, 'value.private') === undefined
85+ }
86+
87+ const prepend = [
88+ // Nearby
89+ computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null),
90+ map(nearby, feedId => Option({
91+ notifications: notifications(feedId),
92+ imageEl: api.about.html.avatar(feedId, 'small'),
93+ label: api.about.obs.name(feedId),
94+ selected: location.feed === feedId,
95+ location: computed(recentMsgCache, recent => {
96+ const lastMsg = recent.find(msg => msg.value.author === feedId)
97+ return lastMsg
98+ ? Object.assign(lastMsg, { feed: feedId })
99+ : { page: 'threadNew', feed: feedId }
100+ }),
101+ }), { comparer: (a, b) => a === b }),
102+
103+ // ---------------------
104+ computed(nearby, n => !isEmpty(n) ? h('hr') : null),
105+
106+ // Discover
107+ Option({
108+ // notifications: '!', //TODO - count this!
109+ // imageEl: h('i.fa.fa-binoculars'),
110+ imageEl: h('i', [
111+ h('img', { src: path.join(__dirname, '../../../assets', 'discover.png') })
112+ ]),
113+ label: strings.blogIndex.title,
114+ selected: isDiscoverSideNav(location),
115+ location: { page: 'blogIndex' },
116+ })
117+ ]
118+
119+ return api.app.html.scroller({
120+ classList: [ 'level', '-one' ],
121+ prepend,
122+ stream: api.feed.pull.private,
123+ filter: privateMsgFilter,
124+ store: recentMsgCache,
125+ updateTop: updateRecentMsgCache,
126+ updateBottom: updateRecentMsgCache,
127+ render: (msgObs) => {
128+ const msg = resolve(msgObs)
129+ const { author } = msg.value
130+ if (nearby.has(author)) return
131+
132+ return Option({
133+ //the number of threads with each peer
134+ notifications: notifications(author),
135+ imageEl: api.about.html.avatar(author),
136+ label: api.about.obs.name(author),
137+ selected: location.feed === author,
138+ location: Object.assign({}, msg, { feed: author }) // TODO make obs?
139+ })
140+ }
141+ })
142+
143+ function updateRecentMsgCache (soFar, newMsg) {
144+ soFar.transaction(() => {
145+ const { author, timestamp } = newMsg.value
146+ const index = indexOf(soFar, (msg) => author === resolve(msg).value.author)
147+ var object = Value()
148+
149+ if (index >= 0) {
150+ // reference already exists, lets use this instead!
151+ const existingMsg = soFar.get(index)
152+
153+ if (resolve(existingMsg).value.timestamp > timestamp) return
154+ // but abort if the existing reference is newer
155+
156+ object = existingMsg
157+ soFar.deleteAt(index)
158+ }
159+
160+ object.set(newMsg)
161+
162+ const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
163+ if (justOlderPosition > -1) {
164+ soFar.insert(object, justOlderPosition)
165+ } else {
166+ soFar.push(object)
167+ }
168+ })
169+ }
170+
171+ }
172+
173+ function getUnreadMsgsCache (key) {
174+ var cache = unreadMsgsCache.get(key)
175+ if (!cache) {
176+ cache = Set ()
177+ unreadMsgsCache.put(key, cache)
178+ }
179+ return cache
180+ }
181+
182+ function notifications (key) {
183+ return computed(getUnreadMsgsCache(key), cache => cache.length)
184+ }
185+
186+ function LevelTwoSideNav () {
187+ const { key, value, feed: targetUser, page } = location
188+ const root = get(value, 'content.root', key)
189+ if (!targetUser) return
190+ if (page === 'userShow') return
191+
192+
193+ const prepend = Option({
194+ selected: page === 'threadNew',
195+ location: {page: 'threadNew', feed: targetUser},
196+ label: h('Button', strings.threadNew.action.new),
197+ })
198+
199+ var userLastMsgCache = usersLastMsgCache.get(targetUser)
200+ if (!userLastMsgCache) {
201+ userLastMsgCache = MutantArray()
202+ usersLastMsgCache.put(targetUser, userLastMsgCache)
203+ }
204+
205+ return api.app.html.scroller({
206+ classList: [ 'level', '-two' ],
207+ prepend,
208+ stream: api.feed.pull.private,
209+ filter: () => pull(
210+ pull.filter(msg => !msg.value.content.root),
211+ pull.filter(msg => msg.value.content.type === 'post'),
212+ pull.filter(msg => msg.value.content.recps),
213+ pull.filter(msg => msg.value.content.recps
214+ .map(recp => typeof recp === 'object' ? recp.link : recp)
215+ .some(recp => recp === targetUser)
216+ )
217+ ),
218+ store: userLastMsgCache,
219+ updateTop: updateLastMsgCache,
220+ updateBottom: updateLastMsgCache,
221+ render: (rootMsgObs) => {
222+ const rootMsg = resolve(rootMsgObs)
223+ return Option({
224+ notifications: notifications(rootMsg.key),
225+ label: api.message.html.subject(rootMsg),
226+ selected: rootMsg.key === root,
227+ location: Object.assign(rootMsg, { feed: targetUser }),
228+ })
229+ }
230+ })
231+
232+ function updateLastMsgCache (soFar, newMsg) {
233+ soFar.transaction(() => {
234+ const { timestamp } = newMsg.value
235+ const index = indexOf(soFar, (msg) => timestamp === resolve(msg).value.timestamp)
236+
237+ if (index >= 0) return
238+ // if reference already exists, abort
239+
240+ var object = Value(newMsg)
241+
242+ const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
243+ if (justOlderPosition > -1) {
244+ soFar.insert(object, justOlderPosition)
245+ } else {
246+ soFar.push(object)
247+ }
248+ })
249+ }
250+ }
251+
252+ function Option ({ notifications = 0, imageEl, label, location, selected }) {
253+ const className = selected ? '-selected' : ''
254+ function goToLocation (e) {
255+ e.preventDefault()
256+ e.stopPropagation()
257+ api.history.sync.push(resolve(location))
258+ }
259+
260+ if (!imageEl) {
261+ return h('Option', { className, 'ev-click': goToLocation }, [
262+ when(notifications, h('div.spacer', h('div.alert', notifications))),
263+ h('div.label', label)
264+ ])
265+ }
266+
267+ return h('Option', { className }, [
268+ h('div.circle', [
269+ when(notifications, h('div.alert', notifications)),
270+ imageEl
271+ ]),
272+ h('div.label', { 'ev-click': goToLocation }, label)
273+ ])
274+ }
275+
276+ function privateMsgFilter () {
277+ return pull(
278+ pull.filter(msg => msg.value.content.type === 'post'),
279+ pull.filter(msg => msg.value.author != myKey),
280+ pull.filter(msg => msg.value.content.recps)
281+ )
282+ }
283+ }
284+}
285+
286+function indexOf (array, fn) {
287+ for (var i = 0; i < array.getLength(); i++) {
288+ if (fn(array.get(i))) {
289+ return i
290+ }
291+ }
292+ return -1
293+}
294+
295+
app/html/sideNav/sideNavDiscovery.mcssView
@@ -1,0 +1,109 @@
1+SideNav -discovery {
2+ div.level {
3+ section {
4+ }
5+
6+ -one {}
7+ -two {
8+ section {
9+ div.Option {
10+ padding: 0 1rem
11+ }
12+ }
13+ }
14+ }
15+}
16+
17+Option {
18+ min-width: 8rem
19+ padding: .5rem 1rem
20+ display: flex
21+ align-items: center
22+
23+ :hover {
24+ cursor: pointer
25+ $backgroundSelected
26+ }
27+
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/index.jsView
@@ -4,15 +4,17 @@
44 },
55 html: {
66 app: require('./html/app'),
77 comments: require('./html/comments'),
8- context: require('./html/context'),
98 header: require('./html/header'),
109 thread: require('./html/thread'),
1110 link: require('./html/link'),
1211 blogCard: require('./html/blogCard'),
1312 blogNav: require('./html/blogNav'),
1413 scroller: require('./html/scroller'),
14+ sideNav: {
15+ discovery: require('./html/sideNav/sideNavDiscovery'),
16+ }
1517 },
1618 page: {
1719 blogIndex: require('./page/blogIndex'),
1820 blogNew: require('./page/blogNew'),
app/page/blogIndex.jsView
@@ -4,9 +4,9 @@
44
55 exports.gives = nest('app.page.blogIndex')
66
77 exports.needs = nest({
8- 'app.html.context': 'first',
8+ 'app.html.sideNav': 'first',
99 'app.html.blogCard': 'first',
1010 'app.html.blogNav': 'first',
1111 'app.html.scroller': 'first',
1212 // 'feed.pull.public': 'first',
@@ -37,17 +37,17 @@
3737 pull.filter(msg => !msg.value.content.root), // show only root messages
3838 pull.filter(msg => !api.message.sync.isBlocked(msg))
3939 ),
4040 // FUTURE : if we need better perf, we can add a persistent cache. At the moment this page is fast enough though.
41- // See implementation of app.html.context for example
41+ // See implementation of app.html.sideNav for example
4242 // store: recentMsgCache,
4343 // updateTop: updateRecentMsgCache,
4444 // updateBottom: updateRecentMsgCache,
4545 render
4646 })
4747
4848 return h('Page -blogIndex', {title: strings.home}, [
49- api.app.html.context(location),
49+ api.app.html.sideNav(location),
5050 blogs
5151 ])
5252 })
5353
app/page/blogNew.jsView
@@ -6,9 +6,9 @@
66
77 exports.gives = nest('app.page.blogNew')
88
99 exports.needs = nest({
10- 'app.html.context': 'first',
10+ 'app.html.sideNav': 'first',
1111 'channel.async.suggest': 'first',
1212 'history.sync.push': 'first',
1313 'message.html.compose': 'first',
1414 'translations.sync.strings': 'first',
@@ -59,9 +59,9 @@
5959 placeholder: strings.channel
6060 })
6161
6262 var page = h('Page -blogNew', [
63- api.app.html.context(location),
63+ api.app.html.sideNav(location),
6464 h('div.content', [
6565 h('div.container', [
6666 h('div.field -channel', [
6767 h('div.label', strings.channel),
app/page/blogSearch.jsView
@@ -4,9 +4,9 @@
44
55 exports.gives = nest('app.page.blogSearch')
66
77 exports.needs = nest({
8- 'app.html.context': 'first',
8+ 'app.html.sideNav': 'first',
99 'app.html.blogCard': 'first',
1010 'app.html.blogNav': 'first',
1111 'app.html.scroller': 'first',
1212 'channel.obs.recent': 'first',
@@ -76,17 +76,17 @@
7676 }),
7777 pull.filter(msg => !msg.value.content.root) // show only root messages
7878 ),
7979 // FUTURE : if we need better perf, we can add a persistent cache. At the moment this page is fast enough though.
80- // See implementation of app.html.context for example
80+ // See implementation of app.html.sideNav for example
8181 // store: recentMsgCache,
8282 // updateTop: updateRecentMsgCache,
8383 // updateBottom: updateRecentMsgCache,
8484 render
8585 })
8686
8787 return h('Page -blogSearch', {title: strings.home}, [
88- api.app.html.context(location),
88+ api.app.html.sideNav(location),
8989 blogs
9090 ])
9191 }
9292
app/page/blogShow.jsView
@@ -10,9 +10,9 @@
1010 'about.html.avatar': 'first',
1111 'about.obs.name': 'first',
1212 'app.html.blogNav': 'first',
1313 'app.html.comments': 'first',
14- 'app.html.context': 'first',
14+ 'app.html.sideNav': 'first',
1515 'contact.html.follow': 'first',
1616 'message.html.channel': 'first',
1717 'message.html.likes': 'first',
1818 'message.html.markdown': 'first',
@@ -39,9 +39,9 @@
3939
4040 const { timeago, channel, markdown, compose } = api.message.html
4141
4242 return h('Page -blogShow', [
43- api.app.html.context({ page: 'discover' }), // HACK to highlight discover
43+ api.app.html.sideNav({ page: 'discover' }), // HACK to highlight discover
4444 h('div.content', [
4545 h('section.top', [
4646 api.app.html.blogNav(location)
4747 ]),
app/page/threadNew.jsView
@@ -5,9 +5,9 @@
55
66 exports.needs = nest({
77 'about.html.image': 'first',
88 'about.obs.name': 'first',
9- 'app.html.context': 'first',
9+ 'app.html.sideNav': 'first',
1010 'app.html.thread': 'first',
1111 'history.sync.push': 'first',
1212 'keys.sync.id': 'first',
1313 'message.html.compose': 'first',
@@ -44,9 +44,9 @@
4444 (err, msg) => api.history.sync.push(err ? err : Object.assign(msg, { feed }))
4545 )
4646
4747 return h('Page -threadNew', {title: strings.threadNew.pageTitle}, [
48- api.app.html.context(location),
48+ api.app.html.sideNav(location),
4949 h('div.content', [
5050 h('div.container', [
5151 h('div.field -to', [
5252 h('div.label', strings.threadNew.field.to),
app/page/threadShow.jsView
@@ -5,9 +5,9 @@
55
66 exports.gives = nest('app.page.threadShow')
77
88 exports.needs = nest({
9- 'app.html.context': 'first',
9+ 'app.html.sideNav': 'first',
1010 'app.html.thread': 'first',
1111 'message.html.compose': 'first',
1212 'unread.sync.markRead': 'first',
1313 'feed.obs.thread': 'first'
@@ -34,9 +34,9 @@
3434 }
3535 const composer = api.message.html.compose({ meta, shrink: false })
3636
3737 return h('Page -threadShow', [
38- api.app.html.context(location),
38+ api.app.html.sideNav(location),
3939 h('div.content', [
4040 when(thread.subject, h('h1', thread.subject)),
4141 api.app.html.thread(thread),
4242 composer
app/page/userShow.jsView
@@ -9,9 +9,9 @@
99 exports.needs = nest({
1010 'about.html.avatar': 'first',
1111 'about.obs.name': 'first',
1212 'about.obs.description': 'first',
13- 'app.html.context': 'first',
13+ 'app.html.sideNav': 'first',
1414 'app.html.link': 'first',
1515 'app.html.blogCard': 'first',
1616 'app.html.blogNav': 'first',
1717 'app.html.scroller': 'first',
@@ -79,9 +79,9 @@
7979 const store = MutantArray()
8080 // store(console.log)
8181
8282 return h('Page -userShow', [
83- api.app.html.context(location),
83+ api.app.html.sideNav(location),
8484 api.app.html.scroller({
8585 classList: ['content'],
8686 prepend,
8787 // stream: api.feed.pull.profile(feed),

Built with git-ssb-web