Skip to content

Commit

Permalink
Enhanced plugin to also embed commits
Browse files Browse the repository at this point in the history
Embedding of commits is now also supported. Short form (gh@hash) or long form (full url). Hooray!
  • Loading branch information
julianlam committed Aug 19, 2015
1 parent 2136219 commit 8c49fc3
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 57 deletions.
171 changes: 140 additions & 31 deletions index.js
Expand Up @@ -9,9 +9,11 @@ var request = require('request'),
meta = module.parent.require('./meta'),

issueRegex = /(?:^|[\s])(?:[\w\d\-.]+\/[\w\d\-.]+|gh|GH)#\d+\b/gm,
fullUrlRegex = /https:\/\/github.com\/([\w]+\/[\w]+)\/issues\/([\d]+)/g,
commitRegex = /(?:^|[\s])(?:[\w\d\-.]+\/[\w\d\-.]+|gh|GH)@[A-Fa-f0-9]{7,}\b/gm,
fullUrlIssueRegex = /https:\/\/github.com\/([\w\d\-.]+\/[\w\d\-.]+)\/issues\/([\d]+)/g,
fullUrlCommitRegex = /https:\/\/github.com\/([\w\d\-.]+\/[\w\d\-.]+)\/commit\/([A-Fa-f0-9]{7,})/g,
Embed = {},
cache, defaultRepo, tokenString, personalAccessToken, appModule;
issueCache, commitCache, defaultRepo, tokenString, personalAccessToken, appModule;

