index.jsView |
---|
19 | 19 | var paramap = require('pull-paramap') |
20 | 20 | var gitPack = require('pull-git-pack') |
21 | 21 | var Mentions = require('ssb-mentions') |
22 | 22 | var Highlight = require('highlight.js') |
| 23 | +var JsDiff = require('diff') |
23 | 24 | |
24 | 25 | |
25 | 26 | var blockRenderer = new marked.Renderer() |
26 | 27 | blockRenderer.urltransform = function (url) { |
231 | 232 | }) |
232 | 233 | } |
233 | 234 | } |
234 | 235 | |
| 236 | +function readObjectString(obj, cb) { |
| 237 | + pull(obj.read, pull.collect(function (err, bufs) { |
| 238 | + if (err) return cb(err) |
| 239 | + cb(null, Buffer.concat(bufs, obj.length).toString('utf8')) |
| 240 | + })) |
| 241 | +} |
| 242 | + |
| 243 | +function getRepoObjectString(repo, id, cb) { |
| 244 | + if (!id) return cb(null, '') |
| 245 | + repo.getObjectFromAny(id, function (err, obj) { |
| 246 | + if (err) return cb(err) |
| 247 | + readObjectString(obj, cb) |
| 248 | + }) |
| 249 | +} |
| 250 | + |
235 | 251 | function compareMsgs(a, b) { |
236 | 252 | return (a.value.timestamp - b.value.timestamp) || (a.key - b.key) |
237 | 253 | } |
238 | 254 | |
633 | 649 | |
634 | 650 | function renderObjectData(obj, filename, repo) { |
635 | 651 | var ext = (/\.([^.]+)$/.exec(filename) || [,filename])[1] |
636 | 652 | return readOnce(function (cb) { |
637 | | - pull(obj.read, pull.collect(function (err, bufs) { |
| 653 | + readObjectString(obj, function (err, buf) { |
| 654 | + buf = buf.toString('utf8') |
638 | 655 | if (err) return cb(err) |
639 | | - var buf = Buffer.concat(bufs, obj.length).toString('utf8') |
640 | 656 | cb(null, (ext == 'md' || ext == 'markdown') |
641 | 657 | ? markdown(buf, repo) |
642 | 658 | : '<pre>' + highlight(buf, ext) + '</pre>') |
643 | | - })) |
| 659 | + }) |
644 | 660 | }) |
645 | 661 | } |
646 | 662 | |
647 | 663 | |
1171 | 1187 | return 'Parent: ' + link([repo.id, 'commit', id], id) |
1172 | 1188 | }).join('<br>') + '</p>' + |
1173 | 1189 | (commit.tree ? 'Tree: ' + link(treePath) : 'No tree') + |
1174 | 1190 | '</section>'), |
1175 | | - renderDiffStat(repo, commit.tree, commit.parents) |
| 1191 | + renderDiffStat(repo, commit.id, commit.parents) |
1176 | 1192 | ])) |
1177 | 1193 | }) |
1178 | 1194 | }) |
1179 | 1195 | ])) |
1184 | 1200 | function renderDiffStat(repo, id, parentIds) { |
1185 | 1201 | if (parentIds.length == 0) parentIds = [null] |
1186 | 1202 | var lastI = parentIds.length |
1187 | 1203 | var oldTree = parentIds[0] |
| 1204 | + var changedFiles = [] |
1188 | 1205 | return cat([ |
1189 | 1206 | pull.once('<section><h3>Files changed</h3>'), |
1190 | 1207 | pull( |
1191 | 1208 | repo.diffTrees(parentIds.concat(id), true), |
1192 | 1209 | pull.map(function (item) { |
1193 | | - var filename = escapeHTML(item.path.join('/')) |
| 1210 | + var filename = item.filename = escapeHTML(item.path.join('/')) |
1194 | 1211 | var oldId = item.id && item.id[0] |
1195 | 1212 | var newId = item.id && item.id[lastI] |
1196 | | - var oldMode = item.mode && item.mode[0].toString(8) |
1197 | | - var newMode = item.mode && item.mode[lastI].toString(8) |
| 1213 | + var oldMode = item.mode && item.mode[0] |
| 1214 | + var newMode = item.mode && item.mode[lastI] |
1198 | 1215 | var action = |
1199 | | - !oldId && newId ? 'new' : |
| 1216 | + !oldId && newId ? 'added' : |
1200 | 1217 | oldId && !newId ? 'deleted' : |
1201 | 1218 | oldMode != newMode ? |
1202 | | - 'changed mode from ' + oldMode + ' to ' + newMode : |
1203 | | - '' |
1204 | | - var newLink = newId ? |
1205 | | - link([repo.id, 'blob', id].concat(item.path), 'new') : '' |
1206 | | - var oldLink = oldId ? |
1207 | | - link([repo.id, 'blob', oldTree].concat(item.path), 'old') : '' |
1208 | | - var links = [oldLink, newLink] |
1209 | | - var fileLink = newLink || oldLink ? filename : |
1210 | | - link([repo.id, 'blob', id].concat(item.path), filename) |
1211 | | - return [fileLink, action, links.filter(Boolean).join(', ')] |
| 1219 | + 'changed mode from ' + oldMode.toString(8) + |
| 1220 | + ' to ' + newMode.toString(8) : |
| 1221 | + 'changed' |
| 1222 | + if (item.id) |
| 1223 | + changedFiles.push(item) |
| 1224 | + var fileHref = item.id ? |
| 1225 | + '#' + encodeURIComponent(item.path.join('/')) : |
| 1226 | + encodeLink([repo.id, 'blob', id].concat(item.path)) |
| 1227 | + return ['<a href="' + fileHref + '">' + filename + '</a>', action] |
1212 | 1228 | }), |
1213 | 1229 | table() |
1214 | 1230 | ), |
| 1231 | + pull( |
| 1232 | + pull.values(changedFiles), |
| 1233 | + paramap(function (item, cb) { |
| 1234 | + var done = multicb({ pluck: 1, spread: true }) |
| 1235 | + getRepoObjectString(repo, item.id[0], done()) |
| 1236 | + getRepoObjectString(repo, item.id[lastI], done()) |
| 1237 | + done(function (err, strOld, strNew) { |
| 1238 | + if (err) return cb(err) |
| 1239 | + var commitId = item.id[lastI] ? id : parentIds.filter(Boolean)[0] |
| 1240 | + cb(null, htmlLineDiff(item.filename, item.filename, |
| 1241 | + strOld, strNew, |
| 1242 | + encodeLink([repo.id, 'blob', commitId].concat(item.path)))) |
| 1243 | + }) |
| 1244 | + }, 4) |
| 1245 | + ), |
1215 | 1246 | pull.once('</section>'), |
1216 | 1247 | ]) |
1217 | 1248 | } |
1218 | 1249 | |
| 1250 | + function htmlLineDiff(filename, anchor, oldStr, newStr, blobHref, rawHref) { |
| 1251 | + var diff = JsDiff.structuredPatch('', '', oldStr, newStr) |
| 1252 | + var groups = diff.hunks.map(function (hunk) { |
| 1253 | + var oldLine = hunk.oldStart |
| 1254 | + var newLine = hunk.newStart |
| 1255 | + var header = '<tr class="diff-hunk-header"><td colspan=2></td><td>' + |
| 1256 | + '@@ -' + oldLine + ',' + hunk.oldLines + ' ' + |
| 1257 | + '+' + newLine + ',' + hunk.newLines + ' @@' + |
| 1258 | + '</td></tr>' |
| 1259 | + return [header].concat(hunk.lines.map(function (line) { |
| 1260 | + var s = line[0] |
| 1261 | + if (s == '\\') return |
| 1262 | + var html = escapeHTML(line) |
| 1263 | + var trClass = s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : '' |
| 1264 | + return '<tr' + (trClass ? ' class="' + trClass + '"' : '') + '>' + |
| 1265 | + '<td class="diff-linenum">' + (s == '+' ? '' : oldLine++) + '</td>' + |
| 1266 | + '<td class="diff-linenum">' + (s == '-' ? '' : newLine++) + '</td>' + |
| 1267 | + '<td class="diff-text">' + html + '</td></tr>' |
| 1268 | + })) |
| 1269 | + }) |
| 1270 | + return '<pre><table class="diff">' + |
| 1271 | + '<tr><th colspan=3><a name="' + anchor + '">' + filename + '</a>' + |
| 1272 | + '<span class="right-bar">' + |
| 1273 | + '<a href="' + blobHref + '">View</a> ' + |
| 1274 | + '</span></th></tr>' + |
| 1275 | + [].concat.apply([], groups).join('') + |
| 1276 | + '</table></pre>' |
| 1277 | + } |
| 1278 | + |
1219 | 1279 | |
1220 | 1280 | |
1221 | 1281 | function serveRepoSomething(req, repo, id, msg, path) { |
1222 | 1282 | return renderRepoPage(repo, null, null, |