Commit 5e06ff6caacaf2d29ccced2d9c39800bbf1923de
highlight new messages, don't show replies from strangers to old posts, bump refactor
#470Matt McKegg committed on 4/8/2017, 5:39:32 AM
Parent: fe2173f46498c968ad5bd79d249d023ca4998f88
Files changed
modules/feed/html/rollup.js | changed |
modules/feed/pull/summary.js | changed |
modules/page/html/render/public.js | changed |
plugs/message/html/layout/default.js | changed |
styles/message.mcss | changed |
modules/feed/html/rollup.js | ||
---|---|---|
@@ -45,8 +45,12 @@ | ||
45 | 45 … | var bumpFilter = opts && opts.bumpFilter |
46 | 46 … | var windowSize = opts && opts.windowSize |
47 | 47 … | var waitFor = opts && opts.waitFor || true |
48 | 48 … | |
49 … | + var newSinceRefresh = new Set() | |
50 … | + var newInSession = new Set() | |
51 … | + var prioritized = {} | |
52 … | + | |
49 | 53 … | var updateLoader = h('a Notifier -loader', { |
50 | 54 … | href: '#', |
51 | 55 … | 'ev-click': refresh |
52 | 56 … | }, [ |
@@ -73,8 +77,12 @@ | ||
73 | 77 … | getStream({old: false}), |
74 | 78 … | pull.drain((item) => { |
75 | 79 … | var type = item && item.value && item.value.content.type |
76 | 80 … | |
81 … | + // prioritize new messages on next refresh | |
82 … | + newInSession.add(item.key) | |
83 … | + newSinceRefresh.add(item.key) | |
84 … | + | |
77 | 85 … | // ignore message handled by another app |
78 | 86 … | if (api.app.sync.externalHandler(item)) return |
79 | 87 … | |
80 | 88 … | if (type && type !== 'vote' && typeof item.value.content === 'object' && item.value.timestamp > twoDaysAgo()) { |
@@ -139,10 +147,15 @@ | ||
139 | 147 … | |
140 | 148 … | var abortable = Abortable() |
141 | 149 … | abortLastFeed = abortable.abort |
142 | 150 … | |
151 … | + prioritized = {} | |
152 … | + newSinceRefresh.forEach(x => { | |
153 … | + prioritized[x] = 2 | |
154 … | + }) | |
155 … | + | |
143 | 156 … | pull( |
144 | - api.feed.pull.summary(getStream, {windowSize, bumpFilter}, () => { | |
157 … | + api.feed.pull.summary(getStream, {windowSize, bumpFilter, prioritized}, () => { | |
145 | 158 … | sync.set(true) |
146 | 159 … | }), |
147 | 160 … | pull.asyncMap(ensureMessageAndAuthor), |
148 | 161 … | pull.filter((item) => { |
@@ -156,16 +169,24 @@ | ||
156 | 169 … | }), |
157 | 170 … | abortable, |
158 | 171 … | Scroller(container, content(), renderItem, false, false) |
159 | 172 … | ) |
173 … | + | |
174 … | + // clear high prioritized items | |
175 … | + newSinceRefresh.clear() | |
160 | 176 … | } |
161 | 177 … | |
162 | 178 … | function renderItem (item) { |
163 | 179 … | if (item.type === 'message') { |
164 | 180 … | var meta = null |
165 | 181 … | var previousId = item.messageId |
166 | 182 … | var replies = item.replies.slice(-4).map((msg) => { |
167 | - var result = api.message.html.render(msg, {inContext: true, inSummary: true, previousId}) | |
183 … | + var result = api.message.html.render(msg, { | |
184 … | + inContext: true, | |
185 … | + inSummary: true, | |
186 … | + previousId, | |
187 … | + priority: prioritized[msg.key] | |
188 … | + }) | |
168 | 189 … | previousId = msg.key |
169 | 190 … | return result |
170 | 191 … | }) |
171 | 192 … | var renderedMessage = item.message ? api.message.html.render(item.message, {inContext: true}) : null |
@@ -194,9 +215,11 @@ | ||
194 | 215 … | h('div.replies', replies) |
195 | 216 … | ]) |
196 | 217 … | ]) |
197 | 218 … | } else { |
198 | - if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
219 … | + // when there is no root message in this window, | |
220 … | + // try and show reply message, only show like message if we have nothing else to give | |
221 … | + if (item.repliesFrom.size) { | |
199 | 222 … | meta = h('div.meta', { |
200 | 223 … | title: names(item.repliesFrom) |
201 | 224 … | }, [ |
202 | 225 … | many(item.repliesFrom, api.profile.html.person), ' replied to ', api.message.html.link(item.messageId) |
@@ -208,9 +231,10 @@ | ||
208 | 231 … | many(item.likes, api.profile.html.person), ' liked ', api.message.html.link(item.messageId) |
209 | 232 … | ]) |
210 | 233 … | } |
211 | 234 … | |
212 | - if (meta || replies.length) { | |
235 … | + // only show this event if it has a meta description | |
236 … | + if (meta) { | |
213 | 237 … | return h('FeedEvent', [ |
214 | 238 … | meta, h('div.replies', replies) |
215 | 239 … | ]) |
216 | 240 … | } |
modules/feed/pull/summary.js | ||
---|---|---|
@@ -17,8 +17,9 @@ | ||
17 | 17 … | |
18 | 18 … | function summary (source, opts, cb) { |
19 | 19 … | var bumpFilter = opts && opts.bumpFilter |
20 | 20 … | var windowSize = opts && opts.windowSize || 1000 |
21 … | + var prioritized = opts && opts.prioritized || {} | |
21 | 22 … | var last = null |
22 | 23 … | var returned = false |
23 | 24 … | var done = false |
24 | 25 … | return pullNext(() => { |
@@ -39,9 +40,9 @@ | ||
39 | 40 … | returned = true |
40 | 41 … | } else { |
41 | 42 … | var fromTime = last && last.timestamp || Date.now() |
42 | 43 … | last = values[values.length - 1] |
43 | - groupMessages(values, fromTime, bumpFilter, (err, result) => { | |
44 … | + groupMessages(values, fromTime, {bumpFilter, prioritized}, (err, result) => { | |
44 | 45 … | if (err) throw err |
45 | 46 … | deferred.resolve( |
46 | 47 … | pull.values(result) |
47 | 48 … | ) |
@@ -55,46 +56,42 @@ | ||
55 | 56 … | return deferred |
56 | 57 … | }) |
57 | 58 … | } |
58 | 59 … | |
59 | -function groupMessages (messages, fromTime, bumpFilter, cb) { | |
60 … | +function groupMessages (messages, fromTime, opts, cb) { | |
60 | 61 … | var subscribes = {} |
61 | 62 … | var follows = {} |
62 | 63 … | var messageUpdates = {} |
63 | 64 … | reverseForEach(messages, function (msg) { |
64 | 65 … | if (!msg.value) return |
65 | 66 … | var c = msg.value.content |
66 | 67 … | if (c.type === 'contact') { |
67 | - updateContact(msg, follows) | |
68 … | + updateContact(msg, follows, opts) | |
68 | 69 … | } else if (c.type === 'channel') { |
69 | - updateChannel(msg, subscribes) | |
70 … | + updateChannel(msg, subscribes, opts) | |
70 | 71 … | } else if (c.type === 'vote') { |
71 | 72 … | if (c.vote && c.vote.link) { |
72 | 73 … | // only show likes of posts added in the current window |
73 | 74 … | // and only for the main post |
74 | 75 … | const group = messageUpdates[c.vote.link] |
75 | 76 … | if (group) { |
76 | 77 … | if (c.vote.value > 0) { |
77 | - group.lastUpdateType = 'like' | |
78 | 78 … | group.likes.add(msg.value.author) |
79 | - bumpIfNeeded(group, msg, bumpFilter) | |
79 … | + group.relatedMessages.push(msg) | |
80 | 80 … | } else { |
81 | 81 … | group.likes.delete(msg.value.author) |
82 | - if (group.lastUpdateType === 'like' && !group.likes.size && !group.replies.length) { | |
83 | - group.lastUpdateType = 'reply' | |
84 | - } | |
82 … | + group.relatedMessages.push(msg) | |
85 | 83 … | } |
86 | 84 … | } |
87 | 85 … | } |
88 | 86 … | } else { |
89 | 87 … | if (c.root) { |
90 | 88 … | const group = ensureMessage(c.root, messageUpdates) |
91 | 89 … | group.fromTime = fromTime |
92 | - group.lastUpdateType = 'reply' | |
93 | 90 … | group.repliesFrom.add(msg.value.author) |
94 | 91 … | SortedArray.add(group.replies, msg, compareUserTimestamp) |
95 | 92 … | group.channel = group.channel || msg.value.content.channel |
96 | - bumpIfNeeded(group, msg, bumpFilter) | |
93 … | + group.relatedMessages.push(msg) | |
97 | 94 … | } else { |
98 | 95 … | const group = ensureMessage(msg.key, messageUpdates) |
99 | 96 … | group.fromTime = fromTime |
100 | 97 … | group.lastUpdateType = 'post' |
@@ -107,36 +104,66 @@ | ||
107 | 104 … | } |
108 | 105 … | }, () => { |
109 | 106 … | var result = [] |
110 | 107 … | Object.keys(follows).forEach((key) => { |
108 … | + bumpIfNeeded(follows[key], opts) | |
111 | 109 … | if (follows[key].updated) { |
112 | 110 … | SortedArray.add(result, follows[key], compareUpdated) |
113 | 111 … | } |
114 | 112 … | }) |
115 | 113 … | Object.keys(subscribes).forEach((key) => { |
114 … | + bumpIfNeeded(subscribes[key], opts) | |
116 | 115 … | if (subscribes[key].updated) { |
117 | 116 … | SortedArray.add(result, subscribes[key], compareUpdated) |
118 | 117 … | } |
119 | 118 … | }) |
120 | 119 … | Object.keys(messageUpdates).forEach((key) => { |
120 … | + bumpIfNeeded(messageUpdates[key], opts) | |
121 | 121 … | if (messageUpdates[key].updated) { |
122 | 122 … | SortedArray.add(result, messageUpdates[key], compareUpdated) |
123 | 123 … | } |
124 | 124 … | }) |
125 | 125 … | cb(null, result) |
126 | 126 … | }) |
127 | 127 … | } |
128 | 128 … | |
129 | -function bumpIfNeeded (group, msg, bumpFilter) { | |
130 | - // only bump when filter passes | |
131 | - var newUpdated = msg.timestamp || msg.value.sequence | |
132 | - if (!group.updated || ((!bumpFilter || bumpFilter(msg, group)) && newUpdated > group.updated)) { | |
133 | - group.updated = newUpdated | |
134 | - } | |
129 … | +function bumpIfNeeded (group, {bumpFilter, prioritized}) { | |
130 … | + group.relatedMessages.forEach(msg => { | |
131 … | + if (prioritized[msg.key] && group.priority < prioritized[msg.key]) { | |
132 … | + group.priority = prioritized[msg.key] | |
133 … | + } | |
134 … | + | |
135 … | + var shouldBump = !bumpFilter || bumpFilter(msg, group) | |
136 … | + | |
137 … | + // only bump when filter passes | |
138 … | + var newUpdated = msg.timestamp || msg.value.sequence | |
139 … | + if (!group.updated || (shouldBump && newUpdated > group.updated)) { | |
140 … | + group.updated = newUpdated | |
141 … | + if (msg.value.content.type === 'vote') { | |
142 … | + if (group.likes.size) { | |
143 … | + group.lastUpdateType = 'like' | |
144 … | + } else if (group.repliesFrom.size) { | |
145 … | + group.lastUpdateType = 'reply' | |
146 … | + } else if (group.message) { | |
147 … | + group.lastUpdateType = 'post' | |
148 … | + } | |
149 … | + } | |
150 … | + | |
151 … | + if (msg.value.content.type === 'post') { | |
152 … | + if (msg.value.content.root) { | |
153 … | + group.lastUpdateType = 'reply' | |
154 … | + } else { | |
155 … | + group.lastUpdateType = 'post' | |
156 … | + } | |
157 … | + } | |
158 … | + } | |
159 … | + }) | |
135 | 160 … | } |
136 | 161 … | |
137 | 162 … | function compareUpdated (a, b) { |
138 | - return b.updated - a.updated | |
163 … | + // highest priority first | |
164 … | + // then most recent date | |
165 … | + return b.priority - a.priority || b.updated - a.updated | |
139 | 166 … | } |
140 | 167 … | |
141 | 168 … | function reverseForEach (items, fn, cb) { |
142 | 169 … | var i = items.length - 1 |
@@ -157,26 +184,28 @@ | ||
157 | 184 … | } |
158 | 185 … | } |
159 | 186 … | } |
160 | 187 … | |
161 | -function updateContact (msg, groups) { | |
188 … | +function updateContact (msg, groups, opts) { | |
162 | 189 … | var c = msg.value.content |
163 | 190 … | var id = msg.value.author |
164 | 191 … | var group = groups[id] |
165 | 192 … | if (ref.isFeed(c.contact)) { |
166 | 193 … | if (c.following) { |
167 | 194 … | if (!group) { |
168 | 195 … | group = groups[id] = { |
169 | 196 … | type: 'follow', |
197 … | + priority: 0, | |
198 … | + relatedMessages: [], | |
170 | 199 … | lastUpdateType: null, |
171 | 200 … | contacts: new Set(), |
172 | 201 … | updated: 0, |
173 | 202 … | author: id, |
174 | 203 … | id: id |
175 | 204 … | } |
176 | 205 … | } |
177 | 206 … | group.contacts.add(c.contact) |
178 | - group.updated = msg.timestamp || msg.value.sequence | |
207 … | + group.relatedMessages.push(msg) | |
179 | 208 … | } else { |
180 | 209 … | if (group) { |
181 | 210 … | group.contacts.delete(c.contact) |
182 | 211 … | if (!group.contacts.size) { |
@@ -186,25 +215,27 @@ | ||
186 | 215 … | } |
187 | 216 … | } |
188 | 217 … | } |
189 | 218 … | |
190 | -function updateChannel (msg, groups) { | |
219 … | +function updateChannel (msg, groups, opts) { | |
191 | 220 … | var c = msg.value.content |
192 | 221 … | var channel = c.channel |
193 | 222 … | var group = groups[channel] |
194 | 223 … | if (typeof channel === 'string') { |
195 | 224 … | if (c.subscribed) { |
196 | 225 … | if (!group) { |
197 | 226 … | group = groups[channel] = { |
198 | 227 … | type: 'subscribe', |
228 … | + priority: 0, | |
229 … | + relatedMessages: [], | |
199 | 230 … | lastUpdateType: null, |
200 | 231 … | subscribers: new Set(), |
201 | 232 … | updated: 0, |
202 | 233 … | channel |
203 | 234 … | } |
204 | 235 … | } |
205 | 236 … | group.subscribers.add(msg.value.author) |
206 | - group.updated = msg.timestamp || msg.value.sequence | |
237 … | + group.relatedMessages.push(msg) | |
207 | 238 … | } else { |
208 | 239 … | if (group) { |
209 | 240 … | group.subscribers.delete(msg.value.author) |
210 | 241 … | if (!group.subscribers.size) { |
@@ -219,9 +250,11 @@ | ||
219 | 250 … | var group = groups[id] |
220 | 251 … | if (!group) { |
221 | 252 … | group = groups[id] = { |
222 | 253 … | type: 'message', |
254 … | + priority: 0, | |
223 | 255 … | repliesFrom: new Set(), |
256 … | + relatedMessages: [], | |
224 | 257 … | replies: [], |
225 | 258 … | message: null, |
226 | 259 … | messageId: id, |
227 | 260 … | likes: new Set(), |
modules/page/html/render/public.js | ||
---|---|---|
@@ -73,18 +73,9 @@ | ||
73 | 73 … | subscribedChannels.sync |
74 | 74 … | ], (...x) => x.every(Boolean)), |
75 | 75 … | windowSize: 500, |
76 | 76 … | filter: (item) => { |
77 | - if (item.type === 'subscribe') { | |
78 | - // HACK: hide people you don't follow from subscribe notifications: | |
79 | - Array.from(item.subscribers).forEach(id => { | |
80 | - if (!following().has(id)) { | |
81 | - item.subscribers.delete(id) | |
82 | - } | |
83 | - }) | |
84 | - } | |
85 | - | |
86 | - return !item.boxed && ( | |
77 … | + return !item.boxed && (item.lastUpdateType !== 'post' || item.message) && ( | |
87 | 78 … | id === item.author || |
88 | 79 … | (item.author && following().has(item.author)) || |
89 | 80 … | (item.type === 'message' && subscribedChannels().has(item.channel)) || |
90 | 81 … | (item.type === 'subscribe' && item.subscribers.size) || |
@@ -92,8 +83,24 @@ | ||
92 | 83 … | item.likes && item.likes.has(id) |
93 | 84 … | ) |
94 | 85 … | }, |
95 | 86 … | bumpFilter: (msg, group) => { |
87 … | + if (group.type === 'subscribe') { | |
88 … | + removeStrangers(group.subscribers) | |
89 … | + } | |
90 … | + | |
91 … | + if (group.type === 'message') { | |
92 … | + removeStrangers(group.likes) | |
93 … | + removeStrangers(group.repliesFrom) | |
94 … | + | |
95 … | + if (!group.message) { | |
96 … | + // if message is old, only show replies from friends | |
97 … | + group.replies = group.replies.filter(x => { | |
98 … | + return (x.value.author === id || following().has(x.value.author)) | |
99 … | + }) | |
100 … | + } | |
101 … | + } | |
102 … | + | |
96 | 103 … | if (!group.message) { |
97 | 104 … | return ( |
98 | 105 … | isMentioned(id, msg.value.content.mentions) || |
99 | 106 … | msg.value.author === id || ( |
@@ -119,8 +126,18 @@ | ||
119 | 126 … | result.reload = feedView.reload |
120 | 127 … | |
121 | 128 … | return result |
122 | 129 … | |
130 … | + function removeStrangers (set) { | |
131 … | + if (set) { | |
132 … | + Array.from(set).forEach(key => { | |
133 … | + if (!following().has(key) && key !== id) { | |
134 … | + set.delete(key) | |
135 … | + } | |
136 … | + }) | |
137 … | + } | |
138 … | + } | |
139 … | + | |
123 | 140 … | function getSidebar () { |
124 | 141 … | var whoToFollow = computed([following, api.profile.obs.recentlyUpdated(), localPeers], (following, recent, peers) => { |
125 | 142 … | return Array.from(recent).filter(x => x !== id && !following.has(x) && !peers.includes(x)).slice(0, 10) |
126 | 143 … | }) |
plugs/message/html/layout/default.js | |||
---|---|---|---|
@@ -38,12 +38,16 @@ | |||
38 | 38 … | } else if (msg.value.content.project) { | |
39 | 39 … | replyInfo = h('span', ['on ', api.message.html.link(msg.value.content.project)]) | |
40 | 40 … | } | |
41 | 41 … | ||
42 … | + if (opts.priority === 2) { | ||
43 … | + classList.push('-new') | ||
44 … | + } | ||
45 … | + | ||
42 | 46 … | return h('div', { | |
43 | 47 … | classList | |
44 | 48 … | }, [ | |
45 | - messageHeader(msg, replyInfo), | ||
49 … | + messageHeader(msg, replyInfo, opts.priority), | ||
46 | 50 … | h('section', [opts.content]), | |
47 | 51 … | computed(msg.key, (key) => { | |
48 | 52 … | if (ref.isMsg(key)) { | |
49 | 53 … | return h('footer', [ | |
@@ -64,9 +68,13 @@ | |||
64 | 68 … | ]) | |
65 | 69 … | ||
66 | 70 … | // scoped | |
67 | 71 … | ||
68 | - function messageHeader (msg, replyInfo) { | ||
72 … | + function messageHeader (msg, replyInfo, priority) { | ||
73 … | + var additionalMeta = [] | ||
74 … | + if (opts.priority >= 2) { | ||
75 … | + additionalMeta.push(h('span.flag -new', {title: 'New Message'})) | ||
76 … | + } | ||
69 | 77 … | return h('header', [ | |
70 | 78 … | h('div.main', [ | |
71 | 79 … | h('a.avatar', {href: `${msg.value.author}`}, [ | |
72 | 80 … | api.about.html.image(msg.value.author) | |
@@ -79,9 +87,12 @@ | |||
79 | 87 … | api.message.html.timestamp(msg), ' ', replyInfo | |
80 | 88 … | ]) | |
81 | 89 … | ]) | |
82 | 90 … | ]), | |
83 | - h('div.meta', api.message.html.meta(msg)) | ||
91 … | + h('div.meta', [ | ||
92 … | + api.message.html.meta(msg), | ||
93 … | + additionalMeta | ||
94 … | + ]) | ||
84 | 95 … | ]) | |
85 | 96 … | } | |
86 | 97 … | } | |
87 | 98 … | } |
styles/message.mcss | ||
---|---|---|
@@ -51,8 +51,12 @@ | ||
51 | 51 … | } |
52 | 52 … | } |
53 | 53 … | } |
54 | 54 … | |
55 … | + -new { | |
56 … | + border-color: #ffe794 | |
57 … | + } | |
58 … | + | |
55 | 59 … | header { |
56 | 60 … | margin: 15px 15px |
57 | 61 … | margin-bottom: 5px; |
58 | 62 … | display: flex |
@@ -83,16 +87,33 @@ | ||
83 | 87 … | font-size: 90% |
84 | 88 … | } |
85 | 89 … | margin-left: 10px |
86 | 90 … | } |
91 … | + } | |
87 | 92 … | |
88 | - div.meta { | |
93 … | + div.meta { | |
89 | 94 … | |
95 … | + span.flag { | |
96 … | + width: 12px | |
97 … | + height: 12px | |
98 … | + | |
99 … | + background-repeat: no-repeat | |
100 … | + background-position: center | |
101 … | + display: inline-block | |
102 … | + vertical-align: middle; | |
103 … | + margin-top: -3px; | |
104 … | + | |
105 … | + -new { | |
106 … | + background-image: svg(new) | |
107 … | + } | |
108 … | + | |
109 … | + @svg new { | |
110 … | + width: 12px | |
111 … | + height: 12px | |
112 … | + content: "<circle cx='6' stroke='none' fill='#62baff' cy='6' r='5' />" | |
113 … | + } | |
90 | 114 … | } |
91 | - } | |
92 | 115 … | |
93 | - div.meta { | |
94 | - | |
95 | 116 … | em { |
96 | 117 … | display: inline-block |
97 | 118 … | padding: 4px |
98 | 119 … | } |
Built with git-ssb-web