Embed.init = function(data, callback) {
function render(req, res) {
Expand All @@ -37,16 +39,19 @@ Embed.buildMenu = function(custom_header, callback) {

Embed.parse = function(data, callback) {
var issueKeys = [],
commitKeys = [],
ltrimRegex = /^\s+/,
raw = typeof data !== 'object',
fullUrlMatch = {},
matches, cleanedText;
fullUrlIssueMatch = {},
fullUrlCommitMatch = {},
issueMatches, commitMatches, cleanedText;

cleanedText = S((raw ? data : data.postData.content).replace(/<blockquote>[\s\S]+?<\/blockquote>/g, '')).stripTags().s;
matches = cleanedText.match(issueRegex);
issueMatches = cleanedText.match(issueRegex);
commitMatches = cleanedText.match(commitRegex);

if (matches && matches.length) {
matches.forEach(function(match) {
if (issueMatches && issueMatches.length) {
issueMatches.forEach(function(match) {
match = match.replace(ltrimRegex, '');

if (match.slice(0, 2).toLowerCase() === 'gh') {
Expand All @@ -64,44 +69,89 @@ Embed.parse = function(data, callback) {
});
}

while(fullUrlMatch.obj = fullUrlRegex.exec(cleanedText)) {
fullUrlMatch.repo = fullUrlMatch.obj[1];
fullUrlMatch.issue = fullUrlMatch.obj[2];
while(fullUrlIssueMatch.obj = fullUrlIssueRegex.exec(cleanedText)) {
fullUrlIssueMatch.repo = fullUrlIssueMatch.obj[1];
fullUrlIssueMatch.issue = fullUrlIssueMatch.obj[2];

issueKeys.push([fullUrlMatch.repo, fullUrlMatch.issue].join('#'));
issueKeys.push([fullUrlIssueMatch.repo, fullUrlIssueMatch.issue].join('#'));
}

async.map(issueKeys, function(issueKey, next) {
if (cache.has(issueKey)) {
next(null, cache.get(issueKey));
} else {
getIssueData(issueKey, function(err, issueObj) {
if (err) {
return next(err);
if (commitMatches && commitMatches.length) {
commitMatches.forEach(function(match) {
match = match.replace(ltrimRegex, '');

if (match.slice(0, 2).toLowerCase() === 'gh') {
if (defaultRepo !== undefined) {
match = defaultRepo + match.slice(2);
} else {
// If a defaultRepo is not defined, skip this match.
match = null;
}
}

cache.set(issueKey, issueObj);
next(err, issueObj);
});
if (match !== null && commitKeys.indexOf(match) === -1) {
commitKeys.push(match);
}
});
}

while(fullUrlCommitMatch.obj = fullUrlCommitRegex.exec(cleanedText)) {
fullUrlCommitMatch.repo = fullUrlCommitMatch.obj[1];
fullUrlCommitMatch.commit = fullUrlCommitMatch.obj[2];

commitKeys.push([fullUrlCommitMatch.repo, fullUrlCommitMatch.commit].join('@'));
}

async.parallel({
issues: function(next) {
async.map(issueKeys, function(issueKey, next) {
if (issueCache.has(issueKey)) {
next(null, issueCache.get(issueKey));
} else {
getIssueData(issueKey, function(err, issueObj) {
if (err) {
return next(err);
}

issueCache.set(issueKey, issueObj);
next(err, issueObj);
});
}
}, next);
},
commits: function(next) {
async.map(commitKeys, function(commitKey, next) {
if (commitCache.has(commitKey)) {
next(null, commitCache.get(commitKey));
} else {
getCommitData(commitKey, function(err, commitObj) {
if (err) {
return next(err);
}

commitCache.set(commitKey, commitObj);
next(err, commitObj);
});
}
}, next);
}
}, function(err, issues) {
}, function(err, payload) {
if (!err) {
// Filter out non-existant issues
issues = issues.filter(function(issue) {
return issue;
});
payload.issues = payload.issues.filter(Boolean);
payload.commits = payload.commits.filter(Boolean);

var payload;
var returnString;

appModule.render('partials/issues-block', {
issues: issues
appModule.render('partials/embed-block', {
embeds: payload.issues.concat(payload.commits)
}, function(err, cardHTML) {
if (raw) {
payload = data += cardHTML;
returnString = data += cardHTML;
} else {
data.postData.content += cardHTML;
}
callback(null, payload || data);
callback(null, returnString || data);
});
} else {
winston.warn('Encountered an error parsing GitHub embed codes, not continuing');
Expand Down Expand Up @@ -135,6 +185,10 @@ var getIssueData = function(issueKey, callback) {
if (response && response.statusCode === 200) {
var issue = JSON.parse(body),
returnData = {
type: {
issue: true,
commit: false
},
repo: repo,
number: issue.number,
url: issue.html_url,
Expand All @@ -154,11 +208,66 @@ var getIssueData = function(issueKey, callback) {
});
};

var getCommitData = function(commitKey, callback) {
var commitData = commitKey.split('@'),
repo = commitData[0],
hash = commitData[1],
reqOpts = {
url: 'https://api.github.com/repos/' + repo + '/commits/' + hash + (tokenString || ''),
headers: {
'User-Agent': 'nodebb-plugin-github-embed'
}
};

if (personalAccessToken) {
reqOpts.auth = {
user: personalAccessToken,
pass: 'x-oauth-basic'
};
}

request.get(reqOpts, function(err, response, body) {
if (err) {
return callback(err);
}
if (response && response.statusCode === 200) {
var commit = JSON.parse(body),
returnData = {
type: {
issue: false,
commit: true
},
repo: repo,
sha: commit.sha,
url: commit.html_url,
message: commit.commit.message,
created: commit.commit.author.date,
commentCount: commit.commit.comment_count,
user: {
login: commit.author.login,
url: commit.author.html_url,
picture: commit.author.avatar_url
}
};

callback(null, returnData);
} else if (response.statusCode === 404) {
winston.verbose('[plugins/github-embed] No matching commit ' + hash + ' in repository ' + repo);
callback();
}
});
};

// Initial setup
meta.settings.get('github-embed', function(err, settings) {
defaultRepo = settings.defaultRepo;

cache = require('lru-cache')({
issueCache = require('lru-cache')({
maxAge: 1000*60*60*(settings.cacheHours || 6),
max: 100
});

commitCache = require('lru-cache')({
maxAge: 1000*60*60*(settings.cacheHours || 6),
max: 100
});
Expand Down
24 changes: 17 additions & 7 deletions less/default.less
@@ -1,4 +1,8 @@
.github-issue {
.github-embeds-container {
margin-top: 1rem;
}

.github-embed {
font: 13px Helvetica, arial, freesans, clean, sans-serif;

a {
Expand Down Expand Up @@ -33,6 +37,12 @@
}
}

pre {
background: none;
border: 0px;
margin-top: 1rem;
}

.meta {
color: #777;
margin-bottom: 2px;
Expand All @@ -41,10 +51,10 @@
font-weight: bold;
}
}
}

img.author-picture {
float: left;
width: 16px;
margin-right: 0.5em;
}
img.author-picture {
float: left;
width: 16px;
margin-right: 0.5em;
}
}
35 changes: 35 additions & 0 deletions templates/partials/embed-block.tpl
@@ -0,0 +1,35 @@
<div class="row github-embeds-container">
<!-- BEGIN embeds -->
<!-- IF embeds.type.issue -->
<div class="col-md-6">
<div class="github-embed panel panel-default">
<div class="panel-body">
<div class="meta">
<img class="author-picture" src="{embeds.user.picture}" title="{embeds.user.login}" />
<a href="{embeds.user.url}"><span class="username">{embeds.user.login}</span></a> created this issue <span class="timeago" title="{embeds.created}"></span> in <a href="//github.com/{embeds.repo}">{embeds.repo}</a>
</div>
<h3>
<span class="label label-default {embeds.state} pull-right">{embeds.state}</span>
<a href="{embeds.url}">{embeds.title}</a>
<span class="number">#{embeds.number}</span>
</h3>
</div>
</div>
</div>
<!-- ENDIF embeds.type.issue -->
<!-- IF embeds.type.commit -->
<div class="col-md-6">
<div class="github-embed panel panel-default">
<div class="panel-body">
<div class="meta">
<span class="pull-right">{embeds.commentCount} <i class="fa fa-comment"></i></span>
<img class="author-picture" src="{embeds.user.picture}" title="{embeds.user.login}" />
<a href="{embeds.user.url}"><span class="username">{embeds.user.login}</span></a> committed <span class="timeago" title="{embeds.created}"></span> to <a href="//github.com/{embeds.repo}">{embeds.repo}</a>
</div>
<a href="{embeds.url}"><pre>{embeds.message}</pre></a>
</div>
</div>
</div>
<!-- ENDIF embeds.type.commit -->
<!-- END embeds -->
</div>
19 changes: 0 additions & 19 deletions templates/partials/issues-block.tpl

This file was deleted.

0 comments on commit 8c49fc3

Please sign in to comment.