diff --git a/.gitignore b/.gitignore index a05fec011..c17bb7d0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ node_modules -package.json npm-debug.log sinopia-*.tgz +.DS_Store ### bin/storage* bin/htpasswd bin/*.yaml test-storage* + +example diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..7dd004dc4 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,39 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + browserify: { + dist: { + files: { + 'lib/static/main.js': ['lib/GUI/js/main.js'] + }, + options: { + debug: true, + transform: ['browserify-handlebars'] + } + } + }, + less: { + dist: { + files: { + 'lib/static/main.css': ['lib/GUI/css/main.less'] + }, + options: { + sourceMap: true + } + } + }, + watch: { + files: [ "lib/GUI/**/*"], + tasks: [ 'default' ] + } + }); + + grunt.loadNpmTasks('grunt-browserify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-less'); + + grunt.registerTask('default', [ + 'browserify', + 'less' + ]); +}; \ No newline at end of file diff --git a/README.md b/README.md index f916563ea..d2a871e06 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ $ npm set always-auth true $ npm set ca null ``` +Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where your local packages will be listed and can be searched. + ### Docker A Sinopia docker image [is available](https://index.docker.io/u/keyvanfatehi/docker-sinopia/) @@ -58,20 +60,15 @@ A Sinopia puppet module [is available at puppet forge](http://forge.puppetlabs.c ## Configuration -When you start a server, it auto-creates a config file that adds one user (password is printed to stdout only once). +When you start a server, it auto-creates a config file. ## Adding a new user -There is no utility to add a new user but you can at least use node on the command-line to generate a password. You will need to edit the config and add the user manually. - -Start node and enter the following code replacing 'newpass' with the password you want to get the hash for. ```bash -$ node -> crypto.createHash('sha1').update('newpass').digest('hex') -'6c55803d6f1d7a177a0db3eb4b343b0d50f9c111' -> [CTRL-D] +npm adduser --registry http://localhost:4873/ ``` +This will prompt you for user credentials which will be saved on the Sinopia server. ## Using private packages @@ -112,18 +109,18 @@ Basic features: Advanced package control: -- Unpublishing packages (npm unpublish) - not yet supported, should be soon +- Unpublishing packages (npm unpublish) - supported - Tagging (npm tag) - not yet supported, should be soon - Deprecation (npm deprecate) - not supported User management: -- Registering new users (npm adduser {newuser}) - not supported, sinopia uses its own acl management system +- Registering new users (npm adduser {newuser}) - supported - Transferring ownership (npm owner add {user} {pkg}) - not supported, sinopia uses its own acl management system Misc stuff: -- Searching (npm search) - not supported +- Searching (npm search) - supported in the browser client but not command line - Starring (npm star, npm unstar) - not supported, doesn't make sense in private registry ## Storage diff --git a/lib/GUI/css/fontello.less b/lib/GUI/css/fontello.less new file mode 100644 index 000000000..22ea25c33 --- /dev/null +++ b/lib/GUI/css/fontello.less @@ -0,0 +1,56 @@ +@font-face { + font-family: 'fontello'; + src: url('../static/fontello.eot?10872183'); + src: url('../static/fontello.eot?10872183#iefix') format('embedded-opentype'), + url('../static/fontello.woff?10872183') format('woff'), + url('../static/fontello.ttf?10872183') format('truetype'), + url('../static/fontello.svg?10872183#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'fontello'; + src: url('../font/fontello.svg?10872183#fontello') format('svg'); + } +} +*/ + + [class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +} + +.icon-search:before { content: '\e801'; } /* '' */ +.icon-cancel:before { content: '\e803'; } /* '' */ +.icon-right-open:before { content: '\e802'; } /* '' */ +.icon-angle-right:before { content: '\e800'; } /* '' */ \ No newline at end of file diff --git a/lib/GUI/css/highlight.js.less b/lib/GUI/css/highlight.js.less new file mode 100644 index 000000000..3d8485b48 --- /dev/null +++ b/lib/GUI/css/highlight.js.less @@ -0,0 +1,153 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; padding: 0.5em; + background: #F0F0F0; +} + +.hljs, +.hljs-subst, +.hljs-tag .hljs-title, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-constant, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-rules .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.haml .hljs-symbol, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-aggregate, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.tex .hljs-special, +.erlang_repl .hljs-function_or_atom, +.asciidoc .hljs-header, +.markdown .hljs-header, +.coffeescript .hljs-attribute { + color: #800; +} + +.smartquote, +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk, +.asciidoc .hljs-blockquote, +.markdown .hljs-blockquote { + color: #888; +} + +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-hexcolor, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.go .hljs-constant, +.hljs-change, +.lasso .hljs-variable, +.makefile .hljs-variable, +.asciidoc .hljs-bullet, +.markdown .hljs-bullet, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + color: #080; +} + +.hljs-label, +.hljs-javadoc, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-important, +.hljs-pseudo, +.hljs-pi, +.haml .hljs-bullet, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.erlang_repl .hljs-reserved, +.hljs-prompt, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label, +.vhdl .hljs-attribute, +.clojure .hljs-attribute, +.asciidoc .hljs-attribute, +.lasso .hljs-attribute, +.coffeescript .hljs-property, +.hljs-phony { + color: #88F +} + +.hljs-keyword, +.hljs-id, +.hljs-title, +.hljs-built_in, +.hljs-aggregate, +.css .hljs-tag, +.hljs-javadoctag, +.hljs-phpdoc, +.hljs-yardoctag, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.go .hljs-typename, +.tex .hljs-command, +.asciidoc .hljs-strong, +.markdown .hljs-strong, +.hljs-request, +.hljs-status { + font-weight: bold; +} + +.asciidoc .hljs-emphasis, +.markdown .hljs-emphasis { + font-style: italic; +} + +.nginx .hljs-built_in { + font-weight: normal; +} + +.coffeescript .javascript, +.javascript .xml, +.lasso .markup, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} diff --git a/lib/GUI/css/main.less b/lib/GUI/css/main.less new file mode 100644 index 000000000..ce1f3acca --- /dev/null +++ b/lib/GUI/css/main.less @@ -0,0 +1,200 @@ +@import "../../../node_modules/helpers.less/helpers.less"; +@import "./markdown.less"; +@import "./highlight.js.less"; +@import "./fontello.less"; + +/*** Main Styles ***/ +body { + margin: 0; + font-family: "Lucida Grande", "Helvetica Neue", Helvetica, Arial, Sans-Serif; +} + +a, a:visited { + text-decoration: none; + color: #0D5AFF; +} + +a:hover { + text-decoration: underline; +} + +.center { + text-align: center; +} + +@contentWidth: 880px; +@headerPadding: 10px; +header { + position: fixed; + width: 100%; + background: #FFF; + top: 0; + z-index: 1; + + #header-inner { + max-width: @contentWidth + @headerPadding*2; + margin: 0 auto; + } +} + +#content { + max-width: @contentWidth; + margin: 0 auto; + padding: 20px; +} + +#logo { + margin: 20px auto 0; + width: 400px; + height: 200px; + display: block; +} + +h1 { + text-align: center; + + a, a:visited { + color: black; + } +} + +/*** Setup ***/ +#setup { + background: #DB4141; + padding: 15px 20px; + display: inline-block; + .border-radius(4px); + text-align: left; + color: #FFF; + margin-top: 20px; + + code { + font-family: Consolas, monaco, monospace; + } +} + +/*** Search Box ***/ +#search-form { + float: right; + + @media (max-width: 540px) { + float: none; + margin-top: 6px; + } + + @height: 30px; + + input, button { + margin: 0; + vertical-align: top; + border: 1px solid #CCC; + + &:focus { + outline: none; + } + } + + input { + width: 200px; + height: @height; + .border-box; + padding: 0 5px; + font-size: 16px; + border-right: 0; + } + + button { + height: @height; + width: @height; + margin: 0; + border-left: 0; + background: #FFF; + cursor: pointer; + font-size: 16px; + color: #999; + } +} + +/*** Heading ***/ +h2 { + border-bottom: 6px solid #424242; + margin: 40px 0 0; + padding: 0 @headerPadding 10px; +} + +/*** Package Entries ***/ +.entry { + background: #F3F3F3; + .border-radius(4px); + padding: 12px 15px 15px; + .transition(height .3s); + overflow: hidden; + margin-bottom: 12px; + + h3 { + font-size: 24px; + margin: 0 0 10px; + } + + .name:hover { + text-decoration: none; + } + + .name:before { + margin: 0; + margin-left: -10px; + .transformTransition(.2s); + } + + &.open .name:before { + .rotate(90deg); + } + + .version { + font-size: 16px; + color: #666; + } + + .author { + font-size: 16px; + float: right; + color: #666; + } + + p { + margin: 0; + } + + .readme { + font-size: 14px; + margin-top: 10px; + background: #FFF; + padding: 10px 12px; + .border-radius(3px); + } +} + +/*** Search Results ***/ +.state-search #all-packages { + display: none; +} + +.search-ajax { + display: block; + margin: 50px auto; +} + +.no-results { + text-align: center; + margin: 50px 0; + color: #888; + + big { + font-size: 38px; + margin-bottom: 8px; + } + + code { + font-size: 1.2em; + } +} \ No newline at end of file diff --git a/lib/GUI/css/markdown.less b/lib/GUI/css/markdown.less new file mode 100644 index 000000000..a62cb55bb --- /dev/null +++ b/lib/GUI/css/markdown.less @@ -0,0 +1,268 @@ +/*** Sourced from this Gist: https://gist.github.com/andyferra/2554919 ***/ + +.readme { + 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 { + margin: 10px 0; + 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; } +} \ No newline at end of file diff --git a/lib/GUI/entry.hbs b/lib/GUI/entry.hbs new file mode 100644 index 000000000..14ce6c21f --- /dev/null +++ b/lib/GUI/entry.hbs @@ -0,0 +1,8 @@ +
+

+ {{ name }} + v{{ version }} +
By: {{ _npmUser.name }}
+

+

{{ description }}

+
\ No newline at end of file diff --git a/lib/GUI/index.hbs b/lib/GUI/index.hbs new file mode 100644 index 000000000..ff47af2f0 --- /dev/null +++ b/lib/GUI/index.hbs @@ -0,0 +1,52 @@ + + + + + {{ name }} + + + + + + +
+
+ + +
+
+ npm set registry {{ baseUrl }}
+ npm adduser --registry {{ baseUrl }} +
+
+ +

+ Available Packages: +
+ +
+

+
+
+ +
+
+ +
+ {{#each packages}} + {{> entry}} + {{/each}} + + {{#unless packages.length}} +
+ No Packages
+ Use npm publish +
+ {{/unless}} +
+
+ + + + + diff --git a/lib/GUI/js/entry.js b/lib/GUI/js/entry.js new file mode 100644 index 000000000..3760eb73f --- /dev/null +++ b/lib/GUI/js/entry.js @@ -0,0 +1,71 @@ +var $ = require('unopinionate').selector, + onClick = require('onclick'), + transitionComplete = require('transition-complete'); + +$(function() { + onClick('.entry .name', function() { + var $this = $(this), + $entry = $this.closest('.entry'); + + //Close entry + if($entry.hasClass('open')) { + $entry + .height($entry.height()) + .removeClass('open'); + + setTimeout(function() { + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); + + transitionComplete(function() { + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); + } + //Open entry + else { + //Close open entries + $('.entry.open').each(function() { + var $entry = $(this); + $entry + .height($entry.height()) + .removeClass('open'); + + setTimeout(function() { + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); + + transitionComplete(function() { + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); + }); + + //Add the open class + $entry.addClass('open'); + + //Explicitly set heights for transitions + var height = $entry.height(); + $entry + .attr('data-height', height) + .css('height', height); + + //Get the data + $.ajax({ + url: '/-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'), + dataType: 'text', + success: function(html) { + var $readme = $("
") + .html(html) + .appendTo($entry); + + $entry.height(height + $readme.outerHeight()); + + transitionComplete(function() { + $entry.css('height', 'auto'); + }); + } + }); + } + }); +}); \ No newline at end of file diff --git a/lib/GUI/js/header.js b/lib/GUI/js/header.js new file mode 100644 index 000000000..d9f6eea05 --- /dev/null +++ b/lib/GUI/js/header.js @@ -0,0 +1,29 @@ +var $ = require('unopinionate').selector, + onScroll = require('onscroll'); + +$(function() { + var $header = $('header'), + $content = $('#content'), + bottomOffset = 52; + + var scrollFunc = function(top) { + var limit = $header.outerHeight() - bottomOffset; + + if(top < 0) { + $header.css('top', 0); + } + else if(top > limit) { + $header.css('top', -limit + 'px'); + } + else { + $header.css('top', -top + 'px'); + } + }; + + onScroll(scrollFunc); + scrollFunc(); + + $(window).resize(function() { + $content.css('margin-top', $header.outerHeight()); + }).resize(); +}); \ No newline at end of file diff --git a/lib/GUI/js/main.js b/lib/GUI/js/main.js new file mode 100644 index 000000000..e32370936 --- /dev/null +++ b/lib/GUI/js/main.js @@ -0,0 +1,3 @@ +require('./search'); +require('./entry'); +require('./header'); \ No newline at end of file diff --git a/lib/GUI/js/search.js b/lib/GUI/js/search.js new file mode 100644 index 000000000..19222140f --- /dev/null +++ b/lib/GUI/js/search.js @@ -0,0 +1,70 @@ +var $ = require('unopinionate').selector, + template = require('../entry.hbs'), + onScroll = require('onscroll'); + +$(function() { + 'use strict'; + + var $form = $('#search-form'), + $input = $form.find('input'), + $searchResults = $("#search-results"), + $body = $('body'), + $clear = $form.find('.clear'), + request, + currentResults; + + $form.bind('submit keyup', function(e) { + e.preventDefault(); + + var q = $input.val(); + + $body.addClass('state-search'); + + //Switch the icons + $clear + [q ? 'addClass' : 'removeClass']('icon-cancel') + [!q ? 'addClass' : 'removeClass']('icon-search'); + + if(q) { + if(request) { + request.abort(); + } + + if(!currentResults) { + $searchResults.html("Spinner"); + } + + request = $.getJSON('/-/search/' + q, function(results) { + currentResults = results; + + if(results.length) { + var html = ''; + + $.each(results, function(i, entry) { + html += template(entry); + }); + + $searchResults.html(html); + } + else { + $searchResults.html("
No Results
"); + } + }); + } + else { + request.abort(); + currentResults = null; + $searchResults.html(''); + $body.removeClass('state-search'); + } + }); + + $clear.click(function(e) { + e.preventDefault(); + $input.val(''); + $form.keyup(); + }); + + + +}); diff --git a/lib/config.js b/lib/config.js index a634e20c0..55d0de039 100644 --- a/lib/config.js +++ b/lib/config.js @@ -4,6 +4,7 @@ var assert = require('assert') , minimatch = require('minimatch') , UError = require('./error').UserError , utils = require('./utils') + , users = require('./users') // [[a, [b, c]], d] -> [a, b, c, d] function flatten(array) { diff --git a/lib/config_def.yaml b/lib/config_def.yaml index 3204dff78..308498398 100644 --- a/lib/config_def.yaml +++ b/lib/config_def.yaml @@ -9,6 +9,9 @@ users: # crypto.createHash('sha1').update(pass).digest('hex') password: __PASSWORD__ +title: Sinopia +# logo: logo.png + users_file: ./htpasswd # Maximum amount of users allowed to register, defaults to "+inf". diff --git a/lib/index.js b/lib/index.js index 4ee6fd3b6..689969e95 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,6 +11,13 @@ var express = require('express') , validate_name = Middleware.validate_name , media = Middleware.media , expect_json = Middleware.expect_json + , Handlebars = require('handlebars') + , fs = require('fs') + , localList = require('./local-list') + , search = require('./search') + , _ = require('underscore') + , users = require('./users') + , marked = require('marked'); function match(regexp) { return function(req, res, next, value, name) { @@ -24,7 +31,9 @@ function match(regexp) { module.exports = function(config_hash) { var config = new Config(config_hash) - , storage = new Storage(config) + , storage = new Storage(config); + + search.configureStorage(storage); var can = function(action) { return function(req, res, next) { @@ -109,13 +118,7 @@ module.exports = function(config_hash) { app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)) app.param('anything', match(/.*/)) -/* app.get('/', function(req, res) { - res.send({ - error: 'unimplemented' - }) - })*/ - -/* app.get('/-/all', function(req, res) { +/* app.get('/-/all', function(req, res) { var https = require('https') var JSONStream = require('JSONStream') var request = require('request')({ @@ -126,6 +129,21 @@ module.exports = function(config_hash) { console.log(d) }) })*/ + + Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8')); + var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8')); + + app.get('/', can('access'), function(req, res, next) { + res.setHeader('Content-Type', 'text/html'); + + storage.get_local(function(err, packages) { + res.send(template({ + name: config.title || "Sinopia", + packages: packages, + baseUrl: config.url_prefix || req.protocol + '://' + req.get('host') + '/' + })); + }); + }); // TODO: anonymous user? app.get('/:package/:version?', can('access'), function(req, res, next) { @@ -185,7 +203,7 @@ module.exports = function(config_hash) { }) //app.get('/*', function(req, res) { - // proxy.request(req, res) + // proxy.request(req, res) //}) // placeholder 'cause npm require to be authenticated to publish @@ -239,6 +257,65 @@ module.exports = function(config_hash) { } }) + // Static + app.get('/-/static/:file', function(req, res, next) { + var file = __dirname + '/static/' + req.params.file; + fs.exists(file, function(exists) { + if(exists) { + res.sendfile(file); + } + else { + res.status(404); + res.send("File Not Found"); + } + }); + }); + + app.get('/-/logo', function(req, res, next) { + res.sendfile(config.logo ? config.logo : __dirname + "/static/logo.png"); + }); + + // Search + app.get('/-/search/:query', function(req, res, next) { + var results = search.query(req.params.query), + packages = []; + + var getData = function(i) { + storage.get_package(results[i].ref, function(err, entry) { + if(entry) { + packages.push(entry.versions[entry['dist-tags'].latest]); + } + + if(i >= results.length - 1) { + res.send(packages); + } + else { + getData(i + 1); + } + }); + }; + + if(results.length) { + getData(0); + } + else { + res.send([]); + } + }); + + // 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') diff --git a/lib/local-list.js b/lib/local-list.js new file mode 100644 index 000000000..64d5bbd04 --- /dev/null +++ b/lib/local-list.js @@ -0,0 +1,36 @@ +var fs = require('fs') + , listFilePath = './local-list.json'; + +var LocalList = function() { + if(fs.existsSync(listFilePath)) { + this.list = JSON.parse(fs.readFileSync(listFilePath, 'utf8')); + } + else { + this.list = []; + } +}; + +LocalList.prototype = { + add: function(name) { + if(this.list.indexOf(name) == -1) { + this.list.push(name); + this.sync(); + } + }, + remove: function(name) { + var i = this.list.indexOf(name); + if(i != -1) { + this.list.splice(i, 1); + } + + this.sync(); + }, + get: function() { + return this.list; + }, + sync: function() { + fs.writeFileSync(listFilePath, JSON.stringify(this.list)); //Uses sync to prevent ugly race condition + } +}; + +module.exports = new LocalList(); diff --git a/lib/local-storage.js b/lib/local-storage.js index 92b6fe095..ec2fc1527 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -8,6 +8,9 @@ var fs = require('fs') , mystreams = require('./streams') , Logger = require('./logger') , info_file = 'package.json' + , localList = require('./local-list') + , targz = require('tar.gz') + , search = require('./search'); // // Implements Storage interface @@ -45,7 +48,7 @@ Storage.prototype._internal_error = function(err, file, message) { }) } -Storage.prototype.add_package = function(name, metadata, callback) { +Storage.prototype.add_package = function(name, info, callback) { this.storage(name).create_json(info_file, get_boilerplate(name), function(err) { if (err && err.code === 'EEXISTS') { return callback(new UError({ @@ -53,6 +56,10 @@ Storage.prototype.add_package = function(name, metadata, callback) { message: 'this package is already present' })) } + + search.add(info.versions[info['dist-tags'].latest]); + localList.add(name); + callback() }) } @@ -91,10 +98,13 @@ Storage.prototype.remove_package = function(name, callback) { // try to unlink the directory, but ignore errors because it can fail self.storage(name).rmdir('.', function(err) { callback(err) - }) - }) - }) - }) + }); + }); + }); + }); + + search.remove(name); + localList.remove(name); } Storage.prototype._read_create_package = function(name, callback) { @@ -378,6 +388,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)) diff --git a/lib/middleware.js b/lib/middleware.js index 4789c5e91..088db2f7b 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -136,7 +136,9 @@ module.exports.log_and_etagify = function(req, res, next) { res.send = function(body) { try { if (typeof(body) === 'string' || typeof(body) === 'object') { - res.header('Content-type', 'application/json') + if (!res.getHeader('Content-type')) { + res.header('Content-type', 'application/json') + } if (typeof(body) === 'object' && body != null) { if (typeof(body.error) === 'string') { diff --git a/lib/search.js b/lib/search.js new file mode 100644 index 000000000..b142971f1 --- /dev/null +++ b/lib/search.js @@ -0,0 +1,46 @@ +var lunr = require('lunr') + , localList = require('./local-list'); + +var Search = function() { + this.index = lunr(function () { + this.field('name', {boost: 10}); + this.field('description', {boost: 4}); + this.field('author', {boost: 6}); + this.field('readme'); + }); +}; + +Search.prototype = { + query: function(q) { + return this.index.search(q); + }, + add: function(package) { + this.index.add({ + id: package.name, + name: package.name, + description: package.description, + author: package._npmUser.name + }); + }, + remove: function(name) { + this.index.remove({ + id: name + }); + }, + reindex: function() { + var self = this; + this.storage.get_local(function(err, packages) { + var i = packages.length; + + while(i--) { + self.add(packages[i]); + } + }); + }, + configureStorage: function(storage) { + this.storage = storage; + this.reindex(); + } +}; + +module.exports = new Search(); \ No newline at end of file diff --git a/lib/static/ajax.gif b/lib/static/ajax.gif new file mode 100644 index 000000000..7f88e6b9f Binary files /dev/null and b/lib/static/ajax.gif differ diff --git a/lib/static/favicon.ico b/lib/static/favicon.ico new file mode 100644 index 000000000..9e0d4eef7 Binary files /dev/null and b/lib/static/favicon.ico differ diff --git a/lib/static/fontello.eot b/lib/static/fontello.eot new file mode 100644 index 000000000..577832808 Binary files /dev/null and b/lib/static/fontello.eot differ diff --git a/lib/static/fontello.svg b/lib/static/fontello.svg new file mode 100644 index 000000000..fba61911a --- /dev/null +++ b/lib/static/fontello.svg @@ -0,0 +1,15 @@ + + + +Copyright (C) 2014 by original authors @ fontello.com + + + + + + + + + + + \ No newline at end of file diff --git a/lib/static/fontello.ttf b/lib/static/fontello.ttf new file mode 100644 index 000000000..09ca6acc8 Binary files /dev/null and b/lib/static/fontello.ttf differ diff --git a/lib/static/fontello.woff b/lib/static/fontello.woff new file mode 100644 index 000000000..aab5fb6b2 Binary files /dev/null and b/lib/static/fontello.woff differ diff --git a/lib/static/logo.png b/lib/static/logo.png new file mode 100644 index 000000000..c26b6c0ff Binary files /dev/null and b/lib/static/logo.png differ diff --git a/lib/static/main.css b/lib/static/main.css new file mode 100644 index 000000000..47eb45770 --- /dev/null +++ b/lib/static/main.css @@ -0,0 +1,731 @@ +/*** Sourced from this Gist: https://gist.github.com/andyferra/2554919 ***/ +.readme a { + color: #4183C4; +} +.readme a.absent { + color: #cc0000; +} +.readme a.anchor { + display: block; + padding-left: 30px; + margin-left: -30px; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + bottom: 0; +} +.readme h1, +.readme h2, +.readme h3, +.readme h4, +.readme h5, +.readme h6 { + margin: 20px 0 10px; + padding: 0; + font-weight: bold; + -webkit-font-smoothing: antialiased; + cursor: text; + position: relative; +} +.readme h1:hover a.anchor, +.readme h2:hover a.anchor, +.readme h3:hover a.anchor, +.readme h4:hover a.anchor, +.readme h5:hover a.anchor, +.readme h6:hover a.anchor { + background: url("../../images/modules/styleguide/para.png") no-repeat 10px center; + text-decoration: none; +} +.readme h1 tt, +.readme h1 code { + font-size: inherit; +} +.readme h2 tt, +.readme h2 code { + font-size: inherit; +} +.readme h3 tt, +.readme h3 code { + font-size: inherit; +} +.readme h4 tt, +.readme h4 code { + font-size: inherit; +} +.readme h5 tt, +.readme h5 code { + font-size: inherit; +} +.readme h6 tt, +.readme h6 code { + font-size: inherit; +} +.readme h1 { + font-size: 28px; + color: black; +} +.readme h2 { + font-size: 24px; + border-bottom: 1px solid #cccccc; + color: black; +} +.readme h3 { + font-size: 18px; +} +.readme h4 { + font-size: 16px; +} +.readme h5 { + font-size: 14px; +} +.readme h6 { + color: #777777; + font-size: 14px; +} +.readme p, +.readme blockquote, +.readme ul, +.readme ol, +.readme dl, +.readme li, +.readme table, +.readme pre { + margin: 15px 0; +} +.readme hr { + background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0; + border: 0 none; + color: #cccccc; + height: 4px; + padding: 0; +} +.readme body > h2:first-child { + margin-top: 0; + padding-top: 0; +} +.readme body > h1:first-child { + margin-top: 0; + padding-top: 0; +} +.readme body > h1:first-child + h2 { + margin-top: 0; + padding-top: 0; +} +.readme body > h3:first-child, +.readme body > h4:first-child, +.readme body > h5:first-child, +.readme body > h6:first-child { + margin-top: 0; + padding-top: 0; +} +.readme a:first-child h1, +.readme a:first-child h2, +.readme a:first-child h3, +.readme a:first-child h4, +.readme a:first-child h5, +.readme a:first-child h6 { + margin-top: 0; + padding-top: 0; +} +.readme h1 p, +.readme h2 p, +.readme h3 p, +.readme h4 p, +.readme h5 p, +.readme h6 p { + margin-top: 0; +} +.readme li p.first { + display: inline-block; +} +.readme ul, +.readme ol { + padding-left: 30px; +} +.readme ul :first-child, +.readme ol :first-child { + margin-top: 0; +} +.readme ul :last-child, +.readme ol :last-child { + margin-bottom: 0; +} +.readme dl { + padding: 0; +} +.readme dl dt { + font-size: 14px; + font-weight: bold; + font-style: italic; + padding: 0; + margin: 15px 0 5px; +} +.readme dl dt:first-child { + padding: 0; +} +.readme dl dt > :first-child { + margin-top: 0; +} +.readme dl dt > :last-child { + margin-bottom: 0; +} +.readme dl dd { + margin: 0 0 15px; + padding: 0 15px; +} +.readme dl dd > :first-child { + margin-top: 0; +} +.readme dl dd > :last-child { + margin-bottom: 0; +} +.readme blockquote { + border-left: 4px solid #dddddd; + padding: 0 15px; + color: #777777; +} +.readme blockquote > :first-child { + margin-top: 0; +} +.readme blockquote > :last-child { + margin-bottom: 0; +} +.readme table { + padding: 0; +} +.readme table tr { + border-top: 1px solid #cccccc; + background-color: white; + margin: 0; + padding: 0; +} +.readme table tr:nth-child(2n) { + background-color: #f8f8f8; +} +.readme table tr th { + font-weight: bold; + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; +} +.readme table tr td { + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; +} +.readme table tr th :first-child, +.readme table tr td :first-child { + margin-top: 0; +} +.readme table tr th :last-child, +.readme table tr td :last-child { + margin-bottom: 0; +} +.readme img { + margin: 10px 0; + max-width: 100%; +} +.readme span.frame { + display: block; + overflow: hidden; +} +.readme span.frame > span { + border: 1px solid #dddddd; + display: block; + float: left; + overflow: hidden; + margin: 13px 0 0; + padding: 7px; + width: auto; +} +.readme span.frame span img { + display: block; + float: left; +} +.readme span.frame span span { + clear: both; + color: #333333; + display: block; + padding: 5px 0 0; +} +.readme span.align-center { + display: block; + overflow: hidden; + clear: both; +} +.readme span.align-center > span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: center; +} +.readme span.align-center span img { + margin: 0 auto; + text-align: center; +} +.readme span.align-right { + display: block; + overflow: hidden; + clear: both; +} +.readme span.align-right > span { + display: block; + overflow: hidden; + margin: 13px 0 0; + text-align: right; +} +.readme span.align-right span img { + margin: 0; + text-align: right; +} +.readme span.float-left { + display: block; + margin-right: 13px; + overflow: hidden; + float: left; +} +.readme span.float-left span { + margin: 13px 0 0; +} +.readme span.float-right { + display: block; + margin-left: 13px; + overflow: hidden; + float: right; +} +.readme span.float-right > span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: right; +} +.readme code, +.readme tt { + margin: 0 2px; + padding: 0 5px; + white-space: nowrap; + border: 1px solid #eaeaea; + background-color: #f8f8f8; + border-radius: 3px; +} +.readme pre code { + margin: 0; + padding: 0; + white-space: pre; + border: none; + background: transparent; +} +.readme .highlight pre { + background-color: #f8f8f8; + border: 1px solid #cccccc; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; + border-radius: 3px; +} +.readme pre { + background-color: #f8f8f8; + border: 1px solid #cccccc; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; + border-radius: 3px; +} +.readme pre code, +.readme pre tt { + background-color: transparent; + border: none; +} +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.hljs, +.hljs-subst, +.hljs-tag .hljs-title, +.lisp .hljs-title, +.clojure .hljs-built_in, +.nginx .hljs-title { + color: black; +} +.hljs-string, +.hljs-title, +.hljs-constant, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value, +.hljs-rules .hljs-value .hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.haml .hljs-symbol, +.ruby .hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.hljs-aggregate, +.hljs-template_tag, +.django .hljs-variable, +.smalltalk .hljs-class, +.hljs-addition, +.hljs-flow, +.hljs-stream, +.bash .hljs-variable, +.apache .hljs-tag, +.apache .hljs-cbracket, +.tex .hljs-command, +.tex .hljs-special, +.erlang_repl .hljs-function_or_atom, +.asciidoc .hljs-header, +.markdown .hljs-header, +.coffeescript .hljs-attribute { + color: #800; +} +.smartquote, +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk, +.asciidoc .hljs-blockquote, +.markdown .hljs-blockquote { + color: #888; +} +.hljs-number, +.hljs-date, +.hljs-regexp, +.hljs-literal, +.hljs-hexcolor, +.smalltalk .hljs-symbol, +.smalltalk .hljs-char, +.go .hljs-constant, +.hljs-change, +.lasso .hljs-variable, +.makefile .hljs-variable, +.asciidoc .hljs-bullet, +.markdown .hljs-bullet, +.asciidoc .hljs-link_url, +.markdown .hljs-link_url { + color: #080; +} +.hljs-label, +.hljs-javadoc, +.ruby .hljs-string, +.hljs-decorator, +.hljs-filter .hljs-argument, +.hljs-localvars, +.hljs-array, +.hljs-attr_selector, +.hljs-important, +.hljs-pseudo, +.hljs-pi, +.haml .hljs-bullet, +.hljs-doctype, +.hljs-deletion, +.hljs-envvar, +.hljs-shebang, +.apache .hljs-sqbracket, +.nginx .hljs-built_in, +.tex .hljs-formula, +.erlang_repl .hljs-reserved, +.hljs-prompt, +.asciidoc .hljs-link_label, +.markdown .hljs-link_label, +.vhdl .hljs-attribute, +.clojure .hljs-attribute, +.asciidoc .hljs-attribute, +.lasso .hljs-attribute, +.coffeescript .hljs-property, +.hljs-phony { + color: #8888ff; +} +.hljs-keyword, +.hljs-id, +.hljs-title, +.hljs-built_in, +.hljs-aggregate, +.css .hljs-tag, +.hljs-javadoctag, +.hljs-phpdoc, +.hljs-yardoctag, +.smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.apache .hljs-tag, +.go .hljs-typename, +.tex .hljs-command, +.asciidoc .hljs-strong, +.markdown .hljs-strong, +.hljs-request, +.hljs-status { + font-weight: bold; +} +.asciidoc .hljs-emphasis, +.markdown .hljs-emphasis { + font-style: italic; +} +.nginx .hljs-built_in { + font-weight: normal; +} +.coffeescript .javascript, +.javascript .xml, +.lasso .markup, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} +@font-face { + font-family: 'fontello'; + src: url('../static/fontello.eot?10872183'); + src: url('../static/fontello.eot?10872183#iefix') format('embedded-opentype'), url('../static/fontello.woff?10872183') format('woff'), url('../static/fontello.ttf?10872183') format('truetype'), url('../static/fontello.svg?10872183#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'fontello'; + src: url('../font/fontello.svg?10872183#fontello') format('svg'); + } +} +*/ +[class^="icon-"]:before, +[class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +} +.icon-search:before { + content: '\e801'; +} +/* '' */ +.icon-cancel:before { + content: '\e803'; +} +/* '' */ +.icon-right-open:before { + content: '\e802'; +} +/* '' */ +.icon-angle-right:before { + content: '\e800'; +} +/* '' */ +/*** Main Styles ***/ +body { + margin: 0; + font-family: "Lucida Grande", "Helvetica Neue", Helvetica, Arial, Sans-Serif; +} +a, +a:visited { + text-decoration: none; + color: #0D5AFF; +} +a:hover { + text-decoration: underline; +} +.center { + text-align: center; +} +header { + position: fixed; + width: 100%; + background: #FFF; + top: 0; + z-index: 1; +} +header #header-inner { + max-width: 900px; + margin: 0 auto; +} +#content { + max-width: 880px; + margin: 0 auto; + padding: 20px; +} +#logo { + margin: 20px auto 0; + width: 400px; + height: 200px; + display: block; +} +h1 { + text-align: center; +} +h1 a, +h1 a:visited { + color: black; +} +/*** Setup ***/ +#setup { + background: #DB4141; + padding: 15px 20px; + display: inline-block; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + text-align: left; + color: #FFF; + margin-top: 20px; +} +#setup code { + font-family: Consolas, monaco, monospace; +} +/*** Search Box ***/ +#search-form { + float: right; +} +@media (max-width: 540px) { + #search-form { + float: none; + margin-top: 6px; + } +} +#search-form input, +#search-form button { + margin: 0; + vertical-align: top; + border: 1px solid #CCC; +} +#search-form input:focus, +#search-form button:focus { + outline: none; +} +#search-form input { + width: 200px; + height: 30px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0 5px; + font-size: 16px; + border-right: 0; +} +#search-form button { + height: 30px; + width: 30px; + margin: 0; + border-left: 0; + background: #FFF; + cursor: pointer; + font-size: 16px; + color: #999; +} +/*** Heading ***/ +h2 { + border-bottom: 6px solid #424242; + margin: 40px 0 0; + padding: 0 10px 10px; +} +/*** Package Entries ***/ +.entry { + background: #F3F3F3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + padding: 12px 15px 15px; + -webkit-transition: height 0.3s; + -moz-transition: height 0.3s; + -ms-transition: height 0.3s; + -o-transition: height 0.3s; + transition: height 0.3s; + overflow: hidden; + margin-bottom: 12px; +} +.entry h3 { + font-size: 24px; + margin: 0 0 10px; +} +.entry .name:hover { + text-decoration: none; +} +.entry .name:before { + margin: 0; + margin-left: -10px; + -webkit-transition: -webkit-transform 0.2s; + -moz-transition: -moz-transform 0.2s; + -ms-transition: -ms-transform 0.2s; + -o-transition: -o-transform 0.2s; + transition: transform 0.2s; +} +.entry.open .name:before { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.entry .version { + font-size: 16px; + color: #666; +} +.entry .author { + font-size: 16px; + float: right; + color: #666; +} +.entry p { + margin: 0; +} +.entry .readme { + font-size: 14px; + margin-top: 10px; + background: #FFF; + padding: 10px 12px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +/*** Search Results ***/ +.state-search #all-packages { + display: none; +} +.search-ajax { + display: block; + margin: 50px auto; +} +.no-results { + text-align: center; + margin: 50px 0; + color: #888; +} +.no-results big { + font-size: 38px; + margin-bottom: 8px; +} +.no-results code { + font-size: 1.2em; +} +/*# sourceMappingURL=data:application/json,%7B%22version%22%3A3%2C%22sources%22%3A%5B%22lib%2FGUI%2Fcss%2Fmarkdown.less%22%2C%22lib%2FGUI%2Fcss%2Fhighlight.js.less%22%2C%22lib%2FGUI%2Fcss%2Ffontello.less%22%2C%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%3BAAEA%2COACE%3BEACE%2CcAAA%3B%3BAAFJ%2COAGE%2CEAAC%3BEACC%2CcAAA%3B%3BAAJJ%2COAKE%2CEAAC%3BEACC%2CcAAA%3BEACA%2CkBAAA%3BEACA%2CkBAAA%3BEACA%2CeAAA%3BEACA%2CkBAAA%3BEACA%2CMAAA%3BEACA%2COAAA%3BEACA%2CSAAA%3B%3BAAbJ%2COAeE%3BAAfF%2COAeM%3BAAfN%2COAeU%3BAAfV%2COAec%3BAAfd%2COAekB%3BAAflB%2COAesB%3BEAClB%2CmBAAA%3BEACA%2CUAAA%3BEACA%2CiBAAA%3BEACA%2CmCAAA%3BEACA%2CYAAA%3BEACA%2CkBAAA%3B%3BAArBJ%2COAuBE%2CGAAE%2CMAAO%2CEAAC%3BAAvBZ%2COAuBqB%2CGAAE%2CMAAO%2CEAAC%3BAAvB%2FB%2COAuBwC%2CGAAE%2CMAAO%2CEAAC%3BAAvBlD%2COAuB2D%2CGAAE%2CMAAO%2CEAAC%3BAAvBrE%2COAuB8E%2CGAAE%2CMAAO%2CEAAC%3BAAvBxF%2COAuBiG%2CGAAE%2CMAAO%2CEAAC%3BEACvG%2CgBAAgB%2CiEAAhB%3BEACA%2CqBAAA%3B%3BAAzBJ%2COA2BE%2CGAAG%3BAA3BL%2COA2BS%2CGAAG%3BEACR%2CkBAAA%3B%3BAA5BJ%2COA8BE%2CGAAG%3BAA9BL%2COA8BS%2CGAAG%3BEACR%2CkBAAA%3B%3BAA%2FBJ%2COAiCE%2CGAAG%3BAAjCL%2COAiCS%2CGAAG%3BEACR%2CkBAAA%3B%3BAAlCJ%2COAoCE%2CGAAG%3BAApCL%2COAoCS%2CGAAG%3BEACR%2CkBAAA%3B%3BAArCJ%2COAuCE%2CGAAG%3BAAvCL%2COAuCS%2CGAAG%3BEACR%2CkBAAA%3B%3BAAxCJ%2COA0CE%2CGAAG%3BAA1CL%2COA0CS%2CGAAG%3BEACR%2CkBAAA%3B%3BAA3CJ%2COA6CE%3BEACE%2CeAAA%3BEACA%2CYAAA%3B%3BAA%2FCJ%2COAiDE%3BEACE%2CeAAA%3BEACA%2CgCAAA%3BEACA%2CYAAA%3B%3BAApDJ%2COAsDE%3BEACE%2CeAAA%3B%3BAAvDJ%2COAyDE%3BEACE%2CeAAA%3B%3BAA1DJ%2COA4DE%3BEACE%2CeAAA%3B%3BAA7DJ%2COA%2BDE%3BEACE%2CcAAA%3BEACA%2CeAAA%3B%3BAAjEJ%2COAmEE%3BAAnEF%2COAmEK%3BAAnEL%2COAmEiB%3BAAnEjB%2COAmEqB%3BAAnErB%2COAmEyB%3BAAnEzB%2COAmE6B%3BAAnE7B%2COAmEiC%3BAAnEjC%2COAmEwC%3BEACpC%2CcAAA%3B%3BAApEJ%2COAsEE%3BEACE%2C4BAA4B%2C0DAA5B%3BEACA%2CcAAA%3BEACA%2CcAAA%3BEACA%2CWAAA%3BEACA%2CUAAA%3B%3BAA3EJ%2COA6EE%2CKAAK%2CKAAI%3BEACP%2CaAAA%3BEACA%2CcAAA%3B%3BAA%2FEJ%2COAgFE%2CKAAK%2CKAAI%3BEACP%2CaAAA%3BEACA%2CcAAA%3B%3BAAlFJ%2COAmFI%2CKAAK%2CKAAI%2CYAAa%3BEACpB%2CaAAA%3BEACA%2CcAAA%3B%3BAArFN%2COAsFE%2CKAAK%2CKAAI%3BAAtFX%2COAsFyB%2CKAAK%2CKAAI%3BAAtFlC%2COAsFgD%2CKAAK%2CKAAI%3BAAtFzD%2COAsFuE%2CKAAK%2CKAAI%3BEAC5E%2CaAAA%3BEACA%2CcAAA%3B%3BAAxFJ%2COA0FE%2CEAAC%2CYAAa%3BAA1FhB%2COA0FoB%2CEAAC%2CYAAa%3BAA1FlC%2COA0FsC%2CEAAC%2CYAAa%3BAA1FpD%2COA0FwD%2CEAAC%2CYAAa%3BAA1FtE%2COA0F0E%2CEAAC%2CYAAa%3BAA1FxF%2COA0F4F%2CEAAC%2CYAAa%3BEACtG%2CaAAA%3BEACA%2CcAAA%3B%3BAA5FJ%2COA8FE%2CGAAG%3BAA9FL%2COA8FQ%2CGAAG%3BAA9FX%2COA8Fc%2CGAAG%3BAA9FjB%2COA8FoB%2CGAAG%3BAA9FvB%2COA8F0B%2CGAAG%3BAA9F7B%2COA8FgC%2CGAAG%3BEAC%2FB%2CaAAA%3B%3BAA%2FFJ%2COAiGE%2CGAAG%2CEAAC%3BEACF%2CqBAAA%3B%3BAAlGJ%2COAoGE%3BAApGF%2COAoGM%3BEACF%2CkBAAA%3B%3BAArGJ%2COAuGE%2CGAAG%3BAAvGL%2COAuGmB%2CGAAG%3BEAClB%2CaAAA%3B%3BAAxGJ%2COA0GE%2CGAAG%3BAA1GL%2COA0GkB%2CGAAG%3BEACjB%2CgBAAA%3B%3BAA3GJ%2COA6GE%3BEACE%2CUAAA%3B%3BAA9GJ%2COA%2BGI%2CGAAG%3BEACD%2CeAAA%3BEACA%2CiBAAA%3BEACA%2CkBAAA%3BEACA%2CUAAA%3BEACA%2CkBAAA%3B%3BAApHN%2COAqHM%2CGAAG%2CGAAE%3BEACH%2CUAAA%3B%3BAAtHR%2COAuHM%2CGAAG%2CGAAG%3BEACJ%2CaAAA%3B%3BAAxHR%2COAyHM%2CGAAG%2CGAAG%3BEACJ%2CgBAAA%3B%3BAA1HR%2COA2HI%2CGAAG%3BEACD%2CgBAAA%3BEACA%2CeAAA%3B%3BAA7HN%2COA8HM%2CGAAG%2CGAAG%3BEACJ%2CaAAA%3B%3BAA%2FHR%2COAgIM%2CGAAG%2CGAAG%3BEACJ%2CgBAAA%3B%3BAAjIR%2COAmIE%3BEACE%2C8BAAA%3BEACA%2CeAAA%3BEACA%2CcAAA%3B%3BAAtIJ%2COAuII%2CWAAW%3BEACT%2CaAAA%3B%3BAAxIN%2COAyII%2CWAAW%3BEACT%2CgBAAA%3B%3BAA1IN%2COA4IE%3BEACE%2CUAAA%3B%3BAA7IJ%2COA8II%2CMAAM%3BEACJ%2C6BAAA%3BEACA%2CuBAAA%3BEACA%2CSAAA%3BEACA%2CUAAA%3B%3BAAlJN%2COAmJM%2CMAAM%2CGAAE%2CUAAU%3BEAChB%2CyBAAA%3B%3BAApJR%2COAqJM%2CMAAM%2CGAAG%3BEACP%2CiBAAA%3BEACA%2CyBAAA%3BEACA%2CgBAAA%3BEACA%2CSAAA%3BEACA%2CiBAAA%3B%3BAA1JR%2COA2JM%2CMAAM%2CGAAG%3BEACP%2CyBAAA%3BEACA%2CgBAAA%3BEACA%2CSAAA%3BEACA%2CiBAAA%3B%3BAA%2FJR%2COAgKM%2CMAAM%2CGAAG%2CGAAG%3BAAhKlB%2COAgKgC%2CMAAM%2CGAAG%2CGAAG%3BEACpC%2CaAAA%3B%3BAAjKR%2COAkKM%2CMAAM%2CGAAG%2CGAAG%3BAAlKlB%2COAkK%2BB%2CMAAM%2CGAAG%2CGAAG%3BEACnC%2CgBAAA%3B%3BAAnKR%2COAqKE%3BEACE%2CcAAA%3BEACA%2CeAAA%3B%3BAAvKJ%2COAyKE%2CKAAI%3BEACF%2CcAAA%3BEACA%2CgBAAA%3B%3BAA3KJ%2COA4KI%2CKAAI%2CMAAO%3BEACT%2CyBAAA%3BEACA%2CcAAA%3BEACA%2CWAAA%3BEACA%2CgBAAA%3BEACA%2CgBAAA%3BEACA%2CYAAA%3BEACA%2CWAAA%3B%3BAAnLN%2COAoLI%2CKAAI%2CMAAO%2CKAAK%3BEACd%2CcAAA%3BEACA%2CWAAA%3B%3BAAtLN%2COAuLI%2CKAAI%2CMAAO%2CKAAK%3BEACd%2CWAAA%3BEACA%2CcAAA%3BEACA%2CcAAA%3BEACA%2CgBAAA%3B%3BAA3LN%2COA4LE%2CKAAI%3BEACF%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CWAAA%3B%3BAA%2FLJ%2COAgMI%2CKAAI%2CaAAc%3BEAChB%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CmBAAA%3BEACA%2CkBAAA%3B%3BAApMN%2COAqMI%2CKAAI%2CaAAc%2CKAAK%3BEACrB%2CcAAA%3BEACA%2CkBAAA%3B%3BAAvMN%2COAwME%2CKAAI%3BEACF%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CWAAA%3B%3BAA3MJ%2COA4MI%2CKAAI%2CYAAa%3BEACf%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CgBAAA%3BEACA%2CiBAAA%3B%3BAAhNN%2COAiNI%2CKAAI%2CYAAa%2CKAAK%3BEACpB%2CSAAA%3BEACA%2CiBAAA%3B%3BAAnNN%2COAoNE%2CKAAI%3BEACF%2CcAAA%3BEACA%2CkBAAA%3BEACA%2CgBAAA%3BEACA%2CWAAA%3B%3BAAxNJ%2COAyNI%2CKAAI%2CWAAY%3BEACd%2CgBAAA%3B%3BAA1NN%2COA2NE%2CKAAI%3BEACF%2CcAAA%3BEACA%2CiBAAA%3BEACA%2CgBAAA%3BEACA%2CYAAA%3B%3BAA%2FNJ%2COAgOI%2CKAAI%2CYAAa%3BEACf%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CmBAAA%3BEACA%2CiBAAA%3B%3BAApON%2COAsOE%3BAAtOF%2COAsOQ%3BEACJ%2CaAAA%3BEACA%2CcAAA%3BEACA%2CmBAAA%3BEACA%2CyBAAA%3BEACA%2CyBAAA%3BEACA%2CkBAAA%3B%3BAA5OJ%2COA8OE%2CIAAI%3BEACF%2CSAAA%3BEACA%2CUAAA%3BEACA%2CgBAAA%3BEACA%2CYAAA%3BEACA%2CuBAAA%3B%3BAAnPJ%2COAqPE%2CWAAW%3BEACT%2CyBAAA%3BEACA%2CyBAAA%3BEACA%2CeAAA%3BEACA%2CiBAAA%3BEACA%2CcAAA%3BEACA%2CiBAAA%3BEACA%2CkBAAA%3B%3BAA5PJ%2COA8PE%3BEACE%2CyBAAA%3BEACA%2CyBAAA%3BEACA%2CeAAA%3BEACA%2CiBAAA%3BEACA%2CcAAA%3BEACA%2CiBAAA%3BEACA%2CkBAAA%3B%3BAArQJ%2COAsQI%2CIAAI%3BAAtQR%2COAsQc%2CIAAI%3BEACZ%2C6BAAA%3BEACA%2CYAAA%3B%3B%3B%3B%3B%3B%3BACpQN%3BEACE%2CcAAA%3BEAAgB%2CcAAA%3BEAChB%2CmBAAA%3B%3BAAGF%3BAACA%3BAACA%2CSAAU%3BAACV%2CKAAM%3BAACN%2CQAAS%3BAACT%2CMAAO%3BEACL%2CYAAA%3B%3BAAGF%3BAACA%3BAACA%3BAACA%3BAACA%2CSAAU%3BAACV%2CWAAY%3BAACZ%2CWAAY%2CYAAY%3BAACxB%3BAACA%3BAACA%2CKAAM%3BAACN%2CKAAM%3BAACN%2CKAAM%2CaAAa%3BAACnB%3BAACA%3BAACA%2COAAQ%3BAACR%2CUAAW%3BAACX%3BAACA%3BAACA%3BAACA%2CKAAM%3BAACN%2COAAQ%3BAACR%2COAAQ%3BAACR%2CIAAK%3BAACL%2CIAAK%3BAACL%2CYAAa%3BAACb%2CSAAU%3BAACV%2CSAAU%3BAACV%2CaAAc%3BEACZ%2CWAAA%3B%3BAAGF%3BAACA%3BAACA%3BAACA%3BAACA%2CKAAM%3BAACN%3BAACA%2CSAAU%3BAACV%2CSAAU%3BEACR%2CWAAA%3B%3BAAGF%3BAACA%3BAACA%3BAACA%3BAACA%3BAACA%2CUAAW%3BAACX%2CUAAW%3BAACX%2CGAAI%3BAACJ%3BAACA%2CMAAO%3BAACP%2CSAAU%3BAACV%2CSAAU%3BAACV%2CSAAU%3BAACV%2CSAAU%3BAACV%2CSAAU%3BEACR%2CWAAA%3B%3BAAGF%3BAACA%3BAACA%2CKAAM%3BAACN%3BAACA%2CYAAa%3BAACb%3BAACA%3BAACA%3BAACA%3BAACA%3BAACA%3BAACA%2CKAAM%3BAACN%3BAACA%3BAACA%3BAACA%3BAACA%2COAAQ%3BAACR%2CMAAO%3BAACP%2CIAAK%3BAACL%2CYAAa%3BAACb%3BAACA%2CSAAU%3BAACV%2CSAAU%3BAACV%2CKAAM%3BAACN%2CQAAS%3BAACT%2CSAAU%3BAACV%2CMAAO%3BAACP%2CaAAc%3BAACd%3BEACE%2CcAAA%3B%3BAAGF%3BAACA%3BAACA%3BAACA%3BAACA%3BAACA%2CIAAK%3BAACL%3BAACA%3BAACA%3BAACA%2CUAAW%3BAACX%3BAACA%2CKAAM%3BAACN%2COAAQ%3BAACR%2CGAAI%3BAACJ%2CIAAK%3BAACL%2CSAAU%3BAACV%2CSAAU%3BAACV%3BAACA%3BEACE%2CiBAAA%3B%3BAAGF%2CSAAU%3BAACV%2CSAAU%3BEACR%2CkBAAA%3B%3BAAGF%2CMAAO%3BEACL%2CmBAAA%3B%3BAAGF%2CaAAc%3BAACd%2CWAAY%3BAACZ%2CMAAO%3BAACP%2CIAAK%3BAACL%2CIAAK%3BAACL%2CIAAK%3BAACL%2CIAAK%3BAACL%2CIAAK%3BEACH%2CYAAA%3B%3BACvJF%3BEACE%2CaAAa%2CUAAb%3BEACA%2CSAAS%2CkCAAT%3BEACA%2CSAAS%2CyCAAyC%2COAAO%2C0BAChD%2CoCAAoC%2COAAO%2CaAC3C%2CmCAAmC%2COAAO%2CiBAC1C%2C4CAA4C%2COAAO%2CMAH5D%3BEAIA%2CmBAAA%3BEACA%2CkBAAA%3B%3B%3B%3B%3B%3B%3B%3B%3B%3B%3B%3BAAaD%2CgBAAgB%3BAAAS%2CiBAAiB%3BEACzC%2CaAAa%2CUAAb%3BEACA%2CkBAAA%3BEACA%2CmBAAA%3BEACA%2CWAAA%3BEAEA%2CqBAAA%3BEACA%2CwBAAA%3BEACA%2CUAAA%3BEACA%2CkBAAA%3BEACA%2CkBAAA%3B%3B%3BEAIA%2CoBAAA%3BEACA%2CoBAAA%3B%3BEAGA%2CgBAAA%3B%3B%3BEAIA%2CiBAAA%3B%3B%3B%3B%3B%3BAASF%2CYAAY%3BEAAU%2CSAAS%2COAAT%3B%3B%3BAACtB%2CYAAY%3BEAAU%2CSAAS%2COAAT%3B%3B%3BAACtB%2CgBAAgB%3BEAAU%2CSAAS%2COAAT%3B%3B%3BAAC1B%2CiBAAiB%3BEAAU%2CSAAS%2COAAT%3B%3B%3B%3BACjD3B%3BEACC%2CSAAA%3BEACA%2CaAAa%2CiBAAiB%2C8CAA9B%3B%3BAAGD%3BAAAG%2CCAAC%3BEACH%2CqBAAA%3BEACA%2CcAAA%3B%3BAAGD%2CCAAC%3BEACA%2C0BAAA%3B%3BAAGD%3BEACC%2CkBAAA%3B%3BAAKD%3BEACC%2CeAAA%3BEACA%2CWAAA%3BEACA%2CgBAAA%3BEACA%2CMAAA%3BEACA%2CUAAA%3B%3BAALD%2CMAOC%3BEACC%2CgBAAA%3BEACA%2CcAAA%3B%3BAAIF%3BEACC%2CgBAAA%3BEACA%2CcAAA%3BEACA%2CaAAA%3B%3BAAGD%3BEACC%2CmBAAA%3BEACA%2CYAAA%3BEACA%2CaAAA%3BEACA%2CcAAA%3B%3BAAGD%3BEACC%2CkBAAA%3B%3BAADD%2CEAGC%3BAAHD%2CEAGI%2CEAAC%3BEACH%2CYAAA%3B%3B%3BAAKF%3BEACC%2CmBAAA%3BEACA%2CkBAAA%3BEACA%2CqBAAA%3BEC%2FCG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BED%2BCH%2CgBAAA%3BEACA%2CWAAA%3BEACA%2CgBAAA%3B%3BAAPD%2CMASC%3BEACC%2CwCAAA%3B%3B%3BAAKF%3BEACC%2CYAAA%3B%3BAAOA%2CQAL0B%3BEAK1B%3BIAJC%2CWAAA%3BIACA%2CeAAA%3B%3B%3BAALF%2CYAUC%3BAAVD%2CYAUQ%3BEACN%2CSAAA%3BEACA%2CmBAAA%3BEACA%2CsBAAA%3B%3BAAEA%2CYALD%2CMAKE%3BAAAD%2CYALM%2COAKL%3BEACA%2CaAAA%3B%3BAAhBH%2CYAoBC%3BEACC%2CYAAA%3BEACA%2CYAAA%3BECrCE%2C2BAAA%3BEACA%2C8BAAA%3BEACA%2CsBAAA%3BEDqCF%2CcAAA%3BEACA%2CeAAA%3BEACA%2CeAAA%3B%3BAA1BF%2CYA6BC%3BEACC%2CYAAA%3BEACA%2CWAAA%3BEACA%2CSAAA%3BEACA%2CcAAA%3BEACA%2CgBAAA%3BEACA%2CeAAA%3BEACA%2CeAAA%3BEACA%2CWAAA%3B%3B%3BAAKF%3BEACC%2CgCAAA%3BEACA%2CgBAAA%3BEACA%2CoBAAA%3B%3B%3BAAID%3BEACC%2CmBAAA%3BEC7GG%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3BED6GH%2CuBAAA%3BECpDG%2C%2BBAAA%3BEACA%2C4BAAA%3BEACA%2C2BAAA%3BEACA%2C0BAAA%3BEACA%2CuBAAA%3BEDkDH%2CgBAAA%3BEACA%2CmBAAA%3B%3BAAND%2CMAQC%3BEACC%2CeAAA%3BEACA%2CgBAAA%3B%3BAAVF%2CMAaC%2CMAAK%3BEACJ%2CqBAAA%3B%3BAAdF%2CMAiBC%2CMAAK%3BEACJ%2CSAAA%3BEACA%2CkBAAA%3BEC1DE%2C0CAAA%3BEACA%2CoCAAA%3BEACA%2CkCAAA%3BEACA%2CgCAAA%3BEACA%2C0BAAA%3B%3BAD0DH%2CMAAC%2CKAAM%2CMAAK%3BEC3CT%2CgCAAA%3BEACA%2C6BAAA%3BEACA%2C2BAAA%3BEACA%2C4BAAA%3BEACA%2CwBAAA%3B%3BADgBJ%2CMA2BC%3BEACC%2CeAAA%3BEACA%2CWAAA%3B%3BAA7BF%2CMAgCC%3BEACC%2CeAAA%3BEACA%2CYAAA%3BEACA%2CWAAA%3B%3BAAnCF%2CMAsCC%3BEACC%2CSAAA%3B%3BAAvCF%2CMA0CC%3BEACC%2CeAAA%3BEACA%2CgBAAA%3BEACA%2CgBAAA%3BEACA%2CkBAAA%3BEC1JE%2C0BAAA%3BEACA%2CuBAAA%3BEACA%2CkBAAA%3B%3B%3BAD8JJ%2CaAAc%3BEACb%2CaAAA%3B%3BAAGD%3BEACC%2CcAAA%3BEACA%2CiBAAA%3B%3BAAGD%3BEACC%2CkBAAA%3BEACA%2CcAAA%3BEACA%2CWAAA%3B%3BAAHD%2CWAKC%3BEACC%2CeAAA%3BEACA%2CkBAAA%3B%3BAAPF%2CWAUC%3BEACC%2CgBAAA%22%7D */ \ No newline at end of file diff --git a/lib/static/main.js b/lib/static/main.js new file mode 100644 index 000000000..82fc2b1c8 --- /dev/null +++ b/lib/static/main.js @@ -0,0 +1,965 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 1.0.0']; +helpers = this.merge(helpers, Handlebars.helpers); data = data || {}; + var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression; + + + buffer += "
\n

\n "; + if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); } + else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } + buffer += escapeExpression(stack1) + + "\n v"; + if (helper = helpers.version) { stack1 = helper.call(depth0, {hash:{},data:data}); } + else { helper = (depth0 && depth0.version); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } + buffer += escapeExpression(stack1) + + "\n
By: " + + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0._npmUser)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1)) + + "
\n

\n

"; + if (helper = helpers.description) { stack1 = helper.call(depth0, {hash:{},data:data}); } + else { helper = (depth0 && depth0.description); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } + buffer += escapeExpression(stack1) + + "

\n
"; + return buffer; + }); +},{"handlebars/runtime":12}],2:[function(require,module,exports){ +var $ = require('unopinionate').selector, + onClick = require('onclick'), + transitionComplete = require('transition-complete'); + +$(function() { + onClick('.entry .name', function() { + var $this = $(this), + $entry = $this.closest('.entry'); + + //Close entry + if($entry.hasClass('open')) { + $entry + .height($entry.height()) + .removeClass('open'); + + setTimeout(function() { + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); + + transitionComplete(function() { + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); + } + //Open entry + else { + //Close open entries + $('.entry.open').each(function() { + var $entry = $(this); + $entry + .height($entry.height()) + .removeClass('open'); + + setTimeout(function() { + $entry.css('height', $entry.attr('data-height') + 'px'); + }, 0); + + transitionComplete(function() { + $entry.find('.readme').remove(); + $entry.css('height', 'auto'); + }); + }); + + //Add the open class + $entry.addClass('open'); + + //Explicitly set heights for transitions + var height = $entry.height(); + $entry + .attr('data-height', height) + .css('height', height); + + //Get the data + $.ajax({ + url: '/-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'), + dataType: 'text', + success: function(html) { + var $readme = $("
") + .html(html) + .appendTo($entry); + + $entry.height(height + $readme.outerHeight()); + + transitionComplete(function() { + $entry.css('height', 'auto'); + }); + } + }); + } + }); +}); +},{"onclick":13,"transition-complete":15,"unopinionate":16}],3:[function(require,module,exports){ +var $ = require('unopinionate').selector, + onScroll = require('onscroll'); + +$(function() { + var $header = $('header'), + $content = $('#content'), + bottomOffset = 52; + + var scrollFunc = function(top) { + var limit = $header.outerHeight() - bottomOffset; + + if(top < 0) { + $header.css('top', 0); + } + else if(top > limit) { + $header.css('top', -limit + 'px'); + } + else { + $header.css('top', -top + 'px'); + } + }; + + onScroll(scrollFunc); + scrollFunc(); + + $(window).resize(function() { + $content.css('margin-top', $header.outerHeight()); + }).resize(); +}); +},{"onscroll":14,"unopinionate":16}],4:[function(require,module,exports){ +require('./search'); +require('./entry'); +require('./header'); +},{"./entry":2,"./header":3,"./search":5}],5:[function(require,module,exports){ +var $ = require('unopinionate').selector, + template = require('../entry.hbs'), + onScroll = require('onscroll'); + +$(function() { + 'use strict'; + + var $form = $('#search-form'), + $input = $form.find('input'), + $searchResults = $("#search-results"), + $body = $('body'), + $clear = $form.find('.clear'), + request, + currentResults; + + $form.bind('submit keyup', function(e) { + e.preventDefault(); + + var q = $input.val(); + + $body.addClass('state-search'); + + //Switch the icons + $clear + [q ? 'addClass' : 'removeClass']('icon-cancel') + [!q ? 'addClass' : 'removeClass']('icon-search'); + + if(q) { + if(request) { + request.abort(); + } + + if(!currentResults) { + $searchResults.html("Spinner"); + } + + request = $.getJSON('/-/search/' + q, function(results) { + currentResults = results; + + if(results.length) { + var html = ''; + + $.each(results, function(i, entry) { + html += template(entry); + }); + + $searchResults.html(html); + } + else { + $searchResults.html("
No Results
"); + } + }); + } + else { + request.abort(); + currentResults = null; + $searchResults.html(''); + $body.removeClass('state-search'); + } + }); + + $clear.click(function(e) { + e.preventDefault(); + $input.val(''); + $form.keyup(); + }); + + + +}); + +},{"../entry.hbs":1,"onscroll":14,"unopinionate":16}],6:[function(require,module,exports){ +"use strict"; +/*globals Handlebars: true */ +var base = require("./handlebars/base"); + +// Each of these augment the Handlebars object. No need to setup here. +// (This is done to easily share code between commonjs and browse envs) +var SafeString = require("./handlebars/safe-string")["default"]; +var Exception = require("./handlebars/exception")["default"]; +var Utils = require("./handlebars/utils"); +var runtime = require("./handlebars/runtime"); + +// For compatibility and usage outside of module systems, make the Handlebars object a namespace +var create = function() { + var hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = SafeString; + hb.Exception = Exception; + hb.Utils = Utils; + + hb.VM = runtime; + hb.template = function(spec) { + return runtime.template(spec, hb); + }; + + return hb; +}; + +var Handlebars = create(); +Handlebars.create = create; + +exports["default"] = Handlebars; +},{"./handlebars/base":7,"./handlebars/exception":8,"./handlebars/runtime":9,"./handlebars/safe-string":10,"./handlebars/utils":11}],7:[function(require,module,exports){ +"use strict"; +var Utils = require("./utils"); +var Exception = require("./exception")["default"]; + +var VERSION = "1.3.0"; +exports.VERSION = VERSION;var COMPILER_REVISION = 4; +exports.COMPILER_REVISION = COMPILER_REVISION; +var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '>= 1.0.0' +}; +exports.REVISION_CHANGES = REVISION_CHANGES; +var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; + +function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); +} + +exports.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); + } else { + if (inverse) { fn.not = inverse; } + this.helpers[name] = fn; + } + }, + + registerPartial: function(name, str) { + if (toString.call(name) === objectType) { + Utils.extend(this.partials, name); + } else { + this.partials[name] = str; + } + } +}; + +function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Exception("Missing helper: '" + arg + "'"); + } + }); + + instance.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + if (isFunction(context)) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if (isArray(context)) { + if(context.length > 0) { + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } + }); + + instance.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); + } + + if(context && typeof context === 'object') { + if (isArray(context)) { + for(var j = context.length; i": ">", + '"': """, + "'": "'", + "`": "`" +}; + +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; + +function escapeChar(chr) { + return escape[chr] || "&"; +} + +function extend(obj, value) { + for(var key in value) { + if(Object.prototype.hasOwnProperty.call(value, key)) { + obj[key] = value[key]; + } + } +} + +exports.extend = extend;var toString = Object.prototype.toString; +exports.toString = toString; +// Sourced from lodash +// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt +var isFunction = function(value) { + return typeof value === 'function'; +}; +// fallback for older versions of Chrome and Safari +if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; +} +var isFunction; +exports.isFunction = isFunction; +var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; +}; +exports.isArray = isArray; + +function escapeExpression(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof SafeString) { + return string.toString(); + } else if (!string && string !== 0) { + return ""; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = "" + string; + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); +} + +exports.escapeExpression = escapeExpression;function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } +} + +exports.isEmpty = isEmpty; +},{"./safe-string":10}],12:[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":6}],13:[function(require,module,exports){ +var $ = require('unopinionate').selector; + +var $document = $(document), + bindings = {}; + +var click = function(events) { + click.bind.apply(click, arguments); + return click; +}; + +/*** Configuration Options ***/ +click.distanceLimit = 10; +click.timeLimit = 140; + +/*** Useful Properties ***/ +click.isTouch = ('ontouchstart' in window) || + window.DocumentTouch && + document instanceof DocumentTouch; + +/*** Cached Functions ***/ +var onTouchstart = function(e) { + e.stopPropagation(); //Prevents multiple click events from happening + + click._doAnywheres(e); + + var $this = $(this), + startTime = new Date().getTime(), + startPos = click._getPos(e); + + $this.one('touchend', function(e) { + e.preventDefault(); //Prevents click event from firing + + var time = new Date().getTime() - startTime, + endPos = click._getPos(e), + distance = Math.sqrt( + Math.pow(endPos.x - startPos.x, 2) + + Math.pow(endPos.y - startPos.y, 2) + ); + + if(time < click.timeLimit && distance < click.distanceLimit) { + //Find the correct callback + $.each(bindings, function(selector, callback) { + if($this.is(selector)) { + callback.apply(e.target, [e]); + return false; + } + }); + } + }); +}; + +/*** API ***/ +click.bind = function(events) { + + //Argument Surgery + if(!$.isPlainObject(events)) { + newEvents = {}; + newEvents[arguments[0]] = arguments[1]; + events = newEvents; + } + + $.each(events, function(selector, callback) { + + /*** Register Binding ***/ + if(typeof bindings[selector] != 'undefined') { + click.unbind(selector); //Ensure no duplicates + } + + bindings[selector] = callback; + + /*** Touch Support ***/ + if(click.isTouch) { + $document.delegate(selector, 'touchstart', onTouchstart); + } + + /*** Mouse Support ***/ + $document.delegate(selector, 'click', function(e) { + e.stopPropagation(); //Prevents multiple click events from happening + //click._doAnywheres(e); //Do anywheres first to be consistent with touch order + callback.apply(this, [e]); + }); + }); + + return this; +}; + +click.unbind = function(selector) { + $document + .undelegate(selector, 'touchstart') + .undelegate(selector, 'click'); + + delete bindings[selector]; + + return this; +}; + +click.unbindAll = function() { + $.each(bindings, function(selector, callback) { + $document + .undelegate(selector, 'touchstart') + .undelegate(selector, 'click'); + }); + + bindings = {}; + + return this; +}; + +click.trigger = function(selector, e) { + e = e || $.Event('click'); + + if(typeof bindings[selector] != 'undefined') { + bindings[selector](e); + } + else { + console.error("No click events bound for selector '"+selector+"'."); + } + + return this; +}; + +click.anywhere = function(callback) { + click._anywheres.push(callback); + return this; +}; + +/*** Internal (but useful) Methods ***/ +click._getPos = function(e) { + e = e.originalEvent; + + if(e.pageX || e.pageY) { + return { + x: e.pageX, + y: e.pageY + }; + } + else if(e.changedTouches) { + return { + x: e.changedTouches[0].clientX, + y: e.changedTouches[0].clientY + }; + } + else { + return { + x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, + y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop + }; + } +}; + +click._anywheres = []; + +click._doAnywheres = function(e) { + var i = click._anywheres.length; + while(i--) { + click._anywheres[i](e); + } +}; + +$(document).bind('mousedown', click._doAnywheres); + +module.exports = click; + + +},{"unopinionate":16}],14:[function(require,module,exports){ +var $ = require('unopinionate').selector; + +var bodyScrollers = []; + +$(function() { + var $html = $('html'), + $body = $('body'); + + $(window, document, 'body').bind('scroll touchmove', function() { + var top = $html[0].scrollTop || $body[0].scrollTop; + + for(var i=0; i= locals.length - 1) { + callback(err, packages); + } + else { + getPackage(i + 1); + } + }); + }; + + if(locals.length) { + getPackage(0); + } + else { + callback(null, []); + } +}; + // function fetches package information from uplinks and synchronizes it with local data // if package is available locally, it MUST be provided in pkginfo // returns callback(err, result, uplink_errors) diff --git a/lib/users.js b/lib/users.js new file mode 100644 index 000000000..e9a4fe2ff --- /dev/null +++ b/lib/users.js @@ -0,0 +1,37 @@ +var fs = require('fs') + , crypto = require('crypto') + , usersPath = './users.json'; + +var Users = function() { + if(fs.existsSync(usersPath)) { + this.users = JSON.parse(fs.readFileSync(usersPath, 'utf8')); + } + else { + this.users = {}; + } +}; + +Users.prototype = { + add: function(params, callback) { + //Hash the Password + if(params.password) { + params.password = crypto.createHash('sha1').update(params.password).digest('hex'); + } + else if(params.password_sha) { + params.password = params.password_sha; + } + + //Save + this.users[params.name] = params; + this.sync(callback); + }, + remove: function(name, callback) { + delete this.users[name]; + this.sync(callback); + }, + sync: function(callback) { + fs.writeFile(usersPath, JSON.stringify(this.users), callback); + } +}; + +module.exports = new Users(); diff --git a/package.yaml b/package.yaml index 7ef3dd015..5b3be20d9 100644 --- a/package.yaml +++ b/package.yaml @@ -28,6 +28,17 @@ dependencies: minimatch: '>= 0.2.14' bunyan: '>= 0.22.1' mkdirp: '>= 0.3.5' + handlebars: '1.x.x' + helpers.less: 'git://github.com/bpeacock/helpers.less.git' + highlight.js: '^8.0.0' + lunr: '^0.5.2' + marked: '^0.3.2' + onclick: '^0.1.0' + onscroll: '0.0.3' + tar.gz: '^0.1.1' + transition-complete: '0.0.2' + underscore: '^1.6.0' + unopinionate: '0.0.4' optionalDependencies: fs-ext: '>= 0.3.2' @@ -44,6 +55,13 @@ devDependencies: # installed, but I don't want it to be installed everytime #heapdump: '*' + browserify: '^3.46.0' + browserify-handlebars: '~0.2.0' + grunt: '^0.4.4' + grunt-browserify: '^2.0.8' + grunt-contrib-less: '^0.11.0' + grunt-contrib-watch: '^0.6.1' + keywords: - private - package @@ -71,4 +89,3 @@ publishConfig: license: type: WTFPL url: http://www.wtfpl.net/txt/copying/ -