Commit 52daeae1fcff1fca37ecf65e1ff73d7a80bea2cf
merge git
Dominic Tarr committed on 7/25/2016, 5:07:09 AMParent: 7cd71189c60f0dfdcbeda721fa54776e85d69a0e
Parent: 6bd94bf8ab683004a0730dbb0b860ef5e8cd28a1
Files changed
modules/index.js | changed |
modules/git.js | added |
style.css | changed |
modules/index.js | ||
---|---|---|
@@ -12,8 +12,9 @@ | ||
12 | 12 | "feed.js": require('./feed.js'), |
13 | 13 | "file-input.js": require('./file-input.js'), |
14 | 14 | "follow.js": require('./follow.js'), |
15 | 15 | "relationships.js": require('./relationships.js'), |
16 | + "git.js": require('./git.js'), | |
16 | 17 | "invite.js": require('./invite.js'), |
17 | 18 | "like.js": require('./like.js'), |
18 | 19 | "markdown.js": require('./markdown.js'), |
19 | 20 | "message-confirm.js": require('./message-confirm.js'), |
@@ -33,4 +34,7 @@ | ||
33 | 34 | "tabs.js": require('./tabs.js'), |
34 | 35 | "thread.js": require('./thread.js'), |
35 | 36 | "timestamp.js": require('./timestamp.js') |
36 | 37 | } |
38 | + | |
39 | + | |
40 | + |
modules/git.js | ||
---|---|---|
@@ -1,0 +1,250 @@ | ||
1 | +var h = require('hyperscript') | |
2 | +var pull = require('pull-stream') | |
3 | +var paramap = require('pull-paramap') | |
4 | +var moment = require('moment') | |
5 | + | |
6 | +var plugs = require('../plugs') | |
7 | +var message_link = plugs.first(exports.message_link = []) | |
8 | +var sbot_links = plugs.first(exports.sbot_links = []) | |
9 | +var sbot_links2 = plugs.first(exports.sbot_links2 = []) | |
10 | +var sbot_get = plugs.first(exports.sbot_get = []) | |
11 | +var getAvatar = require('ssb-avatar') | |
12 | +var avatar_name = plugs.first(exports.avatar_name = []) | |
13 | +var markdown = plugs.first(exports.markdown = []) | |
14 | + | |
15 | +var self_id = require('../keys').id | |
16 | + | |
17 | +function shortRefName(ref) { | |
18 | + return ref.replace(/^refs\/(heads|tags)\//, '') | |
19 | +} | |
20 | + | |
21 | +function repoLink(id) { | |
22 | + var el = h('a', {href: '#'+id}, id.substr(0, 10) + '…') | |
23 | + getAvatar({links: sbot_links}, self_id, id, function (err, avatar) { | |
24 | + if(err) return console.error(err) | |
25 | + el.textContent = avatar.name | |
26 | + }) | |
27 | + return el | |
28 | +} | |
29 | + | |
30 | +function getIssueState(id, cb) { | |
31 | + pull( | |
32 | + sbot_links({dest: id, rel: 'issues', values: true}), | |
33 | + 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 | + }) | |
45 | + }), | |
46 | + pull.flatten(), | |
47 | + 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') | |
59 | + }) | |
60 | + ) | |
61 | +} | |
62 | + | |
63 | +function messageTimestampLink(msg) { | |
64 | + var m = moment(msg.value.timestamp) | |
65 | + return h('a.timestamp', { | |
66 | + timestamp: m, | |
67 | + title: m.format('LLLL'), | |
68 | + href: '#'+msg.key | |
69 | + }, m.fromNow()) | |
70 | +} | |
71 | + | |
72 | +function tableRows(headerRow) { | |
73 | + var thead = h('thead'), tbody = h('tbody') | |
74 | + var first = true | |
75 | + var t = [thead, tbody] | |
76 | + t.append = function (row) { | |
77 | + if (first) { | |
78 | + first = false | |
79 | + thead.appendChild(headerRow) | |
80 | + } | |
81 | + tbody.appendChild(row) | |
82 | + } | |
83 | + return t | |
84 | +} | |
85 | + | |
86 | +function repoName(id, link) { | |
87 | + var el = link | |
88 | + ? h('a', {href: '#'+id}, id.substr(0, 8) + '…') | |
89 | + : h('ins', id.substr(0, 8) + '…') | |
90 | + getAvatar({links: sbot_links}, self_id, id, function (err, avatar) { | |
91 | + if(err) return console.error(err) | |
92 | + el.textContent = avatar.name | |
93 | + }) | |
94 | + return el | |
95 | +} | |
96 | + | |
97 | +exports.message_content = function (msg, sbot) { | |
98 | + var c = msg.value.content | |
99 | + | |
100 | + if(c.type === 'git-repo') { | |
101 | + var nameEl | |
102 | + var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT | |
103 | + var div = h('div', | |
104 | + h('p', 'git repo ', repoName(msg.key)), | |
105 | + c.upstream ? h('p', 'fork of ', repoName(c.upstream, true)) : '', | |
106 | + h('p', h('code', 'ssb://' + msg.key)), | |
107 | + h('div.git-table-wrapper', {style: {'max-height': '12em'}}, | |
108 | + h('table', | |
109 | + branchesT = tableRows(h('tr', | |
110 | + h('th', 'branch'), | |
111 | + h('th', 'commit'), | |
112 | + h('th', 'last update'))), | |
113 | + tagsT = tableRows(h('tr', | |
114 | + h('th', 'tag'), | |
115 | + h('th', 'commit'), | |
116 | + h('th', 'last update'))))), | |
117 | + h('div.git-table-wrapper', {style: {'max-height': '16em'}}, | |
118 | + h('table', | |
119 | + openIssuesT = tableRows(h('tr', | |
120 | + h('th', 'open issues'))), | |
121 | + closedIssuesT = tableRows(h('tr', | |
122 | + h('th', 'closed issues'))))), | |
123 | + h('div.git-table-wrapper', {style: {'max-height': '16em'}}, | |
124 | + h('table', | |
125 | + openPRsT = tableRows(h('tr', | |
126 | + h('th', 'open pull requests'))), | |
127 | + closedPRsT = tableRows(h('tr', | |
128 | + h('th', 'closed pull requests')))))) | |
129 | + | |
130 | + // compute refs | |
131 | + var refs = {} | |
132 | + pull( | |
133 | + sbot_links({ | |
134 | + reverse: true, | |
135 | + source: msg.value.author, | |
136 | + dest: msg.key, | |
137 | + rel: 'repo', | |
138 | + values: true | |
139 | + }), | |
140 | + pull.drain(function (link) { | |
141 | + var refUpdates = link.value.content.refs | |
142 | + for (var ref in refUpdates) { | |
143 | + if (refs[ref]) continue | |
144 | + refs[ref] = true | |
145 | + var rev = refUpdates[ref] | |
146 | + if (!rev) continue | |
147 | + var parts = /^refs\/(heads|tags)\/(.*)$/.exec(ref) || [] | |
148 | + var t | |
149 | + if (parts[1] === 'heads') t = branchesT | |
150 | + else if (parts[1] === 'tags') t = tagsT | |
151 | + if (t) t.append(h('tr', | |
152 | + h('td', parts[2]), | |
153 | + h('td', h('code', rev)), | |
154 | + h('td', messageTimestampLink(link)))) | |
155 | + } | |
156 | + }, function (err) { | |
157 | + if (err) console.error(err) | |
158 | + }) | |
159 | + ) | |
160 | + | |
161 | + // list issues and pull requests | |
162 | + pull( | |
163 | + sbot_links({ | |
164 | + reverse: true, | |
165 | + dest: msg.key, | |
166 | + rel: 'project', | |
167 | + values: true | |
168 | + }), | |
169 | + paramap(function (link, cb) { | |
170 | + getIssueState(link.key, function (err, state) { | |
171 | + if(err) return cb(err) | |
172 | + link.state = state | |
173 | + cb(null, link) | |
174 | + }) | |
175 | + }), | |
176 | + pull.drain(function (link) { | |
177 | + var c = link.value.content | |
178 | + // TODO: support renamed issues | |
179 | + var title = c.title || (c.text ? c.text.length > 30 | |
180 | + ? c.text.substr(0, 30) + '…' | |
181 | + : c.text : link.key) | |
182 | + var author = link.value.author | |
183 | + var t = c.type === 'pull-request' | |
184 | + ? link.state === 'open' ? openPRsT : closedPRsT | |
185 | + : link.state === 'open' ? openIssuesT : closedIssuesT | |
186 | + t.append(h('tr', | |
187 | + h('td', | |
188 | + h('a', {href: '#'+link.key}, title), h('br'), | |
189 | + h('small', | |
190 | + 'opened ', messageTimestampLink(link), | |
191 | + ' by ', h('a', {href: '#'+author}, avatar_name(author)))))) | |
192 | + }, function (err) { | |
193 | + if (err) console.error(err) | |
194 | + }) | |
195 | + ) | |
196 | + | |
197 | + return div | |
198 | + } | |
199 | + | |
200 | + if(c.type === 'git-update') { | |
201 | + return h('p', | |
202 | + 'pushed to ', | |
203 | + repoLink(c.repo), | |
204 | + c.refs ? h('ul', Object.keys(c.refs).map(function (ref) { | |
205 | + var rev = c.refs[ref] | |
206 | + return h('li', | |
207 | + shortRefName(ref) + ': ', | |
208 | + rev ? h('code', rev) : h('em', 'deleted')) | |
209 | + })) : null, | |
210 | + Array.isArray(c.issues) ? c.issues.map(function (issue) { | |
211 | + if (issue.merged === true) | |
212 | + return ['Merged ', message_link(issue.link), ' in ', | |
213 | + h('code', issue.object), ' ', h('q', issue.label)] | |
214 | + if (issue.open === false) | |
215 | + return ['Closed ', message_link(issue.link), ' in ', | |
216 | + h('code', issue.object), ' ', h('q', issue.label)] | |
217 | + }) : null | |
218 | + ) | |
219 | + } | |
220 | + | |
221 | + if (c.type === 'issue') { | |
222 | + return h('div', | |
223 | + h('p', 'opened issue on ', repoLink(c.project)), | |
224 | + c.title ? h('h4', c.title) : '', | |
225 | + markdown(c) | |
226 | + ) | |
227 | + } | |
228 | + | |
229 | + if (c.type === 'pull-request') { | |
230 | + return h('div', | |
231 | + h('p', 'opened pull-request ', | |
232 | + 'to ', repoLink(c.repo), ':', c.branch, ' ', | |
233 | + 'from ', repoLink(c.head_repo), ':', c.head_branch), | |
234 | + c.title ? h('h4', c.title) : '', | |
235 | + markdown(c) | |
236 | + ) | |
237 | + } | |
238 | +} | |
239 | + | |
240 | +exports.message_meta = function (msg, sbot) { | |
241 | + var type = msg.value.content.type | |
242 | + if (type == 'issue' || type == 'pull-request') { | |
243 | + var el = h('em', '...') | |
244 | + getIssueState(msg.key, function (err, state) { | |
245 | + if (err) return console.error(err) | |
246 | + el.textContent = state | |
247 | + }) | |
248 | + return el | |
249 | + } | |
250 | +} |
Built with git-ssb-web