Commit cff270be8e13b929bbb91731f12bf29dfa001197
preserve unread flags on messages after refresh (if haven't scrolled past)
also match unread flag appearance in light and dark themesMatt McKegg committed on 12/4/2017, 2:02:51 AM
Parent: 3ce9f274492ef4b74c0de22ed8b53fa746fe0c4d
Files changed
lib/scroller.js | changed |
locales/en.json | changed |
modules/feed/html/rollup.js | changed |
plugs/message/html/layout/default.js | changed |
styles/dark/feed-event.mcss | changed |
styles/dark/message.mcss | changed |
styles/light/feed-event.mcss | changed |
styles/light/message.mcss | changed |
lib/scroller.js | ||
---|---|---|
@@ -5,11 +5,17 @@ | ||
5 | 5 | var computed = require('mutant/computed') |
6 | 6 | |
7 | 7 | module.exports = Scroller |
8 | 8 | |
9 | -function Scroller (scroller, content, render, cb) { | |
9 | +function Scroller (scroller, content, render, opts) { | |
10 | + if (typeof opts === 'function') { | |
11 | + opts = {onDone: opts} | |
12 | + } else if (!opts) { | |
13 | + opts = {} | |
14 | + } | |
10 | 15 | var toRenderCount = Value(0) |
11 | 16 | var toAppendCount = Value(0) |
17 | + var pendingVisible = new Set() | |
12 | 18 | |
13 | 19 | var queueLength = computed([toRenderCount, toAppendCount], (a, b) => a + b) |
14 | 20 | |
15 | 21 | var pause = Pause(function () {}) |
@@ -19,9 +25,11 @@ | ||
19 | 25 | function appendLoop () { |
20 | 26 | var distanceFromBottom = scroller.scrollHeight - (scroller.scrollTop + scroller.clientHeight) |
21 | 27 | if (distanceFromBottom < scroller.clientHeight) { |
22 | 28 | while (appendQueue.length) { |
23 | - content.appendChild(appendQueue.shift()) | |
29 | + var element = appendQueue.shift() | |
30 | + content.appendChild(element) | |
31 | + pendingVisible.add(element) | |
24 | 32 | } |
25 | 33 | } |
26 | 34 | |
27 | 35 | toAppendCount.set(appendQueue.length) |
@@ -32,8 +40,9 @@ | ||
32 | 40 | |
33 | 41 | if (running || queueLength()) { |
34 | 42 | window.requestAnimationFrame(appendLoop) |
35 | 43 | } |
44 | + | |
36 | 45 | } |
37 | 46 | |
38 | 47 | var stream = pull( |
39 | 48 | pause, |
@@ -50,13 +59,30 @@ | ||
50 | 59 | pause.pause() |
51 | 60 | } |
52 | 61 | }, function (err) { |
53 | 62 | running = false |
54 | - cb ? cb(err) : console.error(err) | |
63 | + clearInterval(visibleInterval) | |
64 | + opts.onDone ? opts.onDone(err) : console.error(err) | |
55 | 65 | }) |
56 | 66 | ) |
57 | 67 | |
68 | + var visibleInterval = setInterval(() => { | |
69 | + // check for visible items every 2 seconds | |
70 | + Array.from(pendingVisible).forEach(checkVisible) | |
71 | + }, 2000) | |
72 | + | |
58 | 73 | stream.queue = queueLength |
59 | 74 | |
60 | 75 | appendLoop() |
61 | 76 | return stream |
77 | + | |
78 | + function checkVisible (element) { | |
79 | + var height = scroller.clientHeight | |
80 | + var rect = element.getBoundingClientRect() | |
81 | + if (height > 50 && rect.bottom < height) { | |
82 | + pendingVisible.delete(element) | |
83 | + if (opts.onItemVisible) { | |
84 | + onceIdle(() => opts.onItemVisible(element)) | |
85 | + } | |
86 | + } | |
87 | + } | |
62 | 88 | } |
locales/en.json | ||
---|---|---|
@@ -183,6 +183,7 @@ | ||
183 | 183 | "pt-BR": "Brazillian Portuguese", |
184 | 184 | "See less": "See less", |
185 | 185 | "See more": "See more", |
186 | 186 | "(missing message)": "(missing message)", |
187 | - "assigned a description to ": "assigned a description to " | |
187 | + "assigned a description to ": "assigned a description to ", | |
188 | + "Unread Message": "Unread Message" | |
188 | 189 | } |
modules/feed/html/rollup.js | ||
---|---|---|
@@ -60,8 +60,9 @@ | ||
60 | 60 | |
61 | 61 | var abortLastFeed = null |
62 | 62 | var content = Value() |
63 | 63 | var loading = Proxy(true) |
64 | + var unreadIds = new Set() | |
64 | 65 | var newSinceRefresh = new Set() |
65 | 66 | var highlightItems = new Set() |
66 | 67 | |
67 | 68 | var container = h('Scroller', { |
@@ -106,8 +107,9 @@ | ||
106 | 107 | // the feed as otherwise the 'show <n> updates message' will be |
107 | 108 | // shown on new messages that patchwork cannot render |
108 | 109 | if (canRenderMessage(msg) && (!msg.root || canRenderMessage(msg.root))) { |
109 | 110 | newSinceRefresh.add(msg.key) |
111 | + unreadIds.add(msg.key) | |
110 | 112 | } |
111 | 113 | |
112 | 114 | if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) { |
113 | 115 | refresh() |
@@ -143,11 +145,22 @@ | ||
143 | 145 | |
144 | 146 | highlightItems = newSinceRefresh |
145 | 147 | newSinceRefresh = new Set() |
146 | 148 | |
149 | + window.thing = unreadIds | |
150 | + | |
147 | 151 | var done = Value(false) |
148 | 152 | var stream = nextStepper(getStream, {reverse: true, limit: 50}) |
149 | - var scroller = Scroller(container, content(), renderItem, () => done.set(true)) | |
153 | + var scroller = Scroller(container, content(), renderItem, { | |
154 | + onDone: () => done.set(true), | |
155 | + onItemVisible: (item) => { | |
156 | + if (Array.isArray(item.msgIds)) { | |
157 | + item.msgIds.forEach(id => { | |
158 | + unreadIds.delete(id) | |
159 | + }) | |
160 | + } | |
161 | + } | |
162 | + }) | |
150 | 163 | |
151 | 164 | // track loading state |
152 | 165 | loading.set(computed([done, scroller.queue], (done, queue) => { |
153 | 166 | return !done && queue < 5 |
@@ -192,16 +205,17 @@ | ||
192 | 205 | } |
193 | 206 | }) |
194 | 207 | |
195 | 208 | var replies = item.replies.filter(isReply).sort(byAssertedTime) |
196 | - var highlightedReplies = replies.filter(isHighlighted) | |
209 | + var highlightedReplies = replies.filter(getPriority) | |
197 | 210 | var replyElements = replies.filter(displayFilter).slice(-3).map((msg) => { |
198 | 211 | var result = api.message.html.render(msg, { |
199 | 212 | previousId, |
200 | 213 | compact: compactFilter(msg, item), |
201 | - priority: highlightItems.has(msg.key) ? 2 : 0 | |
214 | + priority: getPriority(msg) | |
202 | 215 | }) |
203 | 216 | previousId = msg.key |
217 | + | |
204 | 218 | return [ |
205 | 219 | // insert missing message marker (if can't be found) |
206 | 220 | api.message.html.missing(last(msg.value.content.branch), msg), |
207 | 221 | result |
@@ -210,11 +224,13 @@ | ||
210 | 224 | |
211 | 225 | var renderedMessage = api.message.html.render(item, { |
212 | 226 | compact: compactFilter(item), |
213 | 227 | includeForks: false, // this is a root message, so forks are already displayed as replies |
214 | - priority: highlightItems.has(item.key) ? 2 : 0 | |
228 | + priority: getPriority(item) | |
215 | 229 | }) |
216 | 230 | |
231 | + unreadIds.delete(item.key) | |
232 | + | |
217 | 233 | if (!renderedMessage) return h('div') |
218 | 234 | if (lastBumpType) { |
219 | 235 | var bumps = lastBumpType === 'vote' |
220 | 236 | ? getLikeAuthors(groupedBumps[lastBumpType]) |
@@ -228,9 +244,9 @@ | ||
228 | 244 | |
229 | 245 | // if there are new messages, view full thread goes to the top of those, otherwise to very first reply |
230 | 246 | var anchorReply = highlightedReplies.length >= 3 ? highlightedReplies[0] : replies[0] |
231 | 247 | |
232 | - return h('FeedEvent -post', { | |
248 | + var result = h('FeedEvent -post', { | |
233 | 249 | attributes: { |
234 | 250 | 'data-root-id': item.key |
235 | 251 | } |
236 | 252 | }, [ |
@@ -240,12 +256,22 @@ | ||
240 | 256 | h('a.full', {href: item.key, anchor: anchorReply && anchorReply.key}, [i18n('View full thread') + ' (', replies.length, ')']) |
241 | 257 | ), |
242 | 258 | h('div.replies', replyElements) |
243 | 259 | ]) |
260 | + | |
261 | + result.msgIds = [item.key].concat(item.replies.map(x => x.key)) | |
262 | + | |
263 | + return result | |
244 | 264 | } |
245 | 265 | |
246 | - function isHighlighted (msg) { | |
247 | - return highlightItems.has(msg.key) | |
266 | + function getPriority (msg) { | |
267 | + if (highlightItems.has(msg.key)) { | |
268 | + return 2 | |
269 | + } else if (unreadIds.has(msg.key)) { | |
270 | + return 1 | |
271 | + } else { | |
272 | + return 0 | |
273 | + } | |
248 | 274 | } |
249 | 275 | }) |
250 | 276 | |
251 | 277 | function LookupRoot () { |
plugs/message/html/layout/default.js | ||
---|---|---|
@@ -53,8 +53,12 @@ | ||
53 | 53 | if (priority === 2) { |
54 | 54 | classList.push('-new') |
55 | 55 | } |
56 | 56 | |
57 | + if (priority === 1) { | |
58 | + classList.push('-unread') | |
59 | + } | |
60 | + | |
57 | 61 | return h('div', { |
58 | 62 | classList |
59 | 63 | }, [ |
60 | 64 | messageHeader(msg, { replyInfo, priority, needsExpand, expanded }), |
@@ -85,10 +89,12 @@ | ||
85 | 89 | // scoped |
86 | 90 | |
87 | 91 | function messageHeader (msg, {replyInfo, priority}) { |
88 | 92 | var additionalMeta = [] |
89 | - if (priority >= 2) { | |
93 | + if (priority === 2) { | |
90 | 94 | additionalMeta.push(h('span.flag -new', {title: i18n('New Message')})) |
95 | + } else if (priority === 1) { | |
96 | + additionalMeta.push(h('span.flag -unread', {title: i18n('Unread Message')})) | |
91 | 97 | } |
92 | 98 | return h('header', [ |
93 | 99 | h('div.main', [ |
94 | 100 | h('a.avatar', {href: `${msg.value.author}`}, [ |
styles/dark/feed-event.mcss | ||
---|---|---|
@@ -9,9 +9,9 @@ | ||
9 | 9 | :empty { |
10 | 10 | margin-bottom: -25px |
11 | 11 | } |
12 | 12 | |
13 | - -new { | |
13 | + -new, -unread { | |
14 | 14 | box-shadow: 0px 0px 2px #ffc800; |
15 | 15 | background: #fffdf7; |
16 | 16 | } |
17 | 17 |
styles/dark/message.mcss | ||
---|---|---|
@@ -92,9 +92,9 @@ | ||
92 | 92 | } |
93 | 93 | } |
94 | 94 | } |
95 | 95 | |
96 | - -new { | |
96 | + -new, -unread { | |
97 | 97 | box-shadow: 0 0 1px #efef00; |
98 | 98 | z-index: 1; |
99 | 99 | } |
100 | 100 | |
@@ -158,25 +158,32 @@ | ||
158 | 158 | background-position: center |
159 | 159 | display: inline-block |
160 | 160 | vertical-align: middle; |
161 | 161 | |
162 | + -unread { | |
163 | + :after { | |
164 | + content: ' unread' | |
165 | + color: #757474 | |
166 | + } | |
167 | + } | |
168 | + | |
162 | 169 | -new { |
170 | + :after { | |
171 | + content: ' new' | |
172 | + color: #757474 | |
173 | + } | |
174 | + } | |
175 | + | |
176 | + -new, -unread { | |
163 | 177 | width: auto |
164 | 178 | height: auto |
165 | 179 | color: #757474 |
180 | + font-size: 75% | |
166 | 181 | |
167 | 182 | :before { |
168 | - content: '✸ new' | |
169 | - font-size: 75% | |
170 | - } | |
171 | - | |
172 | - :hover { | |
173 | 183 | color: #efef00 |
184 | + content: '✸' | |
174 | 185 | } |
175 | - | |
176 | - :first-letter { | |
177 | - color: #efef00 | |
178 | - } | |
179 | 186 | } |
180 | 187 | } |
181 | 188 | |
182 | 189 | em { |
styles/light/feed-event.mcss | ||
---|---|---|
@@ -10,9 +10,9 @@ | ||
10 | 10 | :empty { |
11 | 11 | margin-bottom: -25px |
12 | 12 | } |
13 | 13 | |
14 | - -new { | |
14 | + -new, -unread { | |
15 | 15 | box-shadow: 0px 0px 2px #ffc800; |
16 | 16 | background: #fffdf7; |
17 | 17 | } |
18 | 18 |
styles/light/message.mcss | ||
---|---|---|
@@ -92,9 +92,9 @@ | ||
92 | 92 | color: #777 |
93 | 93 | } |
94 | 94 | } |
95 | 95 | |
96 | - -new { | |
96 | + -new, -unread { | |
97 | 97 | box-shadow: 0 0 1px #ffc600; |
98 | 98 | z-index: 1; |
99 | 99 | } |
100 | 100 | |
@@ -147,16 +147,36 @@ | ||
147 | 147 | display: inline-block |
148 | 148 | vertical-align: middle; |
149 | 149 | margin-top: -3px; |
150 | 150 | |
151 | + -unread { | |
152 | + :after { | |
153 | + content: ' unread' | |
154 | + font-size: 75% | |
155 | + color: #b7b7b7 | |
156 | + } | |
157 | + } | |
158 | + | |
151 | 159 | -new { |
152 | - background-image: svg(new) | |
160 | + :after { | |
161 | + content: ' new' | |
162 | + font-size: 75% | |
163 | + color: #b7b7b7 | |
164 | + } | |
153 | 165 | } |
154 | 166 | |
155 | - @svg new { | |
156 | - width: 12px | |
157 | - height: 12px | |
158 | - content: "<circle cx='6' stroke='none' fill='#ffcf04' cy='6' r='5' />" | |
167 | + -new, -unread { | |
168 | + width: auto | |
169 | + height: auto | |
170 | + | |
171 | + :before { | |
172 | + color: #ffcf04 | |
173 | + content: '✸' | |
174 | + } | |
175 | + | |
176 | + :first-letter { | |
177 | + font-size: 100% | |
178 | + } | |
159 | 179 | } |
160 | 180 | } |
161 | 181 | |
162 | 182 | em { |
Built with git-ssb-web