diff --git a/index.js b/index.js
index 4342bb8..56b42fe 100644
--- a/index.js
+++ b/index.js
@@ -1,15 +1,59 @@
var $ = require('jquery')
-var Mustache = require('mustache')
+var Handlebars = require('handlebars')
function AnnotationPoller (opts) {
+ this._installExtensions()
+
this.pollInterval = opts.pollInterval || 3000
this.pkg = opts.pkg // what package should we load annotations for?
this.endpoint = '/api/v1/annotations/' + this.pkg
this.annotations = {}
- this.template = '
{{description}}{{external-link-text}}'
+ this.template = Handlebars.compile(
+ '' +
+ '' +
+ ' {{name}}
' +
+ ' {{#each rows}}' +
+ ' - ' +
+ ' {{#hasKey this "image"}}' +
+ ' ' +
+ ' {{/hasKey}}' +
+ ' {{#hasKey this "link"}}' +
+ ' {{#isArray this "link"}}' +
+ ' {{#each link}}' +
+ ' {{text}}{{#unless @last}},{{/unless}}' +
+ ' {{/each}}' +
+ ' {{else}}' +
+ ' {{link.text}}' +
+ ' {{/isArray}}' +
+ ' {{/hasKey}}' +
+ ' {{#hasKey this "text"}}' +
+ ' {{{text}}}' +
+ ' {{/hasKey}}' +
+ '
' +
+ ' {{/each}}' +
+ '
' +
+ '')
this.addonSelector = '#npm-addon-box'
}
+AnnotationPoller.prototype._installExtensions = function () {
+ Handlebars.registerHelper('hasKey', function (obj, key, options) {
+ if (typeof obj === 'object' && obj[key]) {
+ return options.fn(this)
+ } else {
+ return options.inverse(this)
+ }
+ })
+
+ Handlebars.registerHelper('isArray', function (obj, key, options) {
+ if ($.isArray(obj[key])) {
+ return options.fn(this)
+ } else {
+ return options.inverse(this)
+ }
+ })
+}
+
AnnotationPoller.prototype.start = function (loaded) {
var _this = this
var updating = false
@@ -53,11 +97,11 @@ AnnotationPoller.prototype.renderAnnotations = function () {
var addonBox = $(this.addonSelector)
Object.keys(this.annotations).forEach(function (key) {
- annotation = _this.annotations[key]
+ annotation = _this._applyReplacements(_this.annotations[key])
if (annotation._rendered) return
annotationElement = $('#annotation-' + annotation.id)
- newAnnotationElement = Mustache.render(_this.template, annotation)
+ newAnnotationElement = _this.template(annotation)
if (annotationElement.length) {
// don't render the element unless its fingerprint has changed.
if (annotationElement.data('fingerprint') !== annotation.fingerprint) {
@@ -70,6 +114,42 @@ AnnotationPoller.prototype.renderAnnotations = function () {
})
}
+AnnotationPoller.prototype._applyReplacements = function (obj) {
+ var _this = this
+ if ($.isArray(obj.rows)) {
+ obj.rows.forEach(function (row) {
+ // bold any text in between *foo*.
+ if (row.text) {
+ row.text = _this._escape(row.text)
+ row.text = row.text.replace(/\*(.+)\*/, '$1')
+ }
+
+ // escape any HTML in links.
+ if ($.isArray(row.link)) {
+ row.link.forEach(function (l) {
+ if (l.url) l.url = _this._escape(l.url)
+ })
+ } else if (row.link) {
+ if (row.link.url) row.link.url = _this._escape(row.link.url)
+ }
+
+ // escape any HTML in image links.
+ if (row.image) {
+ if (row.image.url) row.image.url = _this._escape(row.image.url)
+ }
+ })
+ } else {
+ // we shouldn't allow obj.rows
+ // to be a non-array value.
+ obj.rows = []
+ }
+ return obj
+}
+
+AnnotationPoller.prototype._escape = function (text) {
+ return $('').text(text).html()
+}
+
module.exports = function (opts) {
return new AnnotationPoller(opts)
}
diff --git a/package.json b/package.json
index e68079e..bf505e2 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
"standard-version": "^2.1.2"
},
"dependencies": {
- "jquery": "^2.2.2",
- "mustache": "^2.2.1"
+ "handlebars": "^4.0.5",
+ "jquery": "^2.2.2"
}
-}
\ No newline at end of file
+}
diff --git a/test.js b/test.js
index fede2b6..e0036e9 100644
--- a/test.js
+++ b/test.js
@@ -24,12 +24,19 @@ describe('annotation-poller', function () {
url: endpoint,
responseText: [{
id: 'abc-123-abc',
- status: 'warn',
- 'status-message': 'module not yet scanned',
- description: 'foo security integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'start audit',
- fingerprint: 'foo'
+ name: 'Awesome Integration',
+ fingerprint: 'a',
+ rows: [{
+ image: {
+ url: 'http://www.example.com/img',
+ text: 'image alt'
+ },
+ link: {
+ url: 'http://www.example.com',
+ text: 'my awesome link'
+ },
+ text: 'hello *world*!'
+ }]
}]
})
@@ -47,19 +54,64 @@ describe('annotation-poller', function () {
url: endpoint,
responseText: [{
id: 'abc-123-abc',
- status: 'warn',
- 'status-message': 'module not yet scanned',
- description: 'my awesome integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'start audit',
- fingerprint: 'bar'
+ name: 'second integration',
+ fingerprint: 'b',
+ rows: [{
+ link: {
+ url: 'http://www.example.com',
+ text: 'my awesome link'
+ }
+ }]
+ }]
+ })
+
+ var poller = annotationPoller({pollInterval: 50, pkg: pkg})
+ poller.start(function () {
+ $('.addon-container').length.should.equal(1)
+ $('.addon-container').text().should.match(/second integration/)
+ poller.stop()
+ return done()
+ })
+ })
+
+ it('replaces *text* with bold', function (done) {
+ $.mockjax({
+ url: endpoint,
+ responseText: [{
+ id: 'abc-123-abc',
+ name: 'second integration',
+ fingerprint: 'c',
+ rows: [{
+ text: 'my *awesome* message'
+ }]
+ }]
+ })
+
+ var poller = annotationPoller({pollInterval: 50, pkg: pkg})
+ poller.start(function () {
+ $('b').text().should.equal('awesome')
+ poller.stop()
+ return done()
+ })
+ })
+
+ it('handles an array of links', function (done) {
+ $.mockjax({
+ url: endpoint,
+ responseText: [{
+ id: 'abc-123-abc',
+ name: 'second integration',
+ fingerprint: 'd',
+ rows: [{
+ link: [{url: 'http://example.com', text: 'link 1'}, {url: 'http://2.example.com', text: 'link 2'}]
+ }]
}]
})
var poller = annotationPoller({pollInterval: 50, pkg: pkg})
poller.start(function () {
- $('ul li').length.should.equal(1)
- $('ul li').text().should.match(/my awesome integration/)
+ $('.addon-container:first').text().should.match(/link 1/)
+ $('.addon-container:last').text().should.match(/link 2/)
poller.stop()
return done()
})
@@ -70,12 +122,14 @@ describe('annotation-poller', function () {
url: endpoint,
responseText: [{
id: 'abc-123-abc',
- status: 'warn',
- 'status-message': 'module not yet scanned',
- description: 'my awesome integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'start audit',
- fingerprint: 'foo'
+ name: 'third integration',
+ fingerprint: 'foo',
+ rows: [{
+ link: {
+ url: 'http://www.example.com',
+ text: 'initial link'
+ }
+ }]
}]
})
@@ -86,18 +140,20 @@ describe('annotation-poller', function () {
url: endpoint,
responseText: [{
id: 'abc-123-abc',
- status: 'green',
- 'status-message': 'module scanned',
- description: 'my awesome integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'view details',
- fingerprint: 'bar'
+ name: 'third integration',
+ fingerprint: 'bar',
+ rows: [{
+ link: {
+ url: 'http://www.example.com',
+ text: 'replaced link'
+ }
+ }]
}]
})
setTimeout(function () {
- $('ul li').length.should.equal(1)
- $('ul li').text().should.match(/view details/)
+ $('.addon-container').length.should.equal(1)
+ $('.addon-container').text().should.match(/replaced link/)
poller.stop()
return done()
}, 1000)
@@ -109,12 +165,14 @@ describe('annotation-poller', function () {
url: endpoint,
responseText: [{
id: 'abc-123-abc',
- status: 'warn',
- 'status-message': 'module not yet scanned',
- description: 'my awesome integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'start audit',
- fingerprint: 'foo'
+ name: 'third integration',
+ fingerprint: 'foo',
+ rows: [{
+ link: {
+ url: 'http://www.example.com',
+ text: 'initial link 1'
+ }
+ }]
}]
})
@@ -124,20 +182,22 @@ describe('annotation-poller', function () {
$.mockjax({
url: endpoint,
responseText: [{
- id: 'fed-234-abc',
- status: 'green',
- 'status-message': 'module licensed',
- description: 'my second integration',
- 'external-link': 'http://example.com/foo-package/audit',
- 'external-link-text': 'view details',
- fingerprint: 'foo'
+ id: 'fed-123-abc',
+ name: 'third integration',
+ fingerprint: 'foo',
+ rows: [{
+ link: {
+ url: 'http://www.example.com',
+ text: 'initial link 2'
+ }
+ }]
}]
})
setTimeout(function () {
- $('ul li').length.should.equal(2)
- $('ul li:first').text().should.match(/my awesome integration/)
- $('ul li:last').text().should.match(/my second integration/)
+ $('.addon-container').length.should.equal(2)
+ $('.addon-container:first').text().should.match(/initial link 1/)
+ $('.addon-container:last').text().should.match(/initial link 2/)
poller.stop()
return done()
}, 1000)