git ssb

30+

cel / git-ssb-web



Tree: 07b3119b1103ad1b3e666beed774e28f425a53c8

Files: 07b3119b1103ad1b3e666beed774e28f425a53c8 / lib / repos / issues.js

10975 bytesRaw
1var pull = require('pull-stream')
2var h = require('pull-hyperscript')
3var paramap = require('pull-paramap')
4var cat = require('pull-cat')
5var multicb = require('multicb')
6var u = require('../util')
7var markdown = require('../markdown')
8var forms = require('../forms')
9
10
11module.exports = function (repoRoutes, web) {
12 return new RepoIssueRoutes(repoRoutes, web)
13}
14
15function RepoIssueRoutes(repoRoutes, web) {
16 this.repo = repoRoutes
17 this.web = web
18}
19
20var I = RepoIssueRoutes.prototype
21
22function 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
35I.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">&plus; ' +
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
158I.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
171I.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
234I.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 ' &middot; ' + 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 })) + ' &middot; ' + 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 }) + ' &middot; ' + 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 }) + ' &middot; ' + msgTimeLink + '<br/>' +
308 commitLink +
309 '</section>'
310 } else {
311 // fallthrough
312 }
313
314 default:
315 return '<section class="collapse" id="' + id + '">' +
316 authorLink +
317 ' &middot; ' + msgTimeLink +
318 u.json(c) +
319 '</section>'
320 }
321}
322

Built with git-ssb-web