From 7e4b72326c17a136dc49a2dabcdec09cfadc4290 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Wed, 20 Apr 2016 15:32:41 -0700 Subject: [PATCH] feat: implement new DSL for annotations (#5) BREAKING CHANGE: DSL for annotations is now different --- index.js | 88 ++++++++++++++++++++++++++++-- package.json | 6 +-- test.js | 148 ++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 191 insertions(+), 51 deletions(-) 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( + '
  • ' + + '' + + '
  • ') 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)