modules/git.jsView |
---|
4 | 4 | var human = require('human-time') |
5 | 5 | |
6 | 6 | var plugs = require('../plugs') |
7 | 7 | var message_link = plugs.first(exports.message_link = []) |
| 8 | +var message_confirm = plugs.first(exports.message_confirm = []) |
8 | 9 | var sbot_links = plugs.first(exports.sbot_links = []) |
9 | 10 | var sbot_links2 = plugs.first(exports.sbot_links2 = []) |
10 | 11 | var sbot_get = plugs.first(exports.sbot_get = []) |
11 | 12 | var getAvatar = require('ssb-avatar') |
28 | 29 | } |
29 | 30 | |
30 | 31 | function getIssueState(id, cb) { |
31 | 32 | pull( |
32 | | - sbot_links({dest: id, rel: 'issues', values: true}), |
| 33 | + sbot_links({dest: id, rel: 'issues', values: true, reverse: true}), |
33 | 34 | pull.map(function (msg) { |
34 | | - var issues = msg.value.content.issues |
35 | | - if (!Array.isArray(issues)) return |
36 | | - return issues.filter(function (issue) { |
37 | | - return issue.link === id |
38 | | - }).map(function (issue) { |
39 | | - return { |
40 | | - ts: msg.value.timestamp, |
41 | | - open: issue.open, |
42 | | - merged: issue.merged, |
43 | | - } |
44 | | - }) |
| 35 | + return msg.value.content.issues |
45 | 36 | }), |
46 | 37 | pull.flatten(), |
| 38 | + pull.filter(function (issue) { |
| 39 | + return issue.link === id |
| 40 | + }), |
| 41 | + pull.map(function (issue) { |
| 42 | + return issue.merged ? 'merged' : issue.open ? 'open' : 'closed' |
| 43 | + }), |
| 44 | + pull.take(1), |
47 | 45 | pull.collect(function (err, updates) { |
48 | | - if (err) return cb(err) |
49 | | - var open = true, merged = false |
50 | | - updates.sort(function (a, b) { |
51 | | - return b.ts - a.ts |
52 | | - }).forEach(function (update) { |
53 | | - if (update.open != null) |
54 | | - open = update.open |
55 | | - if (update.merged != null) |
56 | | - merged = update.merged |
57 | | - }) |
58 | | - cb(null, open ? 'open' : merged ? 'merged' : 'closed') |
| 46 | + cb(err, updates && updates[0] || 'open') |
59 | 47 | }) |
60 | 48 | ) |
61 | 49 | } |
62 | 50 | |
63 | 51 | |
64 | 52 | function messageTimestampLink(msg) { |
| 53 | + var date = new Date(msg.value.timestamp) |
65 | 54 | return h('a.timestamp', { |
66 | 55 | timestamp: msg.value.timestamp, |
67 | | - title: new Date(msg.value.timestamp), |
| 56 | + title: date, |
68 | 57 | href: '#'+msg.key |
69 | | - }, human(msg.value.timestamp)) |
| 58 | + }, human(date)) |
70 | 59 | } |
71 | 60 | |
| 61 | + |
72 | 62 | function tableRows(headerRow) { |
73 | 63 | var thead = h('thead'), tbody = h('tbody') |
74 | 64 | var first = true |
75 | 65 | var t = [thead, tbody] |
93 | 83 | }) |
94 | 84 | return el |
95 | 85 | } |
96 | 86 | |
| 87 | +function renderIssueEdit(c) { |
| 88 | + var id = c.issue || c.link |
| 89 | + return [ |
| 90 | + c.title ? h('p', 'renamed issue ', message_link(id), |
| 91 | + ' to ', h('ins', c.title)) : null, |
| 92 | + c.open === false ? h('p', 'closed issue ', message_link(id)) : null, |
| 93 | + c.open === true ? h('p', 'reopened issue ', message_link(id)) : null] |
| 94 | +} |
| 95 | + |
97 | 96 | exports.message_content = function (msg, sbot) { |
98 | 97 | var c = msg.value.content |
99 | 98 | |
100 | 99 | if(c.type === 'git-repo') { |
101 | | - var nameEl |
102 | 100 | var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT |
| 101 | + var forksT |
103 | 102 | var div = h('div', |
104 | 103 | h('p', 'git repo ', repoName(msg.key)), |
105 | 104 | c.upstream ? h('p', 'fork of ', repoName(c.upstream, true)) : '', |
106 | 105 | h('p', h('code', 'ssb://' + msg.key)), |
124 | 123 | h('table', |
125 | 124 | openPRsT = tableRows(h('tr', |
126 | 125 | h('th', 'open pull requests'))), |
127 | 126 | closedPRsT = tableRows(h('tr', |
128 | | - h('th', 'closed pull requests')))))) |
| 127 | + h('th', 'closed pull requests'))))), |
| 128 | + h('div.git-table-wrapper', |
| 129 | + h('table', |
| 130 | + forksT = tableRows(h('tr', |
| 131 | + h('th', 'forks')))))) |
129 | 132 | |
130 | 133 | |
131 | 134 | var refs = {} |
132 | 135 | pull( |
137 | 140 | rel: 'repo', |
138 | 141 | values: true |
139 | 142 | }), |
140 | 143 | pull.drain(function (link) { |
141 | | - var refUpdates = link.value.content.refs |
142 | | - for (var ref in refUpdates) { |
143 | | - if (refs[ref]) continue |
| 144 | + var refUpdates = link.value.content.refs || {} |
| 145 | + Object.keys(refUpdates).reverse().filter(function (ref) { |
| 146 | + if (refs[ref]) return |
144 | 147 | refs[ref] = true |
145 | 148 | var rev = refUpdates[ref] |
146 | | - if (!rev) continue |
| 149 | + if (!rev) return |
147 | 150 | var parts = /^refs\/(heads|tags)\/(.*)$/.exec(ref) || [] |
148 | 151 | var t |
149 | 152 | if (parts[1] === 'heads') t = branchesT |
150 | 153 | else if (parts[1] === 'tags') t = tagsT |
151 | 154 | if (t) t.append(h('tr', |
152 | 155 | h('td', parts[2]), |
153 | 156 | h('td', h('code', rev)), |
154 | 157 | h('td', messageTimestampLink(link)))) |
155 | | - } |
| 158 | + }) |
156 | 159 | }, function (err) { |
157 | 160 | if (err) console.error(err) |
158 | 161 | }) |
159 | 162 | ) |
174 | 177 | }) |
175 | 178 | }), |
176 | 179 | pull.drain(function (link) { |
177 | 180 | var c = link.value.content |
178 | | - |
179 | | - var title = c.title || (c.text ? c.text.length > 30 |
180 | | - ? c.text.substr(0, 30) + '…' |
| 181 | + var title = c.title || (c.text ? c.text.length > 70 |
| 182 | + ? c.text.substr(0, 70) + '…' |
181 | 183 | : c.text : link.key) |
182 | 184 | var author = link.value.author |
183 | 185 | var t = c.type === 'pull-request' |
184 | 186 | ? link.state === 'open' ? openPRsT : closedPRsT |
193 | 195 | if (err) console.error(err) |
194 | 196 | }) |
195 | 197 | ) |
196 | 198 | |
| 199 | + |
| 200 | + pull( |
| 201 | + sbot_links({ |
| 202 | + reverse: true, |
| 203 | + dest: msg.key, |
| 204 | + rel: 'upstream' |
| 205 | + }), |
| 206 | + pull.drain(function (link) { |
| 207 | + forksT.append(h('tr', h('td', |
| 208 | + repoName(link.key, true), |
| 209 | + ' by ', h('a', {href: '#'+link.source}, avatar_name(link.source))))) |
| 210 | + }, function (err) { |
| 211 | + if (err) console.error(err) |
| 212 | + }) |
| 213 | + ) |
| 214 | + |
197 | 215 | return div |
198 | 216 | } |
199 | 217 | |
200 | 218 | if(c.type === 'git-update') { |
217 | 235 | }) : null |
218 | 236 | ) |
219 | 237 | } |
220 | 238 | |
221 | | - if (c.type === 'issue') { |
| 239 | + if(c.type === 'issue-edit') { |
222 | 240 | return h('div', |
| 241 | + c.issue ? renderIssueEdit(c) : null, |
| 242 | + c.issues ? c.issues.map(renderIssueEdit) : null) |
| 243 | + } |
| 244 | + |
| 245 | + if(c.type === 'issue') { |
| 246 | + return h('div', |
223 | 247 | h('p', 'opened issue on ', repoLink(c.project)), |
224 | 248 | c.title ? h('h4', c.title) : '', |
225 | 249 | markdown(c) |
226 | 250 | ) |
227 | 251 | } |
228 | 252 | |
229 | | - if (c.type === 'pull-request') { |
| 253 | + if(c.type === 'pull-request') { |
230 | 254 | return h('div', |
231 | 255 | h('p', 'opened pull-request ', |
232 | 256 | 'to ', repoLink(c.repo), ':', c.branch, ' ', |
233 | 257 | 'from ', repoLink(c.head_repo), ':', c.head_branch), |
238 | 262 | } |
239 | 263 | |
240 | 264 | exports.message_meta = function (msg, sbot) { |
241 | 265 | var type = msg.value.content.type |
242 | | - if (type == 'issue' || type == 'pull-request') { |
| 266 | + if (type === 'issue' || type === 'pull-request') { |
243 | 267 | var el = h('em', '...') |
| 268 | + |
244 | 269 | getIssueState(msg.key, function (err, state) { |
245 | 270 | if (err) return console.error(err) |
246 | 271 | el.textContent = state |
247 | 272 | }) |
248 | 273 | return el |
249 | 274 | } |
250 | 275 | } |
251 | 276 | |
| 277 | +exports.message_action = function (msg, sbot) { |
| 278 | + var c = msg.value.content |
| 279 | + if(c.type === 'issue' || c.type === 'pull-request') { |
| 280 | + var isOpen |
| 281 | + var a = h('a', {href: '#', onclick: function () { |
| 282 | + message_confirm({ |
| 283 | + type: 'issue-edit', |
| 284 | + root: msg.key, |
| 285 | + issues: [{ |
| 286 | + link: msg.key, |
| 287 | + open: !isOpen |
| 288 | + }] |
| 289 | + }, function (err, msg) { |
| 290 | + if(err) return alert(err) |
| 291 | + if(!msg) return |
| 292 | + isOpen = msg.value.content.open |
| 293 | + update() |
| 294 | + }) |
| 295 | + }}) |
| 296 | + getIssueState(msg.key, function (err, state) { |
| 297 | + if (err) return console.error(err) |
| 298 | + isOpen = state === 'open' |
| 299 | + update() |
| 300 | + }) |
| 301 | + function update() { |
| 302 | + a.textContent = c.type === 'pull-request' |
| 303 | + ? isOpen ? 'Close Pull Request' : 'Reopen Pull Request' |
| 304 | + : isOpen ? 'Close Issue' : 'Reopen Issue' |
| 305 | + } |
| 306 | + return a |
| 307 | + } |
| 308 | +} |
252 | 309 | |