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