Files: 23fd0e96f5d3428c5da449cebc49cd30b9b9d8be / lib / repos / issues.js
8583 bytesRaw
1 | var pull = require('pull-stream') |
2 | var cat = require('pull-cat') |
3 | var u = require('../util') |
4 | var markdown = require('../markdown') |
5 | var forms = require('../forms') |
6 | |
7 | module.exports = function (repoRoutes, web) { |
8 | return new RepoIssueRoutes(repoRoutes, web) |
9 | } |
10 | |
11 | function RepoIssueRoutes(repoRoutes, web) { |
12 | this.repo = repoRoutes |
13 | this.web = web |
14 | } |
15 | |
16 | var I = RepoIssueRoutes.prototype |
17 | |
18 | function getMention(msg, id) { |
19 | if (msg.key == id) return msg |
20 | var mentions = msg.value.content.mentions |
21 | if (mentions) for (var i = 0; i < mentions.length; i++) { |
22 | var mention = mentions[i] |
23 | if (mention.link == id) |
24 | return mention |
25 | } |
26 | return null |
27 | } |
28 | |
29 | /* Issues */ |
30 | |
31 | I.serveRepoIssues = function (req, repo, isPRs) { |
32 | var self = this |
33 | var count = 0 |
34 | var state = req._u.query.state || 'open' |
35 | var newPath = isPRs ? [repo.id, 'compare'] : [repo.id, 'issues', 'new'] |
36 | var title = req._t('Issues') + ' · %{author}/%{repo}' |
37 | var page = isPRs ? 'pulls' : 'issues' |
38 | return self.repo.renderRepoPage(req, repo, page, null, title, cat([ |
39 | pull.once( |
40 | (self.web.isPublic ? '' : |
41 | '<form class="right-bar" method="get"' + |
42 | ' action="' + u.encodeLink(newPath) + '">' + |
43 | '<button class="btn">+ ' + |
44 | req._t(isPRs ? 'pullRequest.New' : 'issue.New') + |
45 | '</button>' + |
46 | '</form>') + |
47 | '<h3>' + req._t(isPRs ? 'PullRequests' : 'Issues') + '</h3>' + |
48 | u.nav([ |
49 | ['?', req._t('issues.Open'), 'open'], |
50 | ['?state=closed', req._t('issues.Closed'), 'closed'], |
51 | ['?state=all', req._t('issues.All'), 'all'] |
52 | ], state)), |
53 | pull( |
54 | (isPRs ? self.web.pullReqs : self.web.issues).list({ |
55 | repo: repo.id, |
56 | project: repo.id, |
57 | reverse: true, |
58 | open: {open: true, closed: false}[state] |
59 | }), |
60 | pull.map(function (issue) { |
61 | count++ |
62 | var state = (issue.open ? 'open' : 'closed') |
63 | var stateStr = req._t(issue.open ? |
64 | 'issue.state.Open' : 'issue.state.Closed') |
65 | return '<section class="collapse">' + |
66 | '<i class="issue-state issue-state-' + state + '"' + |
67 | ' title="' + stateStr + '">◼</i> ' + |
68 | '<a href="' + u.encodeLink(issue.id) + '">' + |
69 | u.escape(issue.title) + |
70 | '<span class="right-bar">' + |
71 | new Date(issue.created_at).toLocaleString(req._locale) + |
72 | '</span>' + |
73 | '</a>' + |
74 | '</section>' |
75 | }) |
76 | ), |
77 | u.readOnce(function (cb) { |
78 | cb(null, count > 0 ? '' : |
79 | '<p>' + req._t(isPRs ? 'NoPullRequests' : 'NoIssues') + '</p>') |
80 | }) |
81 | ])) |
82 | } |
83 | |
84 | /* New Issue */ |
85 | |
86 | I.serveRepoNewIssue = function (req, repo, issueId, path) { |
87 | var title = req._t('issue.New') + ' · %{author}/%{repo}' |
88 | return this.repo.renderRepoPage(req, repo, 'issues', null, title, pull.once( |
89 | '<h3>' + req._t('issue.New') + '</h3>' + |
90 | '<section><form action="" method="post">' + |
91 | '<input type="hidden" name="action" value="new-issue">' + |
92 | '<p><input class="wide-input" name="title" placeholder="' + |
93 | req._t('issue.Title') + '" size="77" /></p>' + |
94 | forms.post(req, repo, req._t('Description'), 8) + |
95 | '<button type="submit" class="btn">' + req._t('Create') + '</button>' + |
96 | '</form></section>')) |
97 | } |
98 | |
99 | /* Issue */ |
100 | |
101 | I.serveRepoIssue = function (req, repo, issue, path, postId) { |
102 | var self = this |
103 | var isAuthor = (self.web.myId == issue.author) |
104 | || (self.web.myId == repo.feed) |
105 | var newestMsg = {key: issue.id, value: {timestamp: issue.created_at}} |
106 | var title = u.escape(issue.title) + ' · %{author}/%{repo}' |
107 | return self.repo.renderRepoPage(req, repo, 'issues', null, title, cat([ |
108 | pull.once( |
109 | forms.name(req, !self.web.isPublic, issue.id, issue.title, |
110 | 'issue-title', null, req._t('issue.Rename'), |
111 | '<h3>' + u.link([issue.id], issue.title) + '</h3>') + |
112 | '<code>' + issue.id + '</code>' + |
113 | '<section class="collapse">' + |
114 | (issue.open |
115 | ? '<strong class="issue-status open">' + |
116 | req._t('issue.state.Open') + '</strong>' |
117 | : '<strong class="issue-status closed">' + |
118 | req._t('issue.state.Closed') + '</strong>')), |
119 | u.readOnce(function (cb) { |
120 | self.web.about.getName(issue.author, function (err, authorName) { |
121 | if (err) return cb(err) |
122 | var authorLink = u.link([issue.author], authorName) |
123 | cb(null, req._t('issue.Opened', |
124 | {name: authorLink, datetime: u.timestamp(issue.created_at, req)})) |
125 | }) |
126 | }), |
127 | pull.once('<hr/>' + markdown(issue.text, repo) + '</section>'), |
128 | // render posts and edits |
129 | pull( |
130 | self.web.ssb.links({ |
131 | dest: issue.id, |
132 | values: true |
133 | }), |
134 | pull.unique('key'), |
135 | self.web.addAuthorName(), |
136 | u.sortMsgs(), |
137 | pull.through(function (msg) { |
138 | // the newest message in the issue thread |
139 | // becomes the branch of the new post |
140 | if (msg.value |
141 | && msg.value.timestamp > newestMsg.value.timestamp |
142 | && msg.value.content.root == issue.id) |
143 | newestMsg = msg |
144 | }), |
145 | pull.map(self.renderIssueActivityMsg.bind(self, req, repo, issue, |
146 | req._t('issue.'), postId)) |
147 | ), |
148 | self.web.isPublic ? pull.empty() : u.readOnce(function (cb) { |
149 | cb(null, forms.issueComment(req, issue, repo, |
150 | newestMsg.key, isAuthor, req._t('issue.'))) |
151 | }) |
152 | ])) |
153 | } |
154 | |
155 | I.renderIssueActivityMsg = function (req, repo, issue, type, postId, msg) { |
156 | var authorLink = u.link([msg.value.author], msg.authorName) |
157 | var msgHref = u.encodeLink(msg.key) + '#' + encodeURIComponent(msg.key) |
158 | var msgTimeLink = '<a href="' + msgHref + '"' + |
159 | ' name="' + u.escape(msg.key) + '">' + |
160 | new Date(msg.value.timestamp).toLocaleString(req._locale) + '</a>' |
161 | var c = msg.value.content |
162 | switch (c.type) { |
163 | case 'vote': |
164 | return '' |
165 | case 'post': |
166 | if (c.root == issue.id) { |
167 | var changed = this.web.issues.isStatusChanged(msg, issue) |
168 | return '<section class="collapse">' + |
169 | (msg.key == postId ? '<div class="highlight">' : '') + |
170 | '<tt class="right-bar item-id">' + msg.key + '</tt> ' + |
171 | (changed == null ? authorLink : req._t( |
172 | changed ? 'issue.Reopened' : 'issue.Closed', |
173 | {name: authorLink, type: type})) + |
174 | ' · ' + msgTimeLink + |
175 | (msg.key == postId ? '</div>' : '') + |
176 | markdown(c.text, repo) + |
177 | '</section>' |
178 | } else { |
179 | var text = c.text || (c.type + ' ' + msg.key) |
180 | return '<section class="collapse mention-preview">' + |
181 | req._t('issue.MentionedIn', { |
182 | name: authorLink, |
183 | type: type, |
184 | post: '<a href="/' + msg.key + '#' + msg.key + '">' + |
185 | String(text).substr(0, 140) + '</a>' |
186 | }) + '</section>' |
187 | } |
188 | case 'issue': |
189 | case 'pull-request': |
190 | return '<section class="collapse mention-preview">' + |
191 | req._t('issue.MentionedIn', { |
192 | name: authorLink, |
193 | type: type, |
194 | post: u.link([msg.key], String(c.title || msg.key).substr(0, 140)) |
195 | }) + '</section>' |
196 | case 'issue-edit': |
197 | return '<section class="collapse">' + |
198 | (msg.key == postId ? '<div class="highlight">' : '') + |
199 | (c.title == null ? '' : req._t('issue.Renamed', { |
200 | author: authorLink, |
201 | type: type, |
202 | name: '<q>' + u.escape(c.title) + '</q>' |
203 | })) + ' · ' + msgTimeLink + |
204 | (msg.key == postId ? '</div>' : '') + |
205 | '</section>' |
206 | case 'git-update': |
207 | var mention = this.web.issues.getMention(msg, issue) |
208 | if (mention) { |
209 | var commitLink = u.link([repo.id, 'commit', mention.object], |
210 | mention.label || mention.object) |
211 | return '<section class="collapse">' + |
212 | req._t(mention.open ? 'issue.Reopened' : 'issue.Closed', { |
213 | name: authorLink, |
214 | type: type |
215 | }) + ' · ' + msgTimeLink + '<br/>' + |
216 | commitLink + |
217 | '</section>' |
218 | } else if ((mention = getMention(msg, issue.id))) { |
219 | var commitLink = u.link(mention.object ? |
220 | [repo.id, 'commit', mention.object] : [msg.key], |
221 | mention.label || mention.object || msg.key) |
222 | return '<section class="collapse">' + |
223 | req._t('issue.Mentioned', { |
224 | name: authorLink, |
225 | type: type |
226 | }) + ' · ' + msgTimeLink + '<br/>' + |
227 | commitLink + |
228 | '</section>' |
229 | } else { |
230 | // fallthrough |
231 | } |
232 | |
233 | default: |
234 | return '<section class="collapse">' + |
235 | authorLink + |
236 | ' · ' + msgTimeLink + |
237 | u.json(c) + |
238 | '</section>' |
239 | } |
240 | } |
241 |
Built with git-ssb-web