Files: 07b3119b1103ad1b3e666beed774e28f425a53c8 / lib / repos / issues.js
10975 bytesRaw
1 | var pull = require('pull-stream') |
2 | var h = require('pull-hyperscript') |
3 | var paramap = require('pull-paramap') |
4 | var cat = require('pull-cat') |
5 | var multicb = require('multicb') |
6 | var u = require('../util') |
7 | var markdown = require('../markdown') |
8 | var forms = require('../forms') |
9 | |
10 | |
11 | module.exports = function (repoRoutes, web) { |
12 | return new RepoIssueRoutes(repoRoutes, web) |
13 | } |
14 | |
15 | function RepoIssueRoutes(repoRoutes, web) { |
16 | this.repo = repoRoutes |
17 | this.web = web |
18 | } |
19 | |
20 | var I = RepoIssueRoutes.prototype |
21 | |
22 | function getMention(msg, id) { |
23 | if (msg.key == id) return msg |
24 | var mentions = msg.value.content.mentions |
25 | if (mentions) for (var i = 0; i < mentions.length; i++) { |
26 | var mention = mentions[i] |
27 | if (mention.link == id) |
28 | return mention |
29 | } |
30 | return null |
31 | } |
32 | |
33 | /* Issues */ |
34 | |
35 | I.serveRepoIssues = function (req, repo, isPRs) { |
36 | var self = this |
37 | var count = 0 |
38 | var state = req._u.query.state || 'open' |
39 | var newPath = isPRs ? [repo.id, 'compare'] : [repo.id, 'issues', 'new'] |
40 | var title = req._t('Issues') + ' · %{author}/%{repo}' |
41 | var page = isPRs ? 'pulls' : 'issues' |
42 | var currentFilter = req._u.query.filter || 'All' |
43 | var selectedFilter = (filter, label, name) => { |
44 | return (label === filter) |
45 | ? `<option value="${label}" selected>${name}</>` |
46 | : `<option value="${label}">${name}</option>` |
47 | } |
48 | function repoLabelsStream () { |
49 | return self.web.ssb.backlinks.read({ |
50 | query: [ |
51 | {$filter: { |
52 | dest: repo.id, |
53 | value: { |
54 | content: { |
55 | type: "issue-label", |
56 | project: repo.id |
57 | } |
58 | } |
59 | }}, |
60 | {$map: { |
61 | id: "key", |
62 | name: ["value", "content", "name"] |
63 | }} |
64 | ] |
65 | }) |
66 | } |
67 | |
68 | return self.repo.serveRepoTemplate(req, repo, page, null, title, cat([ |
69 | pull.once( |
70 | (self.web.isPublic ? '' : |
71 | '<form class="right-bar" method="get"' + |
72 | ' action="' + u.encodeLink(newPath) + '">' + |
73 | '<button class="btn btn-primary">+ ' + |
74 | req._t(isPRs ? 'pullRequest.New' : 'issue.New') + |
75 | '</button>' + |
76 | '</form>') + |
77 | '<h3>' + req._t(isPRs ? 'PullRequests' : 'Issues') + '</h3>' |
78 | ), |
79 | u.nav([ |
80 | ['?', req._t('issues.Open'), 'open'], |
81 | ['?state=closed', req._t('issues.Closed'), 'closed'], |
82 | ['?state=all', req._t('issues.All'), 'all'] |
83 | ], state), |
84 | h('form', { |
85 | action: req.url, |
86 | method:'get' |
87 | }, [ |
88 | h('label', { for: 'filter' }, 'Filter'), |
89 | h('select', { |
90 | class: 'custom-dropdown', |
91 | onChange: 'this.form.submit()', |
92 | name: 'filter' |
93 | }, [ |
94 | selectedFilter(currentFilter, 'All', 'All'), |
95 | pull( |
96 | repoLabelsStream(), |
97 | paramap(function (data, cb) { |
98 | self.web.about.getName(data.id, function (err, name) { |
99 | if (err) { |
100 | return cb(err) |
101 | } |
102 | cb(null, selectedFilter(currentFilter, data.id, name)) |
103 | }) |
104 | }) |
105 | )] |
106 | ) |
107 | ]), |
108 | pull( |
109 | (isPRs ? self.web.pullReqs : self.web.issues).list({ |
110 | repo: repo.id, |
111 | project: repo.id, |
112 | reverse: true, |
113 | open: {open: true, closed: false}[state] |
114 | }), |
115 | pull.filter(function (issue) { |
116 | if (currentFilter === 'All') return true |
117 | return issue.labels.indexOf(currentFilter) > -1 |
118 | }), |
119 | paramap(function (issue, cb) { |
120 | count++ |
121 | var done = multicb({pluck: 1}) |
122 | var state = (issue.open ? 'open' : 'closed') |
123 | var stateStr = req._t(issue.open ? |
124 | 'issue.state.Open' : 'issue.state.Closed') |
125 | issue.labels.forEach( |
126 | labelId => self.web.about.getName(labelId, done()) |
127 | ) |
128 | done(function (err, results) { |
129 | if (err) { |
130 | cb(err) |
131 | } |
132 | cb( |
133 | null, |
134 | '<section class="collapse">' + |
135 | '<i class="issue-state issue-state-' + state + '"' + |
136 | ' title="' + stateStr + '">◼</i> ' + |
137 | '<a href="' + u.encodeLink(issue.id) + '">' + |
138 | u.formatMarkdownTitle(issue.title) + |
139 | results.map(label => `<span class="issue-label">${label}</span>`).join(' ') + |
140 | '<span class="right-bar">' + |
141 | new Date(issue.created_at).toLocaleString(req._locale) + |
142 | '</span>' + |
143 | '</a>' + |
144 | '</section>' |
145 | ) |
146 | }) |
147 | }) |
148 | ), |
149 | u.readOnce(function (cb) { |
150 | cb(null, count > 0 ? '' : |
151 | '<p>' + req._t(isPRs ? 'NoPullRequests' : 'NoIssues') + '</p>') |
152 | }) |
153 | ])) |
154 | } |
155 | |
156 | /* New Issue */ |
157 | |
158 | I.serveRepoNewIssue = function (req, repo, issueId, path) { |
159 | var title = req._t('issue.New') + ' · %{author}/%{repo}' |
160 | return this.repo.serveRepoTemplate(req, repo, 'issues', null, title, pull.once( |
161 | '<h3>' + req._t('issue.New') + '</h3>' + |
162 | '<section><form action="" method="post">' + |
163 | '<input type="hidden" name="action" value="new-issue">' + |
164 | forms.post(req, repo, null, 8) + |
165 | '<button type="submit" class="btn">' + req._t('Create') + '</button>' + |
166 | '</form></section>')) |
167 | } |
168 | |
169 | /* Issue */ |
170 | |
171 | I.serveRepoIssue = function (req, repo, issue, path, postId) { |
172 | var self = this |
173 | issue.labels.forEach(function (label) { |
174 | self.web.about.getName(label, function (err, name) { |
175 | console.log(name) |
176 | }) |
177 | }) |
178 | var newestMsg = {key: issue.id, value: {timestamp: issue.created_at}} |
179 | var title = u.formatMarkdownTitle(issue.title) + ' · %{author}/%{repo}' |
180 | return self.repo.serveRepoTemplate(req, repo, 'issues', null, title, cat([ |
181 | pull.once( |
182 | '<h3>' + u.link([issue.id], u.formatMarkdownTitle(issue.title), true) + '</h3>' + |
183 | '<section class="right-bar"><form action="" method="post">' + |
184 | '<input type="hidden" name="action" value="labels-add"/>' + |
185 | '<input type="hidden" name="issue" value="' + issue.id + '"/>' + |
186 | forms.labels(req, repo, null, 8) + |
187 | '<button type="submit" class="btn ">' + req._t('+ Issue Label') + '</button>' + |
188 | '</form></section>' + |
189 | '<code>' + issue.id + '</code>' + |
190 | '<section class="collapse">' + |
191 | (issue.open |
192 | ? '<strong class="issue-status open">' + |
193 | req._t('issue.state.Open') + '</strong>' |
194 | : '<strong class="issue-status closed">' + |
195 | req._t('issue.state.Closed') + '</strong>')), |
196 | u.readOnce(function (cb) { |
197 | self.web.about.getName(issue.author, function (err, authorName) { |
198 | if (err) return cb(err) |
199 | var authorLink = u.link([issue.author], authorName) |
200 | cb(null, req._t('issue.Opened', |
201 | {name: authorLink, datetime: u.timestamp(issue.created_at, req)})) |
202 | }) |
203 | }), |
204 | pull.once('<hr/>' + markdown(issue.text, repo) + '</section>'), |
205 | // render posts and edits |
206 | pull( |
207 | self.web.ssb.links({ |
208 | dest: issue.id, |
209 | values: true |
210 | }), |
211 | pull.unique('key'), |
212 | u.decryptMessages(self.web.ssb), |
213 | u.readableMessages(), |
214 | self.web.addAuthorName(), |
215 | u.sortMsgs(), |
216 | pull.through(function (msg) { |
217 | // the newest message in the issue thread |
218 | // becomes the branch of the new post |
219 | if (msg.value |
220 | && msg.value.timestamp > newestMsg.value.timestamp |
221 | && msg.value.content.root == issue.id) |
222 | newestMsg = msg |
223 | }), |
224 | pull.map(self.renderIssueActivityMsg.bind(self, req, repo, issue, |
225 | req._t('issue.'), postId)) |
226 | ), |
227 | self.web.isPublic ? pull.empty() : u.readOnce(function (cb) { |
228 | cb(null, forms.issueComment(req, issue, repo, |
229 | newestMsg.key, req._t('issue.'))) |
230 | }) |
231 | ])) |
232 | } |
233 | |
234 | I.renderIssueActivityMsg = function (req, repo, issue, type, postId, msg) { |
235 | var id = u.msgIdToDomId(msg.key) |
236 | var authorLink = u.link([msg.value.author], msg.authorName) |
237 | var msgHref = u.encodeLink(msg.key) + '#' + id |
238 | var msgTimeLink = '<a href="' + msgHref + '"' + |
239 | ' name="' + u.escape(msg.key) + '">' + |
240 | new Date(msg.value.timestamp).toLocaleString(req._locale) + '</a>' |
241 | var c = msg.value.content |
242 | switch (c.type) { |
243 | case 'vote': |
244 | return '' |
245 | case 'post': |
246 | if (c.root == issue.id) { |
247 | var changed = this.web.issues.isStatusChanged(msg, issue) |
248 | return '<section class="collapse" id="' + id + '">' + |
249 | (msg.key == postId ? '<div class="highlight">' : '') + |
250 | '<tt class="right-bar item-id">' + msg.key + '</tt> ' + |
251 | (changed == null ? authorLink : req._t( |
252 | changed ? 'issue.Reopened' : 'issue.Closed', |
253 | {name: authorLink, type: type})) + |
254 | ' · ' + msgTimeLink + |
255 | (msg.key == postId ? '</div>' : '') + |
256 | markdown(c.text, repo) + |
257 | '</section>' |
258 | } else { |
259 | var text = c.text || (c.type + ' ' + msg.key) |
260 | return '<section class="collapse mention-preview" id="' + id + '">' + |
261 | req._t('issue.MentionedIn', { |
262 | name: authorLink, |
263 | type: type, |
264 | post: '<a href="/' + msg.key + '#' + msg.key + '">' + |
265 | String(text).substr(0, 140) + '</a>' |
266 | }) + '</section>' |
267 | } |
268 | case 'issue': |
269 | case 'pull-request': |
270 | return '<section class="collapse mention-preview" id="' + id + '">' + |
271 | req._t('issue.MentionedIn', { |
272 | name: authorLink, |
273 | type: type, |
274 | post: u.link([msg.key], u.messageTitle(msg)) |
275 | }) + '</section>' |
276 | case 'issue-edit': |
277 | return '<section class="collapse" id="' + id + '">' + |
278 | (msg.key == postId ? '<div class="highlight">' : '') + |
279 | // handle deprecated rename |
280 | (c.title == null ? '' : req._t('issue.Renamed', { |
281 | author: authorLink, |
282 | type: type, |
283 | name: '<q>' + u.escape(c.title) + '</q>' |
284 | })) + ' · ' + msgTimeLink + |
285 | (msg.key == postId ? '</div>' : '') + |
286 | '</section>' |
287 | case 'git-update': |
288 | var mention = this.web.issues.getMention(msg, issue) |
289 | if (mention) { |
290 | var commitLink = u.link([repo.id, 'commit', mention.object], |
291 | mention.label || mention.object) |
292 | return '<section class="collapse" id="' + id + '">' + |
293 | req._t(mention.open ? 'issue.Reopened' : 'issue.Closed', { |
294 | name: authorLink, |
295 | type: type |
296 | }) + ' · ' + msgTimeLink + '<br/>' + |
297 | commitLink + |
298 | '</section>' |
299 | } else if ((mention = getMention(msg, issue.id))) { |
300 | var commitLink = u.link(mention.object ? |
301 | [repo.id, 'commit', mention.object] : [msg.key], |
302 | mention.label || mention.object || msg.key) |
303 | return '<section class="collapse" id="' + id + '">' + |
304 | req._t('issue.Mentioned', { |
305 | name: authorLink, |
306 | type: type |
307 | }) + ' · ' + msgTimeLink + '<br/>' + |
308 | commitLink + |
309 | '</section>' |
310 | } else { |
311 | // fallthrough |
312 | } |
313 | |
314 | default: |
315 | return '<section class="collapse" id="' + id + '">' + |
316 | authorLink + |
317 | ' · ' + msgTimeLink + |
318 | u.json(c) + |
319 | '</section>' |
320 | } |
321 | } |
322 |
Built with git-ssb-web