Commit 0405c2c6e56360eb92b2acbfa5f6ea97ddc20475
endless scrolling on feed page, remember view scroll position
Matt McKegg committed on 10/29/2016, 5:19:49 AMParent: 96276e96832b173700c73ba0f9e6b5bb4fc737dd
Files changed
README.md | changed |
lib/pull-scroll.js | added |
main-window.js | changed |
package.json | changed |
styles/main-window.mcss | changed |
styles/loading.mcss | added |
views/public-feed.js | changed |
README.md | ||
---|---|---|
@@ -17,11 +17,14 @@ | ||
17 | 17 … | ## TODO |
18 | 18 … | |
19 | 19 … | - [x] Main navigation buttons |
20 | 20 … | - [x] Compressed feed (the algorithm :wink:) |
21 | -- [ ] Endless scrolling (or load more) on main feed | |
22 | -- [ ] Power user tabs (right-click, Open in New Tab -> tab interface appears safari style) | |
23 | -- [ ] Preserve scroll on back button | |
21 … | +- [x] Endless scrolling (or load more) on main feed [fake paginate, add a new section, leave the current one and remove the top most] | |
22 … | +- [x] Display fixed banner at top of view when there are new updates [scrolls to top of page and reloads view when clicked] | |
23 … | +- [x] Preserve scroll on back button | |
24 … | +- [ ] Notifications drop down (show unread mentions, digs, replies) | |
25 … | + - [ ] to support this, we need to track subscriptions and read status | |
26 … | +- [ ] Treat the different "views" more like tabs. They preserve their state when switched between [scroll position, forms]. | |
24 | 27 … | - [ ] Search |
25 | 28 … | - [ ] Easy navigation sidebar |
26 | 29 … | - [ ] Contacts sidebar |
27 | 30 … | - [ ] Show digs on posts in a nicer way (make it clear that you've dug something) |
lib/pull-scroll.js | ||
---|---|---|
@@ -1,0 +1,126 @@ | ||
1 … | +// FROM: https://raw.githubusercontent.com/dominictarr/pull-scroll/master/index.js | |
2 … | +var pull = require('pull-stream') | |
3 … | +var Pause = require('pull-pause') | |
4 … | +var isVisible = require('is-visible').isVisible | |
5 … | + | |
6 … | +var next = 'undefined' === typeof setImmediate ? setTimeout : setImmediate | |
7 … | + | |
8 … | +function isBottom (scroller, buffer) { | |
9 … | + var rect = scroller.getBoundingClientRect() | |
10 … | + var topmax = scroller.scrollTopMax || (scroller.scrollHeight - rect.height) | |
11 … | + return scroller.scrollTop >= | |
12 … | + + ((topmax) - (buffer || 0)) | |
13 … | +} | |
14 … | + | |
15 … | +function isTop (scroller, buffer) { | |
16 … | + return scroller.scrollTop <= (buffer || 0) | |
17 … | +} | |
18 … | + | |
19 … | +function isFilled(content) { | |
20 … | + return ( | |
21 … | + !isVisible(content) | |
22 … | + //check if the scroller is not visible. | |
23 … | + // && content.getBoundingClientRect().height == 0 | |
24 … | + //and has children. if there are no children, | |
25 … | + //it might be size zero because it hasn't started yet. | |
26 … | +// && | |
27 … | + && content.children.length > 10 | |
28 … | + //&& !isVisible(scroller) | |
29 … | + ) | |
30 … | +} | |
31 … | + | |
32 … | +function isEnd(scroller, buffer, top) { | |
33 … | + //if the element is display none, don't read anything into it. | |
34 … | + return (top ? isTop : isBottom)(scroller, buffer) | |
35 … | +} | |
36 … | + | |
37 … | +function append(scroller, list, el, top, sticky) { | |
38 … | + if(!el) return | |
39 … | + var s = scroller.scrollHeight | |
40 … | + if(top && list.firstChild) | |
41 … | + list.insertBefore(el, list.firstChild) | |
42 … | + else | |
43 … | + list.appendChild(el) | |
44 … | + | |
45 … | + //scroll down by the height of the thing added. | |
46 … | + //if it added to the top (in non-sticky mode) | |
47 … | + //or added it to the bottom (in sticky mode) | |
48 … | + if(top !== sticky) { | |
49 … | + var st = list.scrollTop, d = (scroller.scrollHeight - s) + 1 | |
50 … | + scroller.scrollTop = scroller.scrollTop + d | |
51 … | + } | |
52 … | +} | |
53 … | + | |
54 … | +function overflow (el) { | |
55 … | + return el.style.overflowY || el.style.overflow || (function () { | |
56 … | + var style = getComputedStyle(el) | |
57 … | + return style.overflowY || el.style.overflow | |
58 … | + })() | |
59 … | +} | |
60 … | + | |
61 … | +var buffer = 1000 | |
62 … | +module.exports = function Scroller(scroller, content, render, top, sticky, cb) { | |
63 … | + //if second argument is a function, | |
64 … | + //it means the scroller and content elements are the same. | |
65 … | + if('function' === typeof content) { | |
66 … | + cb = sticky | |
67 … | + top = render | |
68 … | + render = content | |
69 … | + content = scroller | |
70 … | + } | |
71 … | + | |
72 … | + if(!cb) cb = function (err) { if(err) throw err } | |
73 … | + | |
74 … | + var f = overflow(scroller) | |
75 … | + if(!/auto|scroll/.test(f)) | |
76 … | + throw new Error('scroller.style.overflowY must be scroll or auto, was:' + f + '!') | |
77 … | + scroller.addEventListener('scroll', scroll) | |
78 … | + var pause = Pause(function () {}), queue = [] | |
79 … | + | |
80 … | + //apply some changes to the dom, but ensure that | |
81 … | + //`element` is at the same place on screen afterwards. | |
82 … | + | |
83 … | + function add () { | |
84 … | + if(queue.length) | |
85 … | + append(scroller, content, render(queue.shift()), top, sticky) | |
86 … | + } | |
87 … | + | |
88 … | + function scroll (ev) { | |
89 … | + if (isEnd(scroller, buffer, top) || isFilled(content)) { | |
90 … | + pause.resume() | |
91 … | + add() | |
92 … | + } | |
93 … | + } | |
94 … | + | |
95 … | + // pause.pause() | |
96 … | + // | |
97 … | + // //wait until the scroller has been added to the document | |
98 … | + // next(function next () { | |
99 … | + // if(scroller.parentElement) pause.resume() | |
100 … | + // else setTimeout(next, 100) | |
101 … | + // }) | |
102 … | + | |
103 … | + var stream = pull( | |
104 … | + pause, | |
105 … | + pull.drain(function (e) { | |
106 … | + queue.push(e) | |
107 … | + //we don't know the scroll bar positions if it's display none | |
108 … | + //so we have to wait until it becomes visible again. | |
109 … | + if(!isVisible(content)) { | |
110 … | + if(content.children.length < 10) add() | |
111 … | + } | |
112 … | + else if(isEnd(scroller, buffer, top)) add() | |
113 … | + | |
114 … | + if(queue.length > 5) pause.pause() | |
115 … | + }, function (err) { | |
116 … | + scroller.removeEventListener('scroll', scroll) | |
117 … | + if(err) console.error(err) | |
118 … | + cb ? cb(err) : console.error(err) | |
119 … | + }) | |
120 … | + ) | |
121 … | + | |
122 … | + stream.visible = add | |
123 … | + | |
124 … | + return stream | |
125 … | + | |
126 … | +} |
main-window.js | ||
---|---|---|
@@ -6,8 +6,12 @@ | ||
6 | 6 … | var plugs = require('patchbay/plugs') |
7 | 7 … | var Value = require('@mmckegg/mutant/value') |
8 | 8 … | var when = require('@mmckegg/mutant/when') |
9 | 9 … | var computed = require('@mmckegg/mutant/computed') |
10 … | +var toCollection = require('@mmckegg/mutant/dict-to-collection') | |
11 … | +var MutantDict = require('@mmckegg/mutant/dict') | |
12 … | +var MutantMap = require('@mmckegg/mutant/map') | |
13 … | +var watch = require('@mmckegg/mutant/watch') | |
10 | 14 … | |
11 | 15 … | module.exports = function (config, ssbClient) { |
12 | 16 … | var api = SbotApi(ssbClient, config) |
13 | 17 … | var modules = combine(extend(Modules, { |
@@ -26,18 +30,18 @@ | ||
26 | 30 … | |
27 | 31 … | var screenView = plugs.first(modules.plugs.screen_view) |
28 | 32 … | var forwardHistory = [] |
29 | 33 … | var backHistory = [] |
30 | - var views = { | |
34 … | + var views = MutantDict({ | |
31 | 35 … | '/public': screenView('/public') |
32 | - } | |
36 … | + }) | |
37 … | + | |
33 | 38 … | var canGoForward = Value(false) |
34 | 39 … | var canGoBack = Value(false) |
35 | - var currentView = Value(['/public']) | |
36 | - var rootElement = computed(currentView, (data) => { | |
37 | - if (Array.isArray(data)) { | |
38 | - return views[data[0]] | |
39 | - } | |
40 … | + var currentView = Value('/public') | |
41 … | + | |
42 … | + watch(currentView, (view) => { | |
43 … | + window.location.hash = `#${view}` | |
40 | 44 … | }) |
41 | 45 … | |
42 | 46 … | window.onhashchange = function (ev) { |
43 | 47 … | var path = window.location.hash.substring(1) |
@@ -45,11 +49,13 @@ | ||
45 | 49 … | setView(path) |
46 | 50 … | } |
47 | 51 … | } |
48 | 52 … | |
49 | - var mainElement = h('div.main', [ | |
50 | - rootElement | |
51 | - ]) | |
53 … | + var mainElement = h('div.main', MutantMap(toCollection(views), (item) => { | |
54 … | + return h('div.view', { | |
55 … | + hidden: computed([item.key, currentView], (a, b) => a !== b) | |
56 … | + }, [ item.value ]) | |
57 … | + })) | |
52 | 58 … | |
53 | 59 … | return h('MainWindow', { |
54 | 60 … | classList: [ '-' + process.platform ] |
55 | 61 … | }, [ |
@@ -110,24 +116,24 @@ | ||
110 | 116 … | canGoBack.set(true) |
111 | 117 … | } |
112 | 118 … | } |
113 | 119 … | |
114 | - function setView (view, ...args) { | |
115 | - var newView = [view, ...args] | |
116 | - views[view] = screenView(view, ...args) | |
117 | - if (!isSame(newView, currentView())) { | |
120 … | + function setView (view) { | |
121 … | + if (!views.has(view)) { | |
122 … | + views.put(view, screenView(view)) | |
123 … | + } | |
124 … | + if (view !== currentView()) { | |
118 | 125 … | canGoForward.set(false) |
119 | 126 … | canGoBack.set(true) |
120 | 127 … | forwardHistory.length = 0 |
121 | 128 … | backHistory.push(currentView()) |
129 … | + currentView.set(view) | |
122 | 130 … | } |
123 | - currentView.set(newView) | |
124 | - currentView().scrollTop = 0 | |
125 | 131 … | } |
126 | 132 … | |
127 | 133 … | function selected (view) { |
128 | 134 … | return computed([currentView, view], (currentView, view) => { |
129 | - return currentView && currentView[0] === view | |
135 … | + return currentView === view | |
130 | 136 … | }) |
131 | 137 … | } |
132 | 138 … | } |
133 | 139 … |
package.json | ||
---|---|---|
@@ -9,25 +9,30 @@ | ||
9 | 9 … | "postinstall": "npm run rebuild", |
10 | 10 … | "rebuild": "npm rebuild --runtime=electron --target=1.4.3 --abi=50 --disturl=https://atom.io/download/atom-shell" |
11 | 11 … | }, |
12 | 12 … | "author": "", |
13 | - "license": "ISC", | |
13 … | + "license": "GPL", | |
14 | 14 … | "dependencies": { |
15 | - "@mmckegg/mutant": "^3.6.1", | |
15 … | + "@mmckegg/mutant": "~3.7.0", | |
16 | 16 … | "data-uri-to-buffer": "0.0.4", |
17 | - "electron": "^1.4.4", | |
18 | - "electron-default-menu": "^1.0.0", | |
19 | - "insert-css": "^1.0.0", | |
20 | - "micro-css": "^0.6.2", | |
21 | - "patchbay": "^3.5.0", | |
22 | - "pull-file": "^1.0.0", | |
17 … | + "electron": "~1.4.4", | |
18 … | + "electron-default-menu": "~1.0.0", | |
19 … | + "insert-css": "~1.0.0", | |
20 … | + "is-visible": "^2.1.1", | |
21 … | + "micro-css": "~0.6.2", | |
22 … | + "patchbay": "~3.5.0", | |
23 … | + "pull-abortable": "^4.1.0", | |
24 … | + "pull-file": "~1.0.0", | |
23 | 25 … | "pull-identify-filetype": "^1.1.0", |
24 | - "pull-stream": "^3.4.5", | |
25 | - "scuttlebot": "^9.2.0", | |
26 | - "sorted-array-functions": "^1.0.0", | |
27 | - "ssb-blobs": "^0.1.7", | |
28 | - "ssb-keys": "^7.0.0", | |
29 | - "ssb-links": "^2.0.0", | |
30 | - "ssb-query": "^0.1.1", | |
31 | - "ssb-ref": "^2.6.2" | |
26 … | + "pull-next": "0.0.2", | |
27 … | + "pull-pause": "0.0.0", | |
28 … | + "pull-pushable": "^2.0.1", | |
29 … | + "pull-stream": "~3.4.5", | |
30 … | + "scuttlebot": "~9.2.0", | |
31 … | + "sorted-array-functions": "~1.0.0", | |
32 … | + "ssb-blobs": "~0.1.7", | |
33 … | + "ssb-keys": "~7.0.0", | |
34 … | + "ssb-links": "~2.0.0", | |
35 … | + "ssb-query": "~0.1.1", | |
36 … | + "ssb-ref": "~2.6.2" | |
32 | 37 … | } |
33 | 38 … | } |
styles/main-window.mcss | ||
---|---|---|
@@ -115,14 +115,38 @@ | ||
115 | 115 … | } |
116 | 116 … | |
117 | 117 … | div.main { |
118 | 118 … | flex: 1 |
119 | - overflow: auto | |
120 | 119 … | display: flex |
121 | 120 … | |
122 | - div { | |
121 … | + div.view { | |
122 … | + | |
123 … | + [hidden] { | |
124 … | + display: none | |
125 … | + } | |
126 … | + | |
127 … | + display: flex | |
128 … | + flex-direction: column | |
123 | 129 … | flex: 1 |
124 | - -webkit-user-select: text | |
130 … | + | |
131 … | + a.loader { | |
132 … | + padding: 10px; | |
133 … | + display: block; | |
134 … | + background: rgb(214, 228, 236); | |
135 … | + border: 1px solid rgb(187, 201, 210); | |
136 … | + text-align: center; | |
137 … | + | |
138 … | + :hover { | |
139 … | + background: rgb(220, 242, 255); | |
140 … | + } | |
141 … | + | |
142 … | + animation: 0.5s slide-in | |
143 … | + position: relative | |
144 … | + } | |
145 … | + | |
146 … | + div { | |
147 … | + -webkit-user-select: text | |
148 … | + } | |
125 | 149 … | } |
126 | 150 … | } |
127 | 151 … | |
128 | 152 … | div.bottom { |
styles/loading.mcss | ||
---|---|---|
@@ -1,0 +1,63 @@ | ||
1 … | +Loading { | |
2 … | + height: 50% | |
3 … | + display: flex | |
4 … | + align-items: center | |
5 … | + justify-content: center | |
6 … | + | |
7 … | + -inline { | |
8 … | + height: 16px | |
9 … | + width: 16px | |
10 … | + display: inline-block | |
11 … | + margin: -3px 3px | |
12 … | + | |
13 … | + ::before { | |
14 … | + display: block | |
15 … | + height: 16px | |
16 … | + width: 16px | |
17 … | + } | |
18 … | + } | |
19 … | + | |
20 … | + -large { | |
21 … | + ::before { | |
22 … | + height: 100px | |
23 … | + width: 100px | |
24 … | + } | |
25 … | + ::after { | |
26 … | + color: #CCC; | |
27 … | + content: 'Loading...' | |
28 … | + font-size: 200% | |
29 … | + } | |
30 … | + } | |
31 … | + | |
32 … | + ::before { | |
33 … | + content: ' ' | |
34 … | + height: 50px | |
35 … | + width: 50px | |
36 … | + background-image: svg(waitingIcon) | |
37 … | + background-repeat: no-repeat | |
38 … | + background-position: center | |
39 … | + background-size: contain | |
40 … | + animation: spin 3s infinite linear | |
41 … | + } | |
42 … | +} | |
43 … | + | |
44 … | +@svg waitingIcon { | |
45 … | + width: 30px | |
46 … | + height: 30px | |
47 … | + content: "<circle cx='15' cy='15' r='10' /><circle cx='10' cy='10' r='2' /><circle cx='20' cy='20' r='3' />" | |
48 … | + | |
49 … | + circle { | |
50 … | + stroke: #CCC | |
51 … | + stroke-width: 3px | |
52 … | + fill: none | |
53 … | + } | |
54 … | +} | |
55 … | + | |
56 … | +@keyframes spin { | |
57 … | + 0% { | |
58 … | + transform: rotate(0deg); | |
59 … | + } | |
60 … | + 100% { | |
61 … | + transform: rotate(360deg); | |
62 … | + } | |
63 … | +} |
views/public-feed.js | ||
---|---|---|
@@ -1,9 +1,15 @@ | ||
1 | 1 … | var SortedArray = require('sorted-array-functions') |
2 | 2 … | var Value = require('@mmckegg/mutant/value') |
3 | -var MutantMap = require('@mmckegg/mutant/map') | |
4 | 3 … | var h = require('@mmckegg/mutant/html-element') |
5 | 4 … | var when = require('@mmckegg/mutant/when') |
5 … | +var computed = require('@mmckegg/mutant/computed') | |
6 … | +var MutantArray = require('@mmckegg/mutant/array') | |
7 … | +var pullPushable = require('pull-pushable') | |
8 … | +var pullNext = require('pull-next') | |
9 … | +var Scroller = require('../lib/pull-scroll') | |
10 … | +var Abortable = require('pull-abortable') | |
11 … | + | |
6 | 12 … | var m = require('../lib/h') |
7 | 13 … | |
8 | 14 … | var pull = require('pull-stream') |
9 | 15 … | |
@@ -17,79 +23,22 @@ | ||
17 | 23 … | |
18 | 24 … | exports.screen_view = function (path, sbot) { |
19 | 25 … | if (path === '/public') { |
20 | 26 … | var sync = Value(false) |
21 | - var events = Value([]) | |
22 | 27 … | var updates = Value(0) |
23 | 28 … | |
24 | - var updateLoader = m('a', { | |
29 … | + var updateLoader = m('a.loader', { | |
25 | 30 … | href: '#', |
26 | - style: { | |
27 | - 'padding': '10px', | |
28 | - 'display': 'block', | |
29 | - 'background': '#d6e4ec', | |
30 | - 'border': '1px solid #bbc9d2', | |
31 | - 'text-align': 'center' | |
32 | - }, | |
33 | 31 … | 'ev-click': refresh |
34 | - }, [ 'Load ', h('strong', [updates]), ' update(s)' ]) | |
32 … | + }, [ | |
33 … | + 'Show ', | |
34 … | + h('strong', [updates]), ' ', | |
35 … | + when(computed(updates, a => a === 1), 'update', 'updates') | |
36 … | + ]) | |
35 | 37 … | |
36 | - var content = h('div.column.scroller__content', [ | |
37 | - when(updates, updateLoader), | |
38 | - MutantMap(events, (group) => { | |
39 | - if (group.type === 'message') { | |
40 | - var meta = null | |
41 | - var replies = group.replies.slice(-3).map(message_render) | |
42 | - var renderedMessage = group.message ? message_render(group.message) : null | |
43 | - if (renderedMessage) { | |
44 | - if (group.lastUpdateType === 'reply') { | |
45 | - meta = m('div.meta', [ | |
46 | - manyPeople(group.repliesFrom), ' replied' | |
47 | - ]) | |
48 | - } else if (group.lastUpdateType === 'dig') { | |
49 | - meta = m('div.meta', [ | |
50 | - manyPeople(group.digs), ' dug this message' | |
51 | - ]) | |
52 | - } | |
38 … | + var content = h('div.column.scroller__content') | |
53 | 39 … | |
54 | - return m('FeedEvent', [ | |
55 | - meta, | |
56 | - renderedMessage, | |
57 | - when(replies.length, [ | |
58 | - when(group.replies.length > replies.length, | |
59 | - m('a.full', {href: `#${group.messageId}`}, ['View full thread']) | |
60 | - ), | |
61 | - m('div.replies', replies) | |
62 | - ]) | |
63 | - ]) | |
64 | - } else { | |
65 | - if (group.lastUpdateType === 'reply') { | |
66 | - meta = m('div.meta', [ | |
67 | - manyPeople(group.repliesFrom), ' replied to ', message_link(group.messageId) | |
68 | - ]) | |
69 | - } else if (group.lastUpdateType === 'dig') { | |
70 | - meta = m('div.meta', [ | |
71 | - manyPeople(group.digs), ' dug ', message_link(group.messageId) | |
72 | - ]) | |
73 | - } | |
74 | - | |
75 | - if (meta || replies.length) { | |
76 | - return m('FeedEvent', [ | |
77 | - meta, m('div.replies', replies) | |
78 | - ]) | |
79 | - } | |
80 | - } | |
81 | - } else if (group.type === 'follow') { | |
82 | - return m('FeedEvent -follow', [ | |
83 | - m('div.meta', [ | |
84 | - person(group.id), ' followed ', manyPeople(group.contacts) | |
85 | - ]) | |
86 | - ]) | |
87 | - } | |
88 | - }, {maxTime: 5}) | |
89 | - ]) | |
90 | - | |
91 | - var div = h('div.column.scroller', { | |
40 … | + var scrollElement = h('div.column.scroller', { | |
92 | 41 … | style: { |
93 | 42 … | 'overflow': 'auto' |
94 | 43 … | } |
95 | 44 … | }, [ |
@@ -98,37 +47,125 @@ | ||
98 | 47 … | content |
99 | 48 … | ]) |
100 | 49 … | ]) |
101 | 50 … | |
102 | - refresh() | |
51 … | + setTimeout(refresh, 10) | |
103 | 52 … | |
104 | 53 … | pull( |
105 | 54 … | sbot_log({old: false}), |
106 | 55 … | pull.drain((item) => { |
107 | - updates.set(updates() + 1) | |
56 … | + if (!item.value.content.type === 'vote') { | |
57 … | + updates.set(updates() + 1) | |
58 … | + } | |
108 | 59 … | }) |
109 | 60 … | ) |
110 | 61 … | |
111 | - // pull( | |
112 | - // u.next(sbot_log, {reverse: true, limit: 100, live: false}), | |
113 | - // Scroller(div, content, message_render, false, false) | |
114 | - // ) | |
62 … | + var abortLastFeed = null | |
115 | 63 … | |
116 | - return div | |
64 … | + return MutantArray([ | |
65 … | + when(updates, updateLoader), | |
66 … | + when(sync, scrollElement, m('Loading -large')) | |
67 … | + ]) | |
117 | 68 … | } |
118 | 69 … | |
119 | 70 … | // scoped |
120 | 71 … | function refresh () { |
72 … | + if (abortLastFeed) { | |
73 … | + abortLastFeed() | |
74 … | + } | |
75 … | + updates.set(0) | |
76 … | + sync.set(false) | |
77 … | + content.innerHTML = '' | |
78 … | + | |
79 … | + var abortable = Abortable() | |
80 … | + abortLastFeed = abortable.abort | |
81 … | + | |
121 | 82 … | pull( |
122 | - sbot_log({reverse: true, limit: 500, live: false}), | |
83 … | + FeedSummary(sbot_log, 100, () => { | |
84 … | + sync.set(true) | |
85 … | + }), | |
86 … | + abortable, | |
87 … | + Scroller(scrollElement, content, renderItem, false, false) | |
88 … | + ) | |
89 … | + } | |
90 … | +} | |
91 … | + | |
92 … | +function FeedSummary (stream, windowSize, cb) { | |
93 … | + var last = null | |
94 … | + var returned = false | |
95 … | + return pullNext(() => { | |
96 … | + var next = {reverse: true, limit: windowSize, live: false} | |
97 … | + if (last) { | |
98 … | + next.lt = last.timestamp | |
99 … | + } | |
100 … | + var pushable = pullPushable() | |
101 … | + pull( | |
102 … | + sbot_log(next), | |
123 | 103 … | pull.collect((err, values) => { |
124 | 104 … | if (err) throw err |
125 | - events.set(groupMessages(values)) | |
126 | - sync.set(true) | |
127 | - updates.set(0) | |
105 … | + groupMessages(values).forEach(v => pushable.push(v)) | |
106 … | + last = values[values.length - 1] | |
107 … | + pushable.end() | |
108 … | + if (!returned) cb && cb() | |
109 … | + returned = true | |
128 | 110 … | }) |
129 | 111 … | ) |
112 … | + return pushable | |
113 … | + }) | |
114 … | +} | |
115 … | + | |
116 … | +function renderItem (item) { | |
117 … | + if (item.type === 'message') { | |
118 … | + var meta = null | |
119 … | + var replies = item.replies.slice(-3).map(message_render) | |
120 … | + var renderedMessage = item.message ? message_render(item.message) : null | |
121 … | + if (renderedMessage) { | |
122 … | + if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
123 … | + meta = m('div.meta', [ | |
124 … | + manyPeople(item.repliesFrom), ' replied' | |
125 … | + ]) | |
126 … | + } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
127 … | + meta = m('div.meta', [ | |
128 … | + manyPeople(item.digs), ' dug this message' | |
129 … | + ]) | |
130 … | + } | |
131 … | + | |
132 … | + return m('FeedEvent', [ | |
133 … | + meta, | |
134 … | + renderedMessage, | |
135 … | + when(replies.length, [ | |
136 … | + when(item.replies.length > replies.length, | |
137 … | + m('a.full', {href: `#${item.messageId}`}, ['View full thread']) | |
138 … | + ), | |
139 … | + m('div.replies', replies) | |
140 … | + ]) | |
141 … | + ]) | |
142 … | + } else { | |
143 … | + if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
144 … | + meta = m('div.meta', [ | |
145 … | + manyPeople(item.repliesFrom), ' replied to ', message_link(item.messageId) | |
146 … | + ]) | |
147 … | + } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
148 … | + meta = m('div.meta', [ | |
149 … | + manyPeople(item.digs), ' dug ', message_link(item.messageId) | |
150 … | + ]) | |
151 … | + } | |
152 … | + | |
153 … | + if (meta || replies.length) { | |
154 … | + return m('FeedEvent', [ | |
155 … | + meta, m('div.replies', replies) | |
156 … | + ]) | |
157 … | + } | |
158 … | + } | |
159 … | + } else if (item.type === 'follow') { | |
160 … | + return m('FeedEvent -follow', [ | |
161 … | + m('div.meta', [ | |
162 … | + person(item.id), ' followed ', manyPeople(item.contacts) | |
163 … | + ]) | |
164 … | + ]) | |
130 | 165 … | } |
166 … | + | |
167 … | + return h('div') | |
131 | 168 … | } |
132 | 169 … | |
133 | 170 … | function person (id) { |
134 | 171 … | return avatar_link(id, avatar_name(id), '') |
@@ -137,27 +174,29 @@ | ||
137 | 174 … | function manyPeople (ids) { |
138 | 175 … | ids = Array.from(ids) |
139 | 176 … | var featuredIds = ids.slice(-3).reverse() |
140 | 177 … | |
141 | - if (ids.length > 3) { | |
142 | - return [ | |
143 | - person(featuredIds[0]), ', ', | |
144 | - person(featuredIds[1]), | |
145 | - ' and ', ids.length - 2, ' others' | |
146 | - ] | |
147 | - } else if (ids.length === 3) { | |
148 | - return [ | |
149 | - person(featuredIds[0]), ', ', | |
150 | - person(featuredIds[1]), ' and ', | |
151 | - person(featuredIds[2]) | |
152 | - ] | |
153 | - } else if (ids.length === 2) { | |
154 | - return [ | |
155 | - person(featuredIds[0]), ' and ', | |
156 | - person(featuredIds[1]) | |
157 | - ] | |
158 | - } else { | |
159 | - return person(featuredIds[0]) | |
178 … | + if (ids.length) { | |
179 … | + if (ids.length > 3) { | |
180 … | + return [ | |
181 … | + person(featuredIds[0]), ', ', | |
182 … | + person(featuredIds[1]), | |
183 … | + ' and ', ids.length - 2, ' others' | |
184 … | + ] | |
185 … | + } else if (ids.length === 3) { | |
186 … | + return [ | |
187 … | + person(featuredIds[0]), ', ', | |
188 … | + person(featuredIds[1]), ' and ', | |
189 … | + person(featuredIds[2]) | |
190 … | + ] | |
191 … | + } else if (ids.length === 2) { | |
192 … | + return [ | |
193 … | + person(featuredIds[0]), ' and ', | |
194 … | + person(featuredIds[1]) | |
195 … | + ] | |
196 … | + } else { | |
197 … | + return person(featuredIds[0]) | |
198 … | + } | |
160 | 199 … | } |
161 | 200 … | } |
162 | 201 … | |
163 | 202 … | function groupMessages (messages) { |
@@ -178,8 +217,11 @@ | ||
178 | 217 … | group.digs.add(msg.value.author) |
179 | 218 … | group.updated = msg.timestamp |
180 | 219 … | } else { |
181 | 220 … | group.digs.delete(msg.value.author) |
221 … | + if (group.lastUpdateType === 'dig' && !group.digs.size && !group.replies.length) { | |
222 … | + group.lastUpdateType = 'reply' | |
223 … | + } | |
182 | 224 … | } |
183 | 225 … | } |
184 | 226 … | } |
185 | 227 … | } else { |
Built with git-ssb-web