Added readme API with markdown support

This commit is contained in:
Brian Peacock 2014-05-07 14:28:10 -05:00
parent 5464b449ce
commit c0ee2db8a4
11 changed files with 414 additions and 68 deletions

View File

@ -1,4 +1,5 @@
@import "../../../node_modules/helpers.less/helpers.less";
@import "./markdown.css";
/*** Main Styles ***/
body {
@ -51,8 +52,8 @@ h1 {
/*** Package Entries ***/
.entry {
background: #EEE;
.border-radius(3px);
padding: 15px;
.border-radius(4px);
padding: 12px 15px;
h3 {
font-size: 24px;

279
lib/GUI/css/markdown.css Normal file
View File

@ -0,0 +1,279 @@
/*** Sourced from this Gist: https://gist.github.com/andyferra/2554919 ***/
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0; }
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
ul :last-child, ol :last-child {
margin-bottom: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }

0
lib/GUI/js/entry.js Normal file
View File

View File

@ -1,49 +1,2 @@
var $ = require('unopinionate').selector,
template = require('../entry.handlebars');
$(function() {
var $form = $('#search-form'),
$input = $form.find('input'),
$searchResults = $("#search-results"),
$body = $('body'),
request;
$form.bind('submit keyup', function(e) {
e.preventDefault();
var q = $input.val();
$body.addClass('state-search');
if(q) {
if(request) {
request.abort();
}
request = $.getJSON('/-/search/' + q, function(results) {
if(results.length) {
var html = '';
$.each(results, function(i, package) {
html += template(package);
});
$searchResults.html(html);
}
else {
$searchResults.html("<div class='search-no-results'>No Results</div>");
}
});
}
else {
$searchResults.html('');
$body.removeClass('state-search');
}
});
$form.find('.clear').click(function(e) {
e.preventDefault();
$input.val('');
$form.keyup();
});
});
require('./search');
require('./entry');

49
lib/GUI/js/search.js Normal file
View File

@ -0,0 +1,49 @@
var $ = require('unopinionate').selector,
template = require('../entry.handlebars');
$(function() {
var $form = $('#search-form'),
$input = $form.find('input'),
$searchResults = $("#search-results"),
$body = $('body'),
request;
$form.bind('submit keyup', function(e) {
e.preventDefault();
var q = $input.val();
$body.addClass('state-search');
if(q) {
if(request) {
request.abort();
}
request = $.getJSON('/-/search/' + q, function(results) {
if(results.length) {
var html = '';
$.each(results, function(i, package) {
html += template(package);
});
$searchResults.html(html);
}
else {
$searchResults.html("<div class='search-no-results'>No Results</div>");
}
});
}
else {
$searchResults.html('');
$body.removeClass('state-search');
}
});
$form.find('.clear').click(function(e) {
e.preventDefault();
$input.val('');
$form.keyup();
});
});

View File

@ -15,7 +15,8 @@ var express = require('express')
, fs = require('fs')
, localList = require('./local-list')
, Search = require('./search')
, _ = require('underscore');
, _ = require('underscore')
, marked = require('marked');
function match(regexp) {
return function(req, res, next, value, name) {
@ -266,6 +267,19 @@ module.exports = function(config_hash) {
}
});
// Readme
marked.setOptions({
highlight: function (code) {
return require('highlight.js').highlightAuto(code).value;
}
});
app.get('/-/readme/:name/:version', function(req, res, next) {
storage.get_readme(req.params.name, req.params.version, function(readme) {
res.send(marked(readme));
});
});
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')

View File

@ -8,7 +8,8 @@ var fs = require('fs')
, mystreams = require('./streams')
, Logger = require('./logger')
, info_file = 'package.json'
, localList = require('./local-list');
, localList = require('./local-list')
, targz = require('tar.gz');
//
// Implements Storage interface
@ -383,6 +384,41 @@ Storage.prototype.add_tarball = function(name, filename) {
return stream
}
Storage.prototype.unpack_tarball = function(file, callback) {
new targz().extract(file + '.tgz', file, callback);
};
Storage.prototype.get_readme = function(name, version, callback) {
var self = this,
fileName = this.storage(name).path + '/' + name + '-' + version;
fs.exists(fileName, function(exists) {
if(exists) {
returnReadme();
}
else {
self.unpack_tarball(fileName, function(err) {
returnReadme();
});
}
});
function returnReadme() {
var readmeFileName = fileName + '/package/README.md';
fs.exists(readmeFileName, function(exists) {
if(exists) {
fs.readFile(readmeFileName, {encoding: "UTF-8"}, function(err, file) {
callback(file);
});
}
else {
callback('');
}
});
}
};
Storage.prototype.get_tarball = function(name, filename, callback) {
assert(utils.validate_name(filename))

View File

@ -1,3 +1,4 @@
@import "markdown.css";
/*** Main Styles ***/
body {
margin: 0;
@ -43,10 +44,10 @@ h1 a:hover {
/*** Package Entries ***/
.entry {
background: #EEE;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
padding: 15px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
padding: 12px 15px;
}
.entry h3 {
font-size: 24px;
@ -68,4 +69,4 @@ h1 a:hover {
.state-search #all-packages {
display: none;
}
/*# sourceMappingURL=data:application/json,%7B%22version%22%3A3%2C%22sources%22%3A%5B%22lib%2FGUI%2Fcss%2Fmain.less%22%2C%22node_modules%2Fhelpers.less%2Fhelpers.less%22%5D%2C%22names%22%3A%5B%5D%2C%22mappings%22%3A%22%3BAAGA%3BEACC%2CSAAA%3BEACA%2CaAAa%2CiBAAiB%2C8CAA9B%3B%3BAAGD%3BEACC%2CkBAAA%3B%3BAADD%2CEAGC%3BAAHD%2CEAGI%2CEAAC%3BEACH%2CYAAA%3BEACA%2CqBAAA%3B%3BAALF%2CEAQC%2CEAAC%3BEACA%2C0BAAA%3B%3BAAIF%3BEACC%2CkBAAA%3B%3BAAGD%3BEACC%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CaAAA%3B%3B%3BAAID%3BEACC%2CmBAAA%3BEACA%2CkBAAA%3BEACA%2CqBAAA%3BEClBG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BEDkBH%2CgBAAA%3BEACA%2CWAAA%3B%3BAAND%2CMAQC%3BEACC%2CwCAAA%3B%3B%3BAAKF%3BEACC%2CYAAA%3B%3B%3BAAID%3BEACC%2CgBAAA%3BECnCG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BEDmCH%2CaAAA%3B%3BAAHD%2CMAKC%3BEACC%2CeAAA%3BEACA%2CgBAAA%3B%3BAAPF%2CMAUC%3BEACC%2CeAAA%3BEACA%2CWAAA%3B%3BAAZF%2CMAeC%3BEACC%2CeAAA%3BEACA%2CYAAA%3BEACA%2CWAAA%3B%3BAAlBF%2CMAqBC%3BEACC%2CSAAA%3B%3B%3BAAKF%2CaAAc%3BEACb%2CaAAA%22%7D */
/*# sourceMappingURL=data:application/json,%7B%22version%22%3A3%2C%22sources%22%3A%5B%22lib%2FGUI%2Fcss%2Fmain.less%22%2C%22node_modules%2Fhelpers.less%2Fhelpers.less%22%5D%2C%22names%22%3A%5B%5D%2C%22mappings%22%3A%22QACQ%3B%3BAAGR%3BEACC%2CSAAA%3BEACA%2CaAAa%2CiBAAiB%2C8CAA9B%3B%3BAAGD%3BEACC%2CkBAAA%3B%3BAADD%2CEAGC%3BAAHD%2CEAGI%2CEAAC%3BEACH%2CYAAA%3BEACA%2CqBAAA%3B%3BAALF%2CEAQC%2CEAAC%3BEACA%2C0BAAA%3B%3BAAIF%3BEACC%2CkBAAA%3B%3BAAGD%3BEACC%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CaAAA%3B%3B%3BAAID%3BEACC%2CmBAAA%3BEACA%2CkBAAA%3BEACA%2CqBAAA%3BECnBG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BEDmBH%2CgBAAA%3BEACA%2CWAAA%3B%3BAAND%2CMAQC%3BEACC%2CwCAAA%3B%3B%3BAAKF%3BEACC%2CYAAA%3B%3B%3BAAID%3BEACC%2CgBAAA%3BECpCG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BEDoCH%2CkBAAA%3B%3BAAHD%2CMAKC%3BEACC%2CeAAA%3BEACA%2CgBAAA%3B%3BAAPF%2CMAUC%3BEACC%2CeAAA%3BEACA%2CWAAA%3B%3BAAZF%2CMAeC%3BEACC%2CeAAA%3BEACA%2CYAAA%3BEACA%2CWAAA%3B%3BAAlBF%2CMAqBC%3BEACC%2CSAAA%3B%3B%3BAAKF%2CaAAc%3BEACb%2CaAAA%22%7D */

View File

@ -22,7 +22,13 @@ helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ "</p>\n</article>";
return buffer;
});
},{"handlebars/runtime":9}],2:[function(require,module,exports){
},{"handlebars/runtime":11}],2:[function(require,module,exports){
},{}],3:[function(require,module,exports){
require('./search');
require('./entry');
},{"./entry":2,"./search":4}],4:[function(require,module,exports){
var $ = require('unopinionate').selector,
template = require('../entry.handlebars');
@ -73,7 +79,7 @@ $(function() {
});
});
},{"../entry.handlebars":1,"unopinionate":10}],3:[function(require,module,exports){
},{"../entry.handlebars":1,"unopinionate":12}],5:[function(require,module,exports){
"use strict";
/*globals Handlebars: true */
var base = require("./handlebars/base");
@ -106,7 +112,7 @@ var Handlebars = create();
Handlebars.create = create;
exports["default"] = Handlebars;
},{"./handlebars/base":4,"./handlebars/exception":5,"./handlebars/runtime":6,"./handlebars/safe-string":7,"./handlebars/utils":8}],4:[function(require,module,exports){
},{"./handlebars/base":6,"./handlebars/exception":7,"./handlebars/runtime":8,"./handlebars/safe-string":9,"./handlebars/utils":10}],6:[function(require,module,exports){
"use strict";
var Utils = require("./utils");
var Exception = require("./exception")["default"];
@ -287,7 +293,7 @@ exports.log = log;var createFrame = function(object) {
return obj;
};
exports.createFrame = createFrame;
},{"./exception":5,"./utils":8}],5:[function(require,module,exports){
},{"./exception":7,"./utils":10}],7:[function(require,module,exports){
"use strict";
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
@ -316,7 +322,7 @@ function Exception(message, node) {
Exception.prototype = new Error();
exports["default"] = Exception;
},{}],6:[function(require,module,exports){
},{}],8:[function(require,module,exports){
"use strict";
var Utils = require("./utils");
var Exception = require("./exception")["default"];
@ -454,7 +460,7 @@ exports.program = program;function invokePartial(partial, name, context, helpers
exports.invokePartial = invokePartial;function noop() { return ""; }
exports.noop = noop;
},{"./base":4,"./exception":5,"./utils":8}],7:[function(require,module,exports){
},{"./base":6,"./exception":7,"./utils":10}],9:[function(require,module,exports){
"use strict";
// Build out our basic SafeString type
function SafeString(string) {
@ -466,7 +472,7 @@ SafeString.prototype.toString = function() {
};
exports["default"] = SafeString;
},{}],8:[function(require,module,exports){
},{}],10:[function(require,module,exports){
"use strict";
/*jshint -W004 */
var SafeString = require("./safe-string")["default"];
@ -543,12 +549,12 @@ exports.escapeExpression = escapeExpression;function isEmpty(value) {
}
exports.isEmpty = isEmpty;
},{"./safe-string":7}],9:[function(require,module,exports){
},{"./safe-string":9}],11:[function(require,module,exports){
// Create a simple path alias to allow browserify to resolve
// the runtime on a supported path.
module.exports = require('./dist/cjs/handlebars.runtime');
},{"./dist/cjs/handlebars.runtime":3}],10:[function(require,module,exports){
},{"./dist/cjs/handlebars.runtime":5}],12:[function(require,module,exports){
(function (global){
(function(root) {
var unopinionate = {
@ -575,4 +581,4 @@ module.exports = require('./dist/cjs/handlebars.runtime');
})(typeof window != 'undefined' ? window : global);
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[2])
},{}]},{},[3])

View File

@ -174,6 +174,10 @@ Storage.prototype.add_tarball = function(name, filename) {
return this.local.add_tarball(name, filename)
}
Storage.prototype.get_readme = function(name, version, callback) {
return this.local.get_readme(name, version, callback);
};
//
// Get a tarball from a storage for {name} package
//

View File

@ -22,12 +22,15 @@
"express": "3.4.x",
"handlebars": "1.x.x",
"helpers.less": "git://github.com/bpeacock/helpers.less.git",
"highlight.js": "^8.0.0",
"js-yaml": ">= 3.0.1",
"lunr": "^0.5.2",
"marked": "^0.3.2",
"minimatch": ">= 0.2.14",
"mkdirp": ">= 0.3.5",
"request": ">= 2.31.0",
"semver": ">= 2.2.1",
"tar.gz": "^0.1.1",
"underscore": "^1.6.0",
"unopinionate": "0.0.4"
},