├── app
├── fonts
├── scripts
│ ├── settings.js
│ ├── view
│ │ ├── viewport.js
│ │ ├── list-item.js
│ │ ├── sidebar.js
│ │ ├── settings.js
│ │ ├── navbar.js
│ │ ├── dragbar.js
│ │ └── request.js
│ ├── router.js
│ ├── template
│ │ ├── request.htm
│ │ ├── navbar.htm
│ │ ├── sidebar.htm
│ │ └── settings.htm
│ ├── model
│ │ ├── settings.js
│ │ └── request.js
│ ├── model.js
│ ├── collection.js
│ ├── view.js
│ ├── store
│ │ └── gist.js
│ └── app.js
├── styles
│ ├── settings.less
│ ├── logo.less
│ ├── sidebar.less
│ ├── dragbar.less
│ ├── main.less
│ ├── bootstrap.less
│ └── request.less
├── inc
│ ├── jquery.querystring.js
│ ├── tpl.js
│ ├── highlight-default.css
│ ├── highlight.js
│ ├── require.js
│ ├── almond.js
│ ├── text.js
│ ├── behave.js
│ └── underscore.js
└── index.html
├── .agignore
├── .gitignore
├── .dockerignore
├── .jslintrc
├── bin
├── docker-build.sh
└── docker-run.sh
├── .gitmodules
├── inc
└── bootstrap
├── Dockerfile
├── app.build.js
├── app.psgi
├── .editorconfig
├── env-filter.js
├── package.json
├── .github
└── workflows
│ ├── build-production-container.yml
│ ├── build-deployment-container.yml
│ └── codeql-analysis.yml
├── server.js
├── README.md
└── LICENSE
/app/fonts:
--------------------------------------------------------------------------------
1 | inc/bootstrap/fonts
--------------------------------------------------------------------------------
/.agignore:
--------------------------------------------------------------------------------
1 | build/
2 | inc/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/.jslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": true,
3 | "indent": 2,
4 | "predef": [
5 | "define"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/bin/docker-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eux
4 |
5 | docker build -t metacpan/metacpan-explorer:latest .
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "inc/bootstrap"]
2 | path = app/inc/bootstrap
3 | url = https://github.com/twbs/bootstrap.git
4 |
--------------------------------------------------------------------------------
/bin/docker-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eux
4 |
5 | docker run --rm -it -v "$PWD:/usr/src/app" -p 8080:8080 metacpan/metacpan-explorer:latest
6 |
--------------------------------------------------------------------------------
/inc/bootstrap:
--------------------------------------------------------------------------------
1 | The contents of `inc` were moved to `app/inc` including this submodule.
2 | Run `git submodule update --init` and ignore this directory.
3 |
--------------------------------------------------------------------------------
/app/scripts/settings.js:
--------------------------------------------------------------------------------
1 | define(["model/settings"], function(Settings) {
2 | var settings = new Settings();
3 | settings.fetch();
4 | return settings;
5 | });
6 |
--------------------------------------------------------------------------------
/app/scripts/view/viewport.js:
--------------------------------------------------------------------------------
1 | define(["view", "view/navbar"], function(View, Navbar) {
2 | return View.extend({
3 | tagName: "body",
4 | name: "viewport"
5 | });
6 | });
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8.15.1-alpine
2 |
3 | EXPOSE 8080
4 |
5 | WORKDIR /usr/src/app
6 |
7 | # Bundle app source
8 | COPY . .
9 |
10 | RUN npm install --verbose
11 |
12 | CMD [ "npm", "start" ]
13 |
--------------------------------------------------------------------------------
/app.build.js:
--------------------------------------------------------------------------------
1 | ({
2 | baseUrl: "app/scripts",
3 | out: "build/app.js",
4 | name: "../inc/almond",
5 | include: ["app"],
6 | insertRequire: ["app"],
7 | mainConfigFile: "app/scripts/app.js",
8 | // optimize: "none",
9 | wrap: true
10 | })
11 |
--------------------------------------------------------------------------------
/app/scripts/router.js:
--------------------------------------------------------------------------------
1 | define(["backbone"], function(Backbone) {
2 | return new (Backbone.Router.extend({
3 | start: function() {
4 | Backbone.history.start();
5 | },
6 | routes: {
7 | ":id": "load"
8 | }
9 | }))();
10 | });
11 |
--------------------------------------------------------------------------------
/app.psgi:
--------------------------------------------------------------------------------
1 | use Plack::Builder;
2 | use strict;
3 | use warnings;
4 | use Plack::App::Directory;
5 | builder {
6 | mount "/" => builder {
7 | enable "Plack::Middleware::DirIndex", dir_index => 'index.htm';
8 | mount "/" => Plack::App::Directory->new({ root => "app" })->to_app;
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/app/styles/settings.less:
--------------------------------------------------------------------------------
1 | .settings-toggle {
2 | .glyphicon {
3 | border: none !important;
4 | color: inherit;
5 | .fitted-icon();
6 | transform: rotate(0deg);
7 | transition: transform 1.5s;
8 | }
9 |
10 | &.open {
11 | .glyphicon {
12 | transform: rotate(180deg);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/scripts/view/list-item.js:
--------------------------------------------------------------------------------
1 | define(["underscore", "view"], function(_, View) {
2 | return View.extend({
3 | tagName: "li",
4 | name: "list-item",
5 | template: _.template('<%- model.description || model.id %>'),
6 | events: {
7 | "click a": function() { this.model.setActive(); }
8 | }
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | # We recommend you to keep these unchanged
13 | end_of_line = lf
14 | charset = utf-8
15 |
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | # Don't mess with stuff in inc.
20 | [app/inc/**]
21 | trim_trailing_whitespace = false
22 | insert_final_newline = false
23 |
--------------------------------------------------------------------------------
/app/scripts/template/request.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Invalid JSON
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 | <%= model.response || "" %>
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/scripts/template/navbar.htm:
--------------------------------------------------------------------------------
1 |
7 | MetaCPAN Explorer
8 |
16 |
--------------------------------------------------------------------------------
/env-filter.js:
--------------------------------------------------------------------------------
1 | var text = "";
2 | process.stdin.resume();
3 | process.stdin.on("data", function(data){ text += data; });
4 | process.stdin.on("end", function(){
5 | var bust = (new Date()).getTime().toString(36);
6 | process.stdout.write(
7 | text
8 | .replace(/\{\{bust\}\}/g, bust)
9 | .replace(/(?:)?/g, function(_, env, content){
10 | // Enable the contents of the 'build' sections and remove the 'dev' sections.
11 | return env === 'build' ? content : '';
12 | })
13 | // Remove any remaining blank lines.
14 | .replace(/\n{2,}/g, "\n")
15 | );
16 | });
17 |
--------------------------------------------------------------------------------
/app/styles/logo.less:
--------------------------------------------------------------------------------
1 | div.logo {
2 | position: relative;
3 | margin: 15px 20px 15px 0;
4 | float: left;
5 |
6 | div {
7 | position: absolute;
8 | display: inline;
9 | width: 9px;
10 | height: 9px;
11 | background-color: @brand-primary;
12 | border-radius: 9px;
13 | .box-shadow(inset 1px 1px 2px rgba(0,0,0,.2));
14 | .transition(~"-webkit-transform ease 1s");
15 | .transition(~"transform ease 1s");
16 | }
17 |
18 | .ul {
19 | .translate(0px, 0px);
20 | }
21 | .ur {
22 | .translate(12px, 0px);
23 | }
24 | .ll {
25 | .translate(0px, 12px);
26 | }
27 | .lr {
28 | .translate(12px, 12px);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "//": "Define repository to silence npm warning.",
3 | "repository": {
4 | "type": "git",
5 | "url": "http://github.com/metacpan/metacpan-explorer"
6 | },
7 | "devDependencies": {
8 | "clean-css": ">=2.2",
9 | "less": ">=4.1.3",
10 | "less-plugin-clean-css": ">=1.5.1",
11 | "requirejs": ">=2.1.15"
12 | },
13 | "optionalDependencies": {
14 | "node-static": "x"
15 | },
16 | "scripts": {
17 | "js": "r.js -o app.build.js",
18 | "css": "lessc --clean-css app/styles/main.less > build/styles.css",
19 | "html": "node env-filter.js < app/index.html > build/index.html",
20 | "build": "npm run js && npm run css && npm run html"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/build-production-container.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Build production container
3 | on:
4 | push:
5 | branches:
6 | - master
7 | workflow_dispatch:
8 | jobs:
9 | docker:
10 | runs-on: ubuntu-22.04
11 | name: Docker Push
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: docker build
15 | run: docker build . -t metacpan/metacpan-explorer:latest
16 | - name: Log in to Docker Hub
17 | uses: docker/login-action@v2
18 | with:
19 | username: ${{ secrets.DOCKER_HUB_USER }}
20 | password: ${{ secrets.DOCKER_HUB_TOKEN }}
21 | - name: Push build to Docker Hub
22 | run: docker push metacpan/metacpan-explorer:latest
23 |
--------------------------------------------------------------------------------
/.github/workflows/build-deployment-container.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Build deployment container
3 | on:
4 | push:
5 | branches:
6 | - prod
7 | - staging
8 | workflow_dispatch:
9 | jobs:
10 | docker:
11 | runs-on: ubuntu-22.04
12 | name: Docker Push
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Docker build
16 | run: docker build . -t metacpan/metacpan-explorer:$GITHUB_SHA
17 | - name: Log in to Docker Hub
18 | uses: docker/login-action@v2
19 | with:
20 | username: ${{ secrets.DOCKER_HUB_USER }}
21 | password: ${{ secrets.DOCKER_HUB_TOKEN }}
22 | - name: Push build to Docker hub
23 | run: docker push metacpan/metacpan-explorer:$GITHUB_SHA
24 |
--------------------------------------------------------------------------------
/app/scripts/model/settings.js:
--------------------------------------------------------------------------------
1 | define(["backbone"], function(Backbone) {
2 | var lskey = 'metacpan-explorer-settings';
3 | return Backbone.Model.extend({
4 |
5 | defaults: {
6 | // Fancy "IDE-like" behavior in editor.
7 | editorFeatures: true,
8 | // Apply syntax highlighting to response body.
9 | highlightResponse: true,
10 | // Wrap lines of response body (alternative is horizontal scrollbar).
11 | wrapLines: true,
12 | // Validate request body as you type.
13 | instantValidation: true
14 | },
15 |
16 | fetch: function(){
17 | var attr = {};
18 | try {
19 | attr = JSON.parse(localStorage[lskey]);
20 | }catch(ignore){}
21 | this.set(attr);
22 | },
23 | sync: function(){
24 | localStorage[lskey] = JSON.stringify(this);
25 | }
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/scripts/template/sidebar.htm:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/app/inc/jquery.querystring.js:
--------------------------------------------------------------------------------
1 | ;(function ($) {
2 | $.extend({
3 | getParam: function (name) {
4 | function parseParams() {
5 | var params = {},
6 | e,
7 | a = /\+/g, // Regex for replacing addition symbol with a space
8 | r = /([^&=]+)=?([^&]*)/g,
9 | d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
10 | q = window.location.search.substring(1);
11 |
12 | while (e = r.exec(q))
13 | params[d(e[1])] = d(e[2]);
14 |
15 | return params;
16 | }
17 |
18 | if (!this.queryStringParams)
19 | this.queryStringParams = parseParams();
20 |
21 | return this.queryStringParams[name];
22 | }
23 | });
24 | })(jQuery);
25 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 | MetaCPAN Explorer
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/styles/sidebar.less:
--------------------------------------------------------------------------------
1 | div.sidebar {
2 | height: 100%;
3 | z-index: 1;
4 | position: relative;
5 | width: 200px;
6 | border-top: 1px solid rgba(0, 0, 0, 0.15);
7 | text-shadow: 0 1px 0 #fff;
8 | background-color: #f5f5f5;
9 | box-shadow: inset -1px 0 0 #e5e5e5;
10 | overflow-y: auto;
11 |
12 | .nav {
13 | padding-top: (@navbar-height - 1 + 10);
14 |
15 | > li {
16 | &.input {
17 | padding: 0 15px;
18 |
19 | input {
20 | cursor: pointer;
21 | }
22 | }
23 |
24 | /* Nav: first level */
25 |
26 | > a {
27 | display: block;
28 | color: #666;
29 | padding: 4px 15px;
30 |
31 | &:hover,
32 | &:focus {
33 | text-decoration: none;
34 | border-right: 1px solid #d5d5d5;
35 | }
36 | }
37 | }
38 | > .active {
39 | > a,
40 | &:hover > a,
41 | &:focus > a {
42 | cursor: default;
43 | font-weight: 500;
44 | color: #b94a48;
45 | background-color: transparent;
46 | border-right: 1px solid #b94a48;
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/scripts/template/settings.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 | <%= item(checkbox('editorFeatures'), 'Use editor enhancements') %>
13 |
14 | <%= item(checkbox('highlightResponse'), 'Highlight JSON response') %>
15 |
16 | <%= item(checkbox('wrapLines'), 'Wrap JSON response lines') %>
17 |
18 | <%= item(checkbox('instantValidation'), 'Validate JSON as you type') %>
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/scripts/model.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "underscore", "backbone"], function($, _, Backbone) {
2 | return Backbone.Model.extend({
3 | /**
4 | * Returns a human readable description of the model. It is used
5 | * in the progress bar as subtitle.
6 | *
7 | * TODO: localize, i.e. return object with titles for all languages.
8 | *
9 | * @return {String}
10 | */
11 | getTitle: function() {
12 | var title = this.get("title");
13 | return _.isObject(title) ? title[languageSelectGlobal] : title;
14 | },
15 |
16 | /**
17 | * Calls `setActive(model, options)` on the collection.
18 | *
19 | * @param {options} options
20 | */
21 | setActive: function(options) {
22 | if(!this.collection){ return this; }
23 | return this.collection.setActive(this, options);
24 | },
25 | /**
26 | * Returns true if model is active.
27 | *
28 | * @return {Boolean}
29 | */
30 | isActive: function() {
31 | return !!this.get("active");
32 | },
33 | sync: function(method, model, options) {
34 | var store = model.store || model.collection.store;
35 | return store ? store.sync.apply(store, arguments) : $.when();
36 | }
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // NOTE: You should get the github token from the private conf repo
4 | // and export it as GITHUB_TOKEN before running this server.
5 |
6 | var fs = require('fs');
7 | var Static = require('node-static');
8 |
9 | var startServer = function(dir, port) {
10 | console.log("Serving", dir, 'on port', port);
11 |
12 | var httpd = new Static.Server(dir, {
13 | headers: {
14 | "Cache-Control": "no-cache, must-revalidate"
15 | }
16 | });
17 |
18 | require('http').createServer(function (request, response) {
19 | request.addListener('end', function () {
20 |
21 | if (request.url === '/github.js') {
22 | response.writeHead(200, {'Content-Type': 'text/javascript'});
23 | response.end('function github_token () { return ' + JSON.stringify(process.env.GITHUB_TOKEN || 'token') + '; }');
24 | }
25 | else {
26 | httpd.serve(request, response);
27 | }
28 |
29 | }).resume();
30 | }).listen(port);
31 | };
32 |
33 | var port = process.env.PORT || 8080;
34 |
35 | // 0: node, 1: server.js, 2: first arg
36 | var dir = process.argv[2] || 'app';
37 |
38 | fs.stat(dir, function(err, stats){
39 | if( err || !stats.isDirectory() ){
40 | throw(dir + " is not a directory");
41 | }
42 | startServer(dir, port);
43 | });
44 |
--------------------------------------------------------------------------------
/app/scripts/view/sidebar.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jquery",
3 | "view",
4 | "view/list-item",
5 | "tpl!template/sidebar.htm"
6 | ], function($, View, ItemView, template) {
7 | return View.extend({
8 | name: "sidebar",
9 | template: template,
10 | events: {
11 | "click .settings-toggle": "toggleSettings",
12 | "click .input": function(e) {
13 | $(e.target).focus().select();
14 | }
15 | },
16 | initialize: function(options) {
17 | this.listenTo(this.collection, "sync", this.render);
18 | this.listenTo(this.collection, "change:active", this.updateCurl);
19 | this.settingsView = options.settingsView;
20 | },
21 | updateCurl: function(model, value) {
22 | this.$("input").val(value ? model.getCurl() : "");
23 | },
24 | render: function() {
25 | var self = this;
26 | var model = this.collection.getActive();
27 | View.prototype.render.call(this, {
28 | model: model ? model.toJSON() : null
29 | });
30 | var $nav = this.$("ul.nav .examples");
31 | this.collection.each(function(item) {
32 | if(!item.id){
33 | return;
34 | }
35 | $nav.after(self.add(new ItemView({ model: item })).render().el);
36 | });
37 | return this;
38 | },
39 | toggleSettings: function(e) {
40 | e.preventDefault();
41 | this.settingsView.show();
42 | }
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/app/styles/dragbar.less:
--------------------------------------------------------------------------------
1 | .request {
2 | // Position the outer .dragbar with the percentage so its between the two
3 | // areas, then relative position the child .handle so that it can be large
4 | // enough to grab, but overall the draggable element seems "in the middle".
5 | // Leave the style as a single pixel bar; Make the handle wider so its easier
6 | // to grab but leave it transparent so the single pixel bar shows through.
7 | .dragbar {
8 | background: transparent;
9 | left: 50%;
10 | position: absolute;
11 | width: 1px;
12 | z-index: 10;
13 |
14 | &, .handle {
15 | border: 0;
16 | bottom: 0;
17 | height: 100%;
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | .handle {
23 | // ...but don't use background:transparent as that makes IE act like it's
24 | // smaller than it is... so fake it with a translucent white.
25 | background: #fff;
26 | opacity: 0;
27 | cursor: col-resize;
28 | left: -5px;
29 | position: relative;
30 | width: 9px;
31 | }
32 | }
33 | }
34 |
35 | .user-select (@val) {
36 | -moz-user-select: @val;
37 | -moz-user-select: -moz-@val;
38 | -khtml-user-select: @val;
39 | -webkit-user-select: @val;
40 | -ms-user-select: @val;
41 | user-select: @val;
42 | }
43 | // Don't select text while dragging.
44 | body.dragging * {
45 | .user-select(none);
46 | }
47 |
--------------------------------------------------------------------------------
/app/styles/main.less:
--------------------------------------------------------------------------------
1 | @import "bootstrap.less";
2 |
3 | // Make .btn-primary the same color as the dots in the logo.
4 | @brand-primary: #da2037;
5 | @glyphicons-font-path: "/fonts";
6 |
7 | html, body {
8 | height: 100%;
9 | }
10 |
11 | @import "request.less";
12 | @import "dragbar.less";
13 | @import "sidebar.less";
14 | @import "settings.less";
15 |
16 | @import "logo.less";
17 |
18 | // Force import as less to enusre the contents are embedded in build file.
19 | @import (less) "../inc/highlight-default.css";
20 |
21 | textarea,
22 | input[type="text"],
23 | input[type="password"],
24 | input[type="datetime"],
25 | input[type="datetime-local"],
26 | input[type="date"],
27 | input[type="month"],
28 | input[type="time"],
29 | input[type="week"],
30 | input[type="number"],
31 | input[type="email"],
32 | input[type="url"],
33 | input[type="search"],
34 | input[type="tel"],
35 | input[type="color"] {
36 | // Focus state
37 | &:focus {
38 | border-color: @input-border;
39 | outline: 0;
40 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
41 | }
42 | }
43 |
44 | * {
45 | -webkit-tap-highlight-color: rgba(0,0,0,0);
46 | -webkit-overflow-scrolling: touch;
47 | }
48 |
49 | .navbar-fixed-top {
50 | .box-shadow(0 1px 3px rgba(0,0,0,.2));
51 | }
52 |
53 | .modal {
54 | .modal-header {
55 | // Make header bg different than body but don't lose the round (top) corners.
56 | background-color: #eee;
57 | .border-top-radius(inherit);
58 | }
59 | }
60 |
61 | .fitted-icon() {
62 | display: inline-block;
63 | height: @font-size-base;
64 | line-height: @font-size-base;
65 | margin: 0;
66 | padding: 0;
67 | text-decoration: none !important;
68 | width: @font-size-base;
69 | }
70 |
--------------------------------------------------------------------------------
/app/scripts/collection.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "underscore", "backbone"], function($, _, Backbone) {
2 | return Backbone.Collection.extend({
3 | /**
4 | * Sets the `active` attribute of all models but `model` to `false`.
5 | * Triggers the `active` event with the `model` passed as first parameter.
6 | *
7 | * @param {App.Model} model Model that is set to active
8 | * @param {Object} options Options that are passed to `set` such as `{ silent: true }`
9 | * @return {App.Model} returns `model`
10 | */
11 | setActive: function(model, options) {
12 | _.invoke(this.without(model), "set", "active", false, options);
13 | model.set("active", true, options);
14 | options = options || {};
15 | return model;
16 | },
17 | /**
18 | * Get active model of collection
19 | *
20 | * @return {App.Model} Returns a model of one is active.
21 | */
22 | getActive: function() {
23 | return this.find(function(model) { return model.isActive(); });
24 | },
25 | /**
26 | * Calls fetch on all models and returns a $.Deferred object that resolves
27 | * when all models have been fetched.
28 | *
29 | * @return {$.Deferred}
30 | */
31 | fetchAll: function() {
32 | var self = this;
33 | return $.when.apply($, this.invoke("fetch")).pipe(function(){ return self; });
34 | },
35 | sync: function(method, model, options) {
36 | var store = this.store || (this.model && this.model.prototype.store);
37 | return store ? store.sync.apply(store, arguments) : $.when();
38 | },
39 | newModel: function(attributes, options) {
40 | var model = new this.model(attributes, options);
41 | this.add(model);
42 | return model;
43 | }
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # metacpan-explorer
2 |
3 | ## Clone
4 |
5 | This repo includes Bootstrap as a submodule,
6 | so after cloning (or pulling) make sure your submodule is up to date:
7 |
8 | ```
9 | git clone https://github.com/metacpan/metacpan-explorer.git
10 | git submodule init && git submodule update
11 | ```
12 |
13 | ## Docker Development
14 |
15 | ###Build an image:
16 |
17 | `docker build -t metacpan/metacpan-explorer .`
18 |
19 | ###Run your image:
20 |
21 | `docker run -p 8080:8080 metacpan/metacpan-explorer`
22 |
23 | ###View in your browser:
24 |
25 | http://localhost:8080/
26 |
27 |
28 | ## Dockerless Development
29 |
30 | ### Rebuilding the static files
31 |
32 | In the project root run
33 |
34 | ./build.sh
35 |
36 | It will install dependencies via npm
37 | and regenerate the static files into the `build` directory.
38 |
39 | The [developer vm](https://github.com/metacpan/metacpan-developer)
40 | has everything you need for this.
41 |
42 | To run it somewhere else you'll need to make sure you have
43 | [node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed.
44 |
45 | ### Running the development server
46 |
47 | You can run `node server.js` (or `npm start`) to launch a dev server.
48 | See the comments in `server.js` for additional instructions.
49 |
50 | ## Adding Examples
51 |
52 | Log in using the credentials from https://github.com/metacpan/metacpan-credentials/blob/master/github and go to https://gist.github.com/.
53 | Create a new **public** gist with the following file structure:
54 |
55 | * endpoint.txt
56 |
57 | Contains the path to the API endpoint (e.g. `/v1/author/_search`)
58 |
59 | * body.json
60 |
61 | Contains the JSON encoded body of the request. Can be `null` if the request has no body.
62 |
63 | Give the gist a useful description and save. The example should then show up on explorer.metacpan.org
64 |
--------------------------------------------------------------------------------
/app/scripts/view/settings.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jquery",
3 | "underscore",
4 | "backbone",
5 | "tpl!template/settings.htm",
6 | "bootstrap-modal"
7 | ], function($, _, Backbone, template){
8 |
9 | var _helpers = {
10 | //e: _.escape,
11 |
12 | checkbox: function(name) {
13 | return '';
14 | },
15 |
16 | item: function() {
17 | return [
18 | ''
21 | ].join('');
22 | }
23 | };
24 |
25 | var _bind_helpers = function(context) {
26 | return _.reduce(_helpers, function(memo, func, name) {
27 | memo[name] = _.bind(func, context);
28 | return memo;
29 | }, {});
30 | };
31 |
32 | return Backbone.View.extend({
33 | tagName: 'div',
34 | id: 'settings',
35 | template: template,
36 |
37 | events: {
38 | "change input[type=checkbox]": "toggleCheckbox",
39 | "click .cancel": "hide",
40 | "click .save": "save"
41 | },
42 |
43 | initialize: function() {
44 | this.helpers = _bind_helpers(this);
45 | },
46 |
47 | bsmodal: function(arg) {
48 | this.$modal.modal(arg);
49 | },
50 |
51 | render: function(options) {
52 | this.changes = {};
53 | this.$el.html(this.template(_.extend({
54 | model: this.model.toJSON()
55 | }, this.helpers, options)));
56 | this.$modal = $('#settings .modal');
57 | this.$toggle = $('.settings-toggle');
58 | return this;
59 | },
60 |
61 | toggleCheckbox: function(e) {
62 | var $el = $(e.target),
63 | name = $el.attr('name') || $el.attr('id');
64 | this.changes[name] = $el.prop('checked');
65 | },
66 |
67 | save: function(){
68 | this.model.save(this.changes);
69 | this.hide();
70 | },
71 |
72 | hide: function() {
73 | this.$toggle.removeClass('open');
74 | this.bsmodal('hide');
75 | },
76 | show: function() {
77 | // Reset form elements to current settings.
78 | this.render();
79 | this.$toggle.addClass('open');
80 | this.bsmodal();
81 | }
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/app/scripts/view.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "underscore", "backbone"], function($, _, Backbone) {
2 | return Backbone.View.extend({
3 | constructor: function(options) {
4 | options = options || {};
5 | var name = this.name || options.name;
6 | if(!this.template && name) {
7 | var template = $("#template-" + name).html();
8 | if(template){
9 | this.template = _.template(template);
10 | }
11 | }
12 | if(this.name && (!this.attributes || !this.attributes.class)) {
13 | this.attributes = _.extend(
14 | this.attributes || {},
15 | { "class": name }
16 | );
17 | }
18 | if(options.model){
19 | this.listenTo(options.model, "change:active", function(model, value) {
20 | this.$el.toggleClass("active", value);
21 | });
22 | }
23 | Backbone.View.apply(this, arguments);
24 | },
25 | triggerSelect: function() {
26 | this.trigger("select", this.model);
27 | },
28 | proxy: function(from, event) {
29 | return from.bind(event, _.bind(this.trigger, this, event));
30 | },
31 | add: function() {
32 | this.views = this.views || [];
33 | this.views.push.apply(this.views, arguments);
34 | return arguments.length === 1 ? arguments[0] : arguments;
35 | },
36 | remove: function() {
37 | var args = arguments;
38 | _.each(this.views || [], function(view) {
39 | view.remove.apply(view, args);
40 | });
41 | return Backbone.View.prototype.remove.apply(this, arguments);
42 | },
43 | removeViews: function() {
44 | var views = this.views;
45 | _.invoke(views, "remove");
46 | this.views = [];
47 | return views;
48 | },
49 | render: function(options) {
50 | this.removeViews();
51 | var template = this.options.template || this.template;
52 | options = _.extend({
53 | model: this.model ? this.model.toJSON() : {},
54 | collection: this.collection ? this.collection.toJSON() : []
55 | }, options || {});
56 | if(template){
57 | this.$el.html(template(options));
58 | }
59 | if(this.model){
60 | this.$el.toggleClass("active", this.model.isActive());
61 | }
62 | return this;
63 | }
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/app/scripts/view/navbar.js:
--------------------------------------------------------------------------------
1 | define([
2 | "underscore",
3 | "view",
4 | "tpl!template/navbar.htm",
5 | "bootstrap-typeahead",
6 | "bootstrap-tooltip"
7 | ], function(_, View, template) {
8 | return View.extend({
9 | loading: 0,
10 | loadingInterval: null,
11 | template: template,
12 | name: "navbar",
13 | attributes: {
14 | "class": "navbar navbar-fixed-top"
15 | },
16 | events: {
17 | "submit form" : function() {
18 | this.collection.getActive().set({
19 | endpoint: this.$endpoint.val()
20 | }).request();
21 | return false;
22 | }
23 | },
24 | initialize: function() {
25 | this.listenTo(this.collection, "load:start", this.startLoading);
26 | this.listenTo(this.collection, "load:end", this.endLoading);
27 | this.listenTo(this.collection, "change:active", this.render);
28 | this.listenTo(this.collection, "change:endpoint", this.render);
29 | },
30 | startLoading: function() {
31 | if(!this.loading) {
32 | this.loadingInterval = window.setInterval(_.bind(this.animateLogo, this), 2000);
33 | _.defer(_.bind(this.animateLogo, this));
34 | }
35 | this.loading++;
36 | },
37 | endLoading: function() {
38 | this.loading--;
39 | if(!this.loading){
40 | window.clearInterval(this.loadingInterval);
41 | }
42 | },
43 | animateLogo: function() {
44 | var ll = this.$(".ll"),
45 | lr = this.$(".lr"),
46 | ur = this.$(".ur"),
47 | ul = this.$(".ul");
48 | ll.toggleClass("ll ul");
49 | lr.toggleClass("lr ll");
50 | ur.toggleClass("ur lr");
51 | ul.toggleClass("ul ur");
52 | },
53 | render: function(model) {
54 | model = model || this.collection.getActive();
55 | View.prototype.render.call(this, { model: model ? model.toJSON() : {} });
56 | this.$endpoint = this.$("input").typeahead({
57 | source: [
58 | "/v1/file",
59 | "/v1/author",
60 | "/v1/release",
61 | "/v1/distribution",
62 | "/v1/module",
63 | "/v1/favorite",
64 | "/v1/rating"
65 | ]
66 | });
67 | this.$("button").tooltip({ placement: "bottom", trigger: "hover", container: "body" });
68 | return this;
69 | }
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/app/styles/bootstrap.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.0.0
3 | *
4 | * Copyright 2013 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world by @mdo and @fat.
9 | */
10 |
11 | // Core variables and mixins
12 | @import "../inc/bootstrap/less/variables.less";
13 | @import "../inc/bootstrap/less/mixins.less";
14 |
15 | // Reset
16 | @import "../inc/bootstrap/less/normalize.less";
17 | //@import "../inc/bootstrap/less/print.less";
18 |
19 | // Core CSS
20 | @import "../inc/bootstrap/less/scaffolding.less";
21 | @import "../inc/bootstrap/less/type.less";
22 | @import "../inc/bootstrap/less/code.less";
23 | @import "../inc/bootstrap/less/grid.less";
24 |
25 | //@import "../inc/bootstrap/less/tables.less";
26 | @import "../inc/bootstrap/less/forms.less";
27 | @import "../inc/bootstrap/less/buttons.less";
28 |
29 | // Components: common
30 | //@import "../inc/bootstrap/less/component-animations.less";
31 | @import "../inc/bootstrap/less/glyphicons.less";
32 | @import "../inc/bootstrap/less/dropdowns.less";
33 | @import "../inc/bootstrap/less/list-group.less";
34 | //@import "../inc/bootstrap/less/panels.less";
35 | //@import "../inc/bootstrap/less/wells.less";
36 | @import "../inc/bootstrap/less/close.less";
37 |
38 | // Components: Nav
39 | @import "../inc/bootstrap/less/navs.less";
40 | @import "../inc/bootstrap/less/navbar.less";
41 | @import "../inc/bootstrap/less/button-groups.less";
42 | //@import "../inc/bootstrap/less/breadcrumbs.less";
43 | //@import "../inc/bootstrap/less/pagination.less";
44 | //@import "../inc/bootstrap/less/pager.less";
45 |
46 | // Components: Popovers
47 | @import "../inc/bootstrap/less/modals.less";
48 | @import "../inc/bootstrap/less/tooltip.less";
49 | //@import "../inc/bootstrap/less/popovers.less";
50 |
51 | // Components: Misc
52 | //@import "../inc/bootstrap/less/alerts.less";
53 | //@import "../inc/bootstrap/less/thumbnails.less";
54 | //@import "../inc/bootstrap/less/media.less";
55 | @import "../inc/bootstrap/less/labels.less";
56 | //@import "../inc/bootstrap/less/badges.less";
57 | //@import "../inc/bootstrap/less/progress-bars.less";
58 | //@import "../inc/bootstrap/less/accordion.less";
59 | //@import "../inc/bootstrap/less/carousel.less";
60 | //@import "../inc/bootstrap/less/jumbotron.less";
61 |
62 | // Utility classes
63 | @import "../inc/bootstrap/less/utilities.less"; // Has to be last to override when necessary
64 | //@import "../inc/bootstrap/less/responsive-utilities.less";
65 |
--------------------------------------------------------------------------------
/app/scripts/store/gist.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "underscore", "backbone"], function($, _, Backbone) {
2 |
3 | var Storage = function() { return; };
4 | _.extend(Storage.prototype, Backbone.Events, {
5 | config: {
6 | user: 'metacpan-user',
7 | token: github_token()
8 | },
9 | request: function(options) {
10 | //options.url += "?access_token=" + this.config.token;
11 | return $.ajax(options);
12 | },
13 | find: function(model, options) {
14 | return this.request({
15 | url: "https://api.github.com/gists/" + model.id,
16 | context: this,
17 | dataType: "json"
18 | });
19 | },
20 | findAll: function() {
21 | return $.ajax({
22 | url: "https://api.github.com/users/" + this.config.user + "/gists",
23 | context: this,
24 | dataType: "json"
25 | });
26 | },
27 | create: function(model, options) {
28 | var gist = {
29 | public: false,
30 | files: {
31 | "endpoint.txt": {
32 | content: model.get("endpoint")
33 | },
34 | //"response.json": {
35 | // content: model.get("response")
36 | //},
37 | "body.json": {
38 | content: model.get("body") || "null"
39 | }
40 | }
41 | };
42 | return this.request({
43 | url: "https://api.github.com/gists" + (model.id ? "/" + model.id : ""),
44 | type: model.id ? "PATCH" : "POST",
45 | context: this,
46 | dataType: "json",
47 | contentType: "application/json",
48 | data: JSON.stringify(gist)
49 | }).then(function(res) { return { id: res.id }; });
50 | },
51 | update: function() { return this.create.apply(this, arguments); },
52 | destroy: function() { throw "destroy not implemented in " + this; },
53 | sync: function(method, model, options) {
54 | model.trigger("load:start");
55 | options = options || {};
56 | var resp;
57 | switch (method) {
58 | case "read": resp = model.id ? this.find(model, options) : this.findAll(model, options); break;
59 | case "create": resp = this.create(model, options); break;
60 | case "update": resp = this.update(model, options); break;
61 | case "delete": resp = this.destroy(model, options); break;
62 | }
63 | resp.always(function() { model.trigger("load:end"); });
64 | return resp.fail(options.error || $.noop).done(options.success || $.noop);
65 | }
66 | });
67 | return Storage;
68 | });
69 |
--------------------------------------------------------------------------------
/app/inc/tpl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adapted from the official plugin text.js
3 | *
4 | * Uses UnderscoreJS micro-templates : http://documentcloud.github.com/underscore/#template
5 | * @author Julien Cabanès
6 | * @version 0.3
7 | *
8 | * @license RequireJS text 0.24.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
9 | * Available via the MIT or new BSD license.
10 | * see: http://github.com/jrburke/requirejs for details
11 | */
12 |
13 | define(['module', 'text', 'underscore'], function(module, text, _) {
14 | var settings = module.config() || {};
15 |
16 | return {
17 | /**
18 | * Dynamically loads your module by using text! to require the content, and then return the results of _.template.
19 | * @param name
20 | * @param req
21 | * @param onLoad
22 | * @param config
23 | */
24 | load: function(name, req, onLoad, config) {
25 | if (config.isBuild) {
26 | text.load.call(text, name, req, onLoad, config);
27 | return;
28 | }
29 | req([ 'text!' + name ], function(content) {
30 | onLoad(content
31 | ? _.template(content, undefined, settings)
32 | : function() { return ''; });
33 | });
34 | },
35 | /**
36 | * Used by the optimizer to compile your templates. Logs detailed error messages whenever an invalid template is
37 | * encountered.
38 | * @param pluginName
39 | * @param moduleName
40 | * @param write
41 | * @param config
42 | */
43 | write: function(pluginName, moduleName, write, config) {
44 | text.write.call(text, pluginName, moduleName, {
45 | asModule: function(name, contents) {
46 | contents = contents.substr(contents.indexOf('return') + 7);
47 | contents = contents.substr(0, contents.lastIndexOf(';') - 3);
48 | try {
49 | var template = _.template(eval(contents), undefined, settings);
50 | write.asModule(pluginName + "!" + moduleName,
51 | "define(function() { return " + template.source + " });");
52 | }
53 | catch (err) {
54 | console.error('~~~~~');
55 | console.error('FAILED TO COMPILE ' + pluginName + "!" + moduleName);
56 | console.error('Error:\t\t' + String(err));
57 | console.error('\n');
58 | if (err && err.source) {
59 | console.error('Error Source:\n');
60 | console.error(err.source);
61 | console.error('\n\n');
62 | }
63 | console.error('Original Contents:\n');
64 | console.error(contents);
65 | console.error('\n\n');
66 | throw err;
67 | }
68 | }
69 | }, config);
70 | }
71 | };
72 | });
--------------------------------------------------------------------------------
/app/scripts/model/request.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "underscore", "model", "store/gist"], function($, _, Model, Store) {
2 | return Model.extend({
3 | store: new Store(),
4 | defaults: {
5 | endpoint: null,
6 | body: null,
7 | response: null,
8 | active: false
9 | },
10 | getCurl: function() {
11 | if(!this.get("endpoint")){ return ""; }
12 | var curl = "curl " + (this.get("body") ? "-XPOST " : "") +
13 | "'https://fastapi.metacpan.org" + this.get("endpoint") + "'";
14 | if(this.get("body")){
15 | curl += " -d \"$(curl -Ls gist.github.com/" + this.store.config.user + "/" + this.id + "/raw/body.json)\"";
16 | }
17 | return curl;
18 | },
19 | toJSON: function() {
20 | var json = Model.prototype.toJSON.apply(this, arguments);
21 | _.extend(json, { curl: this.getCurl() });
22 | return json;
23 | },
24 | parse: function(res) {
25 | if(!res.files){ return res; }
26 | return _.extend(res, {
27 | body: (res.files["body.json"] && res.files["body.json"].content !== "null" ?
28 | res.files["body.json"].content : null),
29 | endpoint: res.files["endpoint.txt"].content
30 | });
31 | },
32 | request: function(options) {
33 | // Notify that the request has been initiated.
34 | this.trigger("pending", true);
35 |
36 | options = options || {};
37 | var self = this;
38 | var body = this.get("body");
39 | return $.ajax({
40 | url: "https://fastapi.metacpan.org" + this.get("endpoint"),
41 | dataType: "text",
42 | type: (body ? "POST" : "GET"),
43 | data: (body || null)
44 | }).then(function(res) {
45 | self.set({
46 | response: res,
47 | success: true
48 | });
49 | return self;
50 | }, function(res) {
51 | self.set({
52 | response: res.responseText,
53 | success: false
54 | });
55 | return self;
56 | }).always(function(model) {
57 | // Notify that request completed
58 | // ("change:response" won't fire if the response text is the same).
59 | model.trigger("pending", false);
60 |
61 | if(options.gist !== false && model.get("public") !== true){
62 | model.save();
63 | }
64 | });
65 | },
66 | validate: function(attributes) {
67 | var json = attributes.body;
68 | try {
69 | if( json ){
70 | JSON.parse(json);
71 | }
72 | }
73 | catch(e) {
74 | return e;
75 | }
76 | }
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/app/scripts/view/dragbar.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jquery",
3 | "underscore",
4 | "backbone",
5 | ], function($, _, Backbone) {
6 | var lskey = 'metacpan-explorer-dragbar',
7 | cancel_event = function () { return false; };
8 |
9 | return Backbone.View.extend({
10 | events: {
11 | // The move/up events are handled in the start/stop methods.
12 | 'mousedown': 'start'
13 | },
14 |
15 | initialize: function (options) {
16 | this.dragging = false;
17 | this.$body = $('body');
18 | this.$container = options.container;
19 | this.$left = options.left;
20 | this.$right = options.right;
21 | this.fetch();
22 | this._bound_move = _.bind(this.move, this);
23 | this._bound_stop = _.bind(this.stop, this);
24 | },
25 |
26 | fetch: function () {
27 | try {
28 | this.position = localStorage[lskey] || 50;
29 | }
30 | catch (e) {
31 | this.position = 50;
32 | }
33 | },
34 |
35 | render: function () {
36 | this.setPosition(this.position);
37 | return this;
38 | },
39 |
40 | start: function (e) {
41 | // Only with left-button.
42 | if(e.which !== 1){ return; }
43 |
44 | this.dragging = true;
45 |
46 | // Cache this here as it seems unlikely to change while dragging.
47 | // Subtract one to help center the bar under the cursor.
48 | this.offsetLeft = this.$container.offset().left - 1;
49 | this.totalWidth = this.$container.width();
50 |
51 | this.$body
52 | // Don't let the browser think we're trying to select text.
53 | .addClass('dragging')
54 | .on('selectstart.dragbar', cancel_event)
55 | // Listen to mouse move/up on whole body so that dragging ends
56 | // even if the mouse moves off the bar.
57 | // Only listen to body mouse events when dragging.
58 | .on('mousemove.dragbar', this._bound_move)
59 | .on('mouseup.dragbar', this._bound_stop);
60 | },
61 |
62 | move: function (e) {
63 | if(!this.dragging){ return; }
64 |
65 | // Convert position to percentage of width
66 | // so it can easily be used as width for surrounding elements.
67 | var pos = ((e.pageX - this.offsetLeft) / this.totalWidth) * 100;
68 |
69 | // Don't let either box get too small.
70 | if( pos >= 10 && pos <= 90 ){
71 | this.setPosition(pos);
72 | }
73 | },
74 |
75 | stop: function () {
76 | if(!this.dragging){ return; }
77 |
78 | this.dragging = false;
79 |
80 | this.$body
81 | .off('.dragbar')
82 | .removeClass('dragging');
83 |
84 | this.save();
85 | },
86 |
87 | save: function () {
88 | localStorage[lskey] = this.position;
89 | },
90 |
91 | setPosition: function (pos) {
92 | this.position = pos;
93 | this.$el.css('left', pos + '%');
94 | this.$left.css('width', pos + '%');
95 | this.$right.css('width', (100 - pos) + '%');
96 | }
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "master" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "master" ]
20 | schedule:
21 | - cron: '25 17 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/app/styles/request.less:
--------------------------------------------------------------------------------
1 | div.request {
2 | position: absolute;
3 | padding-left: 200px;
4 | top: 0px;
5 | height: 100%;
6 | width: 100%;
7 |
8 | div.request-inner {
9 | position: relative;
10 | height: 100%;
11 | }
12 |
13 | div.editor,
14 | div.response {
15 | position: absolute;
16 | top: 0;
17 | bottom: 0;
18 | top: (@navbar-height - 1);
19 | width: 50%;
20 | }
21 |
22 | div.editor {
23 | .label {
24 | position: absolute;
25 | right: 10px;
26 | top: 10px;
27 | }
28 |
29 | textarea {
30 | margin: 0;
31 | height: 100%;
32 | display: block;
33 | position: absolute;
34 | bottom: 0;
35 | top: 0;
36 | tab-size: 2;
37 | font-family: @font-family-monospace;
38 | border-radius: 0;
39 | resize: none;
40 | border-left-width: 0;
41 | font-size: 0.8em;
42 | line-height: 1.4;
43 | color: #000;
44 |
45 | // Redo the placeholder style with increased specificity.
46 | // (It looks fine most of the time but in my vm IE it looks black.)
47 | .placeholder();
48 | }
49 | }
50 | }
51 |
52 | div.response {
53 | right: 0px;
54 |
55 | pre {
56 | position: absolute;
57 | bottom: 0px;
58 | top: 0px;
59 | width: 100%;
60 | tab-size: 2;
61 | margin: 0;
62 | overflow: auto;
63 | border-radius: 0;
64 | border-left-width: 0;
65 | word-wrap: normal;
66 | font-size: 0.8em;
67 | line-height: 1.4;
68 |
69 | code {
70 | background: inherit;
71 | font: inherit;
72 | margin: 0;
73 | padding: 0;
74 | white-space: pre;
75 |
76 | &.wrap {
77 | white-space: pre-wrap;
78 | }
79 | }
80 |
81 | .hljs-attribute {
82 | color: #550;
83 | }
84 | }
85 |
86 | }
87 |
88 | .keyframes-transform(@name, @from, @to) {
89 | @-moz-keyframes @name {
90 | from { -moz-transform: @from; }
91 | to { -moz-transform: @to; }
92 | }
93 | @-webkit-keyframes @name {
94 | from { -webkit-transform: @from; }
95 | to { -webkit-transform: @to; }
96 | }
97 | @-o-keyframes @name {
98 | from { -o-transform: @from; }
99 | to { -o-transform: @to; }
100 | }
101 | @keyframes @name {
102 | from { transform: @from; }
103 | to { transform: @to; }
104 | }
105 | }
106 | .keyframes-transform(spin, rotate(0deg), rotate(360deg));
107 |
108 | .animation(@args) {
109 | -moz-animation: @args;
110 | -webkit-animation: @args;
111 | -o-animation: @args;
112 | animation: @args;
113 | }
114 |
115 | .response {
116 | .indicator {
117 | .animation(spin 2.5s 0s infinite linear normal);
118 | .fitted-icon();
119 | color: #888;
120 | display: none;
121 | padding: 1px;
122 | position: absolute;
123 | right: 50%;
124 | top: 25%;
125 | width: auto;
126 | z-index: 10;
127 | }
128 | &.pending {
129 | .indicator {
130 | display: block;
131 | }
132 | pre code {
133 | opacity: 0.5;
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/app/inc/highlight-default.css:
--------------------------------------------------------------------------------
1 | // https://highlightjs.org/download/
2 | /*
3 |
4 | Original style from softwaremaniacs.org (c) Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | background: #f0f0f0;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs,
17 | .hljs-subst,
18 | .hljs-tag .hljs-title,
19 | .nginx .hljs-title {
20 | color: black;
21 | }
22 |
23 | .hljs-string,
24 | .hljs-title,
25 | .hljs-constant,
26 | .hljs-parent,
27 | .hljs-tag .hljs-value,
28 | .hljs-rules .hljs-value,
29 | .hljs-preprocessor,
30 | .hljs-pragma,
31 | .haml .hljs-symbol,
32 | .ruby .hljs-symbol,
33 | .ruby .hljs-symbol .hljs-string,
34 | .hljs-template_tag,
35 | .django .hljs-variable,
36 | .smalltalk .hljs-class,
37 | .hljs-addition,
38 | .hljs-flow,
39 | .hljs-stream,
40 | .bash .hljs-variable,
41 | .apache .hljs-tag,
42 | .apache .hljs-cbracket,
43 | .tex .hljs-command,
44 | .tex .hljs-special,
45 | .erlang_repl .hljs-function_or_atom,
46 | .asciidoc .hljs-header,
47 | .markdown .hljs-header,
48 | .coffeescript .hljs-attribute {
49 | color: #800;
50 | }
51 |
52 | .smartquote,
53 | .hljs-comment,
54 | .hljs-annotation,
55 | .hljs-template_comment,
56 | .diff .hljs-header,
57 | .hljs-chunk,
58 | .asciidoc .hljs-blockquote,
59 | .markdown .hljs-blockquote {
60 | color: #888;
61 | }
62 |
63 | .hljs-number,
64 | .hljs-date,
65 | .hljs-regexp,
66 | .hljs-literal,
67 | .hljs-hexcolor,
68 | .smalltalk .hljs-symbol,
69 | .smalltalk .hljs-char,
70 | .go .hljs-constant,
71 | .hljs-change,
72 | .lasso .hljs-variable,
73 | .makefile .hljs-variable,
74 | .asciidoc .hljs-bullet,
75 | .markdown .hljs-bullet,
76 | .asciidoc .hljs-link_url,
77 | .markdown .hljs-link_url {
78 | color: #080;
79 | }
80 |
81 | .hljs-label,
82 | .hljs-javadoc,
83 | .ruby .hljs-string,
84 | .hljs-decorator,
85 | .hljs-filter .hljs-argument,
86 | .hljs-localvars,
87 | .hljs-array,
88 | .hljs-attr_selector,
89 | .hljs-important,
90 | .hljs-pseudo,
91 | .hljs-pi,
92 | .haml .hljs-bullet,
93 | .hljs-doctype,
94 | .hljs-deletion,
95 | .hljs-envvar,
96 | .hljs-shebang,
97 | .apache .hljs-sqbracket,
98 | .nginx .hljs-built_in,
99 | .tex .hljs-formula,
100 | .erlang_repl .hljs-reserved,
101 | .hljs-prompt,
102 | .asciidoc .hljs-link_label,
103 | .markdown .hljs-link_label,
104 | .vhdl .hljs-attribute,
105 | .clojure .hljs-attribute,
106 | .asciidoc .hljs-attribute,
107 | .lasso .hljs-attribute,
108 | .coffeescript .hljs-property,
109 | .hljs-phony {
110 | color: #88f;
111 | }
112 |
113 | .hljs-keyword,
114 | .hljs-id,
115 | .hljs-title,
116 | .hljs-built_in,
117 | .css .hljs-tag,
118 | .hljs-javadoctag,
119 | .hljs-phpdoc,
120 | .hljs-dartdoc,
121 | .hljs-yardoctag,
122 | .smalltalk .hljs-class,
123 | .hljs-winutils,
124 | .bash .hljs-variable,
125 | .apache .hljs-tag,
126 | .hljs-type,
127 | .hljs-typename,
128 | .tex .hljs-command,
129 | .asciidoc .hljs-strong,
130 | .markdown .hljs-strong,
131 | .hljs-request,
132 | .hljs-status {
133 | font-weight: bold;
134 | }
135 |
136 | .asciidoc .hljs-emphasis,
137 | .markdown .hljs-emphasis {
138 | font-style: italic;
139 | }
140 |
141 | .nginx .hljs-built_in {
142 | font-weight: normal;
143 | }
144 |
145 | .coffeescript .javascript,
146 | .javascript .xml,
147 | .lasso .markup,
148 | .tex .hljs-formula,
149 | .xml .javascript,
150 | .xml .vbscript,
151 | .xml .css,
152 | .xml .hljs-cdata {
153 | opacity: 0.5;
154 | }
155 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | /*global require*/
2 | require.config({
3 | baseUrl: "scripts",
4 | urlArgs: "bust=" + (new Date()).getTime(),
5 | shim: {
6 | underscore: {
7 | exports: '_'
8 | },
9 | backbone: {
10 | deps: ["underscore", "jquery"],
11 | exports: "Backbone"
12 | },
13 | "bootstrap-dropdown": {
14 | deps: ["jquery"],
15 | exports: "jQuery.fn.dropdown"
16 | },
17 | "bootstrap-modal": {
18 | deps: ["jquery"],
19 | exports: "jQuery.fn.modal"
20 | },
21 | "bootstrap-typeahead": {
22 | deps: ["jquery"],
23 | exports: "jQuery.fn.typeahead"
24 | },
25 | "bootstrap-tooltip": {
26 | deps: ["jquery"],
27 | exports: "jQuery.fn.tooltip"
28 | },
29 | "jquery.querystring": {
30 | deps: ["jquery"],
31 | exports: "jQuery.fn.getParam"
32 | },
33 | "highlight": {
34 | // https://highlightjs.org/download/
35 | // We can download a custom build with only JSON support (all we need).
36 | // Version 8.2 of highlight.js uses `var` so when requirejs emeds it in
37 | // a large function call it won't be available on `this`.
38 | // By returning the local scope var we can use the minified custom
39 | // download without needing to wrap or alter the file.
40 | init: function(){ /*global hljs*/ return hljs; },
41 | exports: "hljs"
42 | }
43 | },
44 | paths: {
45 | "jquery": "../inc/jquery",
46 | "underscore": "../inc/underscore",
47 | "backbone": "../inc/backbone",
48 | "bootstrap-typeahead": "../inc/bootstrap/js/bootstrap-typeahead",
49 | "bootstrap-dropdown": "../inc/bootstrap/js/bootstrap-dropdown",
50 | "bootstrap-modal": "../inc/bootstrap/js/bootstrap-modal",
51 | "bootstrap-tooltip": "../inc/bootstrap/js/bootstrap-tooltip",
52 | "jquery.querystring": "../inc/jquery.querystring",
53 | "behave": "../inc/behave",
54 | "highlight": "../inc/highlight",
55 | "text": "../inc/text",
56 | "tpl": "../inc/tpl"
57 | }
58 | });
59 |
60 | define([
61 | "jquery",
62 | "router",
63 | "view/viewport",
64 | "view/navbar",
65 | "view/request",
66 | "view/sidebar",
67 | "view/settings",
68 | "settings",
69 | "model/request",
70 | "model",
71 | "collection",
72 | "jquery.querystring"
73 | ],
74 | function (
75 | $,
76 | router,
77 | Viewport,
78 | Navbar,
79 | RequestView,
80 | SidebarView,
81 | SettingsView,
82 | settings,
83 | Request,
84 | Model,
85 | Collection
86 | ) {
87 | $(function(){
88 | var viewport = new Viewport();
89 | $(document.body).replaceWith(viewport.render().el);
90 |
91 | var request = new Request({ active: true });
92 |
93 | var examples = window.e = new Collection([request], {
94 | model: Request,
95 | comparator: "description"
96 | });
97 |
98 | var settingsView = new SettingsView({ model: settings });
99 | var sidebar = new SidebarView({
100 | settingsView: settingsView,
101 | collection: examples
102 | });
103 | var navbar = new Navbar({ collection: examples });
104 |
105 | var fetch = examples.fetch({ remove: false });
106 |
107 | viewport.$el.append(
108 | navbar.render().el,
109 | sidebar.render().el,
110 | settingsView.render().el,
111 | viewport.add(new RequestView({ model: request })).render().el
112 | );
113 |
114 | examples.bind("change:active", function(model, value) {
115 | if(!value) return;
116 | viewport.removeViews();
117 | viewport.$el.append(viewport.add(new RequestView({ model: model })).render().el);
118 | });
119 |
120 | examples.bind("change:id", function(model, id) {
121 | if(!model.isActive()) return;
122 | window.history.pushState(null, null, "/");
123 | router.navigate("//" + id);
124 | });
125 |
126 | router.on("route:load", function(id) {
127 | navbar.startLoading();
128 | fetch.then(function() {
129 | var model = examples.get(id) || examples.newModel(id === "new" ? null : { id: id });
130 | model.setActive();
131 | if(id !== "new")
132 | return model.fetch().then(function() {
133 | return model.request({ gist: false });
134 | });
135 | }).always(function() {
136 | navbar.endLoading();
137 | });
138 | }).start();
139 |
140 | if($.getParam("url")) {
141 | navbar.startLoading();
142 | examples.newModel().setActive().set({
143 | endpoint: $.getParam("url"),
144 | body: $.getParam("content")
145 | }).request().always(function() {
146 | navbar.endLoading();
147 | });
148 | }
149 | });
150 | });
151 |
--------------------------------------------------------------------------------
/app/scripts/view/request.js:
--------------------------------------------------------------------------------
1 | define([
2 | "underscore",
3 | "view",
4 | "view/dragbar",
5 | "settings",
6 | "behave",
7 | "highlight",
8 | "tpl!template/request.htm",
9 | "bootstrap-dropdown"
10 | ], function(_, View, DragBar, settings, Behave, hljs, template) {
11 | /*jslint unparam: true*/ // A lot of event callbacks in here.
12 | return View.extend({
13 | name: "request",
14 | template: template,
15 |
16 | events: {
17 | "keydown textarea": function(e) {
18 | // Shift + Enter to send request.
19 | // NOTE: Our copy of behave has an edit on line 441 to disable behave's
20 | // enterKey functionality when shift is pressed.
21 | // Behave offers hooks for keydown as well as enter:before but they both
22 | // fire after behave's enterKey function has started, so I think it's
23 | // too late... I think we're stuck with the edit.
24 | if((e.keyCode || e.which) === 13 && e.shiftKey === true) {
25 | this.model.request();
26 | return false;
27 | }
28 | },
29 | "keyup textarea": "updateBody"
30 | },
31 |
32 | updateBody: function() {
33 | var json = this.$body.val();
34 | this.model.set("body", json);
35 | },
36 | validateBody: function () {
37 | this.setValid(this.model.isValid());
38 | },
39 | onChangeBody: function(m, body) {
40 | // Only update the html if the text is different,
41 | // since doing so can move the cursor in some browsers.
42 | if( body !== this.$body.val() ){
43 | this.$body.val(body);
44 | }
45 | if( settings.get('instantValidation') ){
46 | this.validateBody();
47 | }
48 | },
49 |
50 | initialize: function() {
51 | this.listenTo(this.model, {
52 | // Use special pending event not only for the start, but also
53 | // to ensure we get the event even if the response doesn't *change*.
54 | "change:response": this.updateResponse,
55 | "change:body": this.onChangeBody,
56 | "pending": this.updatePendingIndicator
57 | });
58 | this.listenTo(settings, {
59 | 'change:editorFeatures': this.onChangeEditorFeatures,
60 | 'change:highlightResponse': this.updateResponse,
61 | 'change:wrapLines': this.onChangeWrapLines,
62 | 'change:instantValidation': this.onChangeInstantValidation
63 | });
64 | },
65 |
66 | updatePendingIndicator: function (pending) {
67 | this.$resbox.toggleClass('pending', pending);
68 | },
69 | updateResponse: function() {
70 | var res = _.escape(this.model.get("response"));
71 | this.$response.html(res);
72 | if( settings.get('highlightResponse') ){
73 | hljs.highlightBlock(this.$response.get(0));
74 | }
75 | },
76 |
77 | render: function() {
78 | View.prototype.render.apply(this, arguments);
79 | this.$label = this.$(".editor .label").hide();
80 | this.$body = this.$("textarea");
81 | this.$resbox = this.$('.response');
82 | this.$response = this.$('pre code');
83 |
84 | this.dragbar = (new DragBar({
85 | container: this.$('.request-inner'),
86 | left: this.$('.editor'),
87 | right: this.$resbox,
88 | el: this.$('.dragbar')
89 | })).render();
90 |
91 | this.setEditorFeatures(settings.get('editorFeatures'));
92 | this.setWrapLines(settings.get('wrapLines'));
93 |
94 | this.updateResponse();
95 | return this;
96 | },
97 |
98 | onChangeEditorFeatures: function(m, val, o){ this.setEditorFeatures(val); },
99 | setEditorFeatures: function(enabled) {
100 | if( this.behave ){
101 | this.behave.destroy();
102 | this.behave = null;
103 | }
104 | if( enabled ){
105 | this.behave = new Behave({
106 | textarea: this.$body.get(0),
107 | tabSize: 2
108 | });
109 | }
110 | },
111 |
112 | onChangeInstantValidation: function(m, val, o){
113 | if( val ){
114 | // Initiate validation when enabled.
115 | this.validateBody();
116 | }
117 | else {
118 | // Update the display if checking is disabled.
119 | this.setValid(true);
120 | }
121 | },
122 | setValid: function(valid) {
123 | if( valid ){
124 | this.$label.hide();
125 | }
126 | else {
127 | this.$label.show();
128 | }
129 | },
130 |
131 | onChangeWrapLines: function(m, val, o) { this.setWrapLines(val); },
132 | setWrapLines: function(wrap) {
133 | this.$response.toggleClass('wrap', wrap);
134 | }
135 | });
136 | });
137 |
--------------------------------------------------------------------------------
/app/inc/highlight.js:
--------------------------------------------------------------------------------
1 | // custom built (with only json included) from https://highlightjs.org/download/
2 | var hljs=new function(){function j(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||/no(-?)highlight/.test(x)})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);if(!t(A).match(/br|hr|img|input/)){v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}}}x(y)}function c(T,L,J,R){function v(V,W){for(var U=0;U";V+=aa+'">';return V+Y+Z}function N(){if(!I.k){return j(C)}var U="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(C);while(V){U+=j(C.substr(X,V.index-X));var W=E(I,V);if(W){H+=W[1];U+=w(W[0],j(V[0]))}else{U+=j(V[0])}X=I.lR.lastIndex;V=I.lR.exec(C)}return U+j(C.substr(X))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var U=I.sL?c(I.sL,C,true,S):e(C);if(I.r>0){H+=U.r}if(I.subLanguageMode=="continuous"){S=U.top}return w(U.language,U.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(W,V){var U=W.cN?w(W.cN,"",true):"";if(W.rB){D+=U;C=""}else{if(W.eB){D+=j(V)+U;C=""}else{D+=U;C=V}}I=Object.create(W,{parent:{value:I}})}function G(U,Y){C+=U;if(Y===undefined){D+=Q();return 0}var W=v(Y,I);if(W){D+=Q();P(W,Y);return W.rB?0:Y.length}var X=z(I,Y);if(X){var V=I;if(!(V.rE||V.eE)){C+=Y}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=X.parent);if(V.eE){D+=j(Y)}C="";if(X.starts){P(X.starts,"")}return V.rE?0:Y.length}if(A(Y,I)){throw new Error('Illegal lexeme "'+Y+'" for mode "'+(I.cN||"")+'"')}C+=Y;return Y.length||1}var M=i(T);if(!M){throw new Error('Unknown language: "'+T+'"')}m(M);var I=R||M;var S;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,"",true)+D}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:T,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"
")}return v}function p(A){var B=r(A);if(/no(-?)highlight/.test(B)){return}var y;if(b.useBR){y=document.createElementNS("http://www.w3.org/1999/xhtml","div");y.innerHTML=A.innerHTML.replace(/\n/g,"").replace(/
/g,"\n")}else{y=A}var z=y.textContent;var v=B?c(B,z,true):e(z);var x=u(y);if(x.length){var w=document.createElementNS("http://www.w3.org/1999/xhtml","div");w.innerHTML=v.value;v.value=q(x,u(w),z)}v.value=g(v.value);A.innerHTML=v.value;A.className+=" hljs "+(!B&&v.language||"");A.result={language:v.language,re:v.r};if(v.second_best){A.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});
3 |
--------------------------------------------------------------------------------
/app/inc/require.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var requirejs,require,define;
7 | (function(aa){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error=
19 | a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=
20 | i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this,
21 | function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=
22 | this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a,
23 | this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};G(a,function(a,b){e[b]?
24 | "map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[],
25 | a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+
26 | b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x=
34 | b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),
35 | function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this);
--------------------------------------------------------------------------------
/app/inc/almond.js:
--------------------------------------------------------------------------------
1 | /**
2 | * almond 0.2.5 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
3 | * Available via the MIT or new BSD license.
4 | * see: http://github.com/jrburke/almond for details
5 | */
6 | //Going sloppy to avoid 'use strict' string cost, but strict practices should
7 | //be followed.
8 | /*jslint sloppy: true */
9 | /*global setTimeout: false */
10 |
11 | var requirejs, require, define;
12 | (function (undef) {
13 | var main, req, makeMap, handlers,
14 | defined = {},
15 | waiting = {},
16 | config = {},
17 | defining = {},
18 | hasOwn = Object.prototype.hasOwnProperty,
19 | aps = [].slice;
20 |
21 | function hasProp(obj, prop) {
22 | return hasOwn.call(obj, prop);
23 | }
24 |
25 | /**
26 | * Given a relative module name, like ./something, normalize it to
27 | * a real name that can be mapped to a path.
28 | * @param {String} name the relative name
29 | * @param {String} baseName a real name that the name arg is relative
30 | * to.
31 | * @returns {String} normalized name
32 | */
33 | function normalize(name, baseName) {
34 | var nameParts, nameSegment, mapValue, foundMap,
35 | foundI, foundStarMap, starI, i, j, part,
36 | baseParts = baseName && baseName.split("/"),
37 | map = config.map,
38 | starMap = (map && map['*']) || {};
39 |
40 | //Adjust any relative paths.
41 | if (name && name.charAt(0) === ".") {
42 | //If have a base name, try to normalize against it,
43 | //otherwise, assume it is a top-level require that will
44 | //be relative to baseUrl in the end.
45 | if (baseName) {
46 | //Convert baseName to array, and lop off the last part,
47 | //so that . matches that "directory" and not name of the baseName's
48 | //module. For instance, baseName of "one/two/three", maps to
49 | //"one/two/three.js", but we want the directory, "one/two" for
50 | //this normalization.
51 | baseParts = baseParts.slice(0, baseParts.length - 1);
52 |
53 | name = baseParts.concat(name.split("/"));
54 |
55 | //start trimDots
56 | for (i = 0; i < name.length; i += 1) {
57 | part = name[i];
58 | if (part === ".") {
59 | name.splice(i, 1);
60 | i -= 1;
61 | } else if (part === "..") {
62 | if (i === 1 && (name[2] === '..' || name[0] === '..')) {
63 | //End of the line. Keep at least one non-dot
64 | //path segment at the front so it can be mapped
65 | //correctly to disk. Otherwise, there is likely
66 | //no path mapping for a path starting with '..'.
67 | //This can still fail, but catches the most reasonable
68 | //uses of ..
69 | break;
70 | } else if (i > 0) {
71 | name.splice(i - 1, 2);
72 | i -= 2;
73 | }
74 | }
75 | }
76 | //end trimDots
77 |
78 | name = name.join("/");
79 | } else if (name.indexOf('./') === 0) {
80 | // No baseName, so this is ID is resolved relative
81 | // to baseUrl, pull off the leading dot.
82 | name = name.substring(2);
83 | }
84 | }
85 |
86 | //Apply map config if available.
87 | if ((baseParts || starMap) && map) {
88 | nameParts = name.split('/');
89 |
90 | for (i = nameParts.length; i > 0; i -= 1) {
91 | nameSegment = nameParts.slice(0, i).join("/");
92 |
93 | if (baseParts) {
94 | //Find the longest baseName segment match in the config.
95 | //So, do joins on the biggest to smallest lengths of baseParts.
96 | for (j = baseParts.length; j > 0; j -= 1) {
97 | mapValue = map[baseParts.slice(0, j).join('/')];
98 |
99 | //baseName segment has config, find if it has one for
100 | //this name.
101 | if (mapValue) {
102 | mapValue = mapValue[nameSegment];
103 | if (mapValue) {
104 | //Match, update name to the new value.
105 | foundMap = mapValue;
106 | foundI = i;
107 | break;
108 | }
109 | }
110 | }
111 | }
112 |
113 | if (foundMap) {
114 | break;
115 | }
116 |
117 | //Check for a star map match, but just hold on to it,
118 | //if there is a shorter segment match later in a matching
119 | //config, then favor over this star map.
120 | if (!foundStarMap && starMap && starMap[nameSegment]) {
121 | foundStarMap = starMap[nameSegment];
122 | starI = i;
123 | }
124 | }
125 |
126 | if (!foundMap && foundStarMap) {
127 | foundMap = foundStarMap;
128 | foundI = starI;
129 | }
130 |
131 | if (foundMap) {
132 | nameParts.splice(0, foundI, foundMap);
133 | name = nameParts.join('/');
134 | }
135 | }
136 |
137 | return name;
138 | }
139 |
140 | function makeRequire(relName, forceSync) {
141 | return function () {
142 | //A version of a require function that passes a moduleName
143 | //value for items that may need to
144 | //look up paths relative to the moduleName
145 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
146 | };
147 | }
148 |
149 | function makeNormalize(relName) {
150 | return function (name) {
151 | return normalize(name, relName);
152 | };
153 | }
154 |
155 | function makeLoad(depName) {
156 | return function (value) {
157 | defined[depName] = value;
158 | };
159 | }
160 |
161 | function callDep(name) {
162 | if (hasProp(waiting, name)) {
163 | var args = waiting[name];
164 | delete waiting[name];
165 | defining[name] = true;
166 | main.apply(undef, args);
167 | }
168 |
169 | if (!hasProp(defined, name) && !hasProp(defining, name)) {
170 | throw new Error('No ' + name);
171 | }
172 | return defined[name];
173 | }
174 |
175 | //Turns a plugin!resource to [plugin, resource]
176 | //with the plugin being undefined if the name
177 | //did not have a plugin prefix.
178 | function splitPrefix(name) {
179 | var prefix,
180 | index = name ? name.indexOf('!') : -1;
181 | if (index > -1) {
182 | prefix = name.substring(0, index);
183 | name = name.substring(index + 1, name.length);
184 | }
185 | return [prefix, name];
186 | }
187 |
188 | /**
189 | * Makes a name map, normalizing the name, and using a plugin
190 | * for normalization if necessary. Grabs a ref to plugin
191 | * too, as an optimization.
192 | */
193 | makeMap = function (name, relName) {
194 | var plugin,
195 | parts = splitPrefix(name),
196 | prefix = parts[0];
197 |
198 | name = parts[1];
199 |
200 | if (prefix) {
201 | prefix = normalize(prefix, relName);
202 | plugin = callDep(prefix);
203 | }
204 |
205 | //Normalize according
206 | if (prefix) {
207 | if (plugin && plugin.normalize) {
208 | name = plugin.normalize(name, makeNormalize(relName));
209 | } else {
210 | name = normalize(name, relName);
211 | }
212 | } else {
213 | name = normalize(name, relName);
214 | parts = splitPrefix(name);
215 | prefix = parts[0];
216 | name = parts[1];
217 | if (prefix) {
218 | plugin = callDep(prefix);
219 | }
220 | }
221 |
222 | //Using ridiculous property names for space reasons
223 | return {
224 | f: prefix ? prefix + '!' + name : name, //fullName
225 | n: name,
226 | pr: prefix,
227 | p: plugin
228 | };
229 | };
230 |
231 | function makeConfig(name) {
232 | return function () {
233 | return (config && config.config && config.config[name]) || {};
234 | };
235 | }
236 |
237 | handlers = {
238 | require: function (name) {
239 | return makeRequire(name);
240 | },
241 | exports: function (name) {
242 | var e = defined[name];
243 | if (typeof e !== 'undefined') {
244 | return e;
245 | } else {
246 | return (defined[name] = {});
247 | }
248 | },
249 | module: function (name) {
250 | return {
251 | id: name,
252 | uri: '',
253 | exports: defined[name],
254 | config: makeConfig(name)
255 | };
256 | }
257 | };
258 |
259 | main = function (name, deps, callback, relName) {
260 | var cjsModule, depName, ret, map, i,
261 | args = [],
262 | usingExports;
263 |
264 | //Use name if no relName
265 | relName = relName || name;
266 |
267 | //Call the callback to define the module, if necessary.
268 | if (typeof callback === 'function') {
269 |
270 | //Pull out the defined dependencies and pass the ordered
271 | //values to the callback.
272 | //Default to [require, exports, module] if no deps
273 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
274 | for (i = 0; i < deps.length; i += 1) {
275 | map = makeMap(deps[i], relName);
276 | depName = map.f;
277 |
278 | //Fast path CommonJS standard dependencies.
279 | if (depName === "require") {
280 | args[i] = handlers.require(name);
281 | } else if (depName === "exports") {
282 | //CommonJS module spec 1.1
283 | args[i] = handlers.exports(name);
284 | usingExports = true;
285 | } else if (depName === "module") {
286 | //CommonJS module spec 1.1
287 | cjsModule = args[i] = handlers.module(name);
288 | } else if (hasProp(defined, depName) ||
289 | hasProp(waiting, depName) ||
290 | hasProp(defining, depName)) {
291 | args[i] = callDep(depName);
292 | } else if (map.p) {
293 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
294 | args[i] = defined[depName];
295 | } else {
296 | throw new Error(name + ' missing ' + depName);
297 | }
298 | }
299 |
300 | ret = callback.apply(defined[name], args);
301 |
302 | if (name) {
303 | //If setting exports via "module" is in play,
304 | //favor that over return value and exports. After that,
305 | //favor a non-undefined return value over exports use.
306 | if (cjsModule && cjsModule.exports !== undef &&
307 | cjsModule.exports !== defined[name]) {
308 | defined[name] = cjsModule.exports;
309 | } else if (ret !== undef || !usingExports) {
310 | //Use the return value from the function.
311 | defined[name] = ret;
312 | }
313 | }
314 | } else if (name) {
315 | //May just be an object definition for the module. Only
316 | //worry about defining if have a module name.
317 | defined[name] = callback;
318 | }
319 | };
320 |
321 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
322 | if (typeof deps === "string") {
323 | if (handlers[deps]) {
324 | //callback in this case is really relName
325 | return handlers[deps](callback);
326 | }
327 | //Just return the module wanted. In this scenario, the
328 | //deps arg is the module name, and second arg (if passed)
329 | //is just the relName.
330 | //Normalize module name, if it contains . or ..
331 | return callDep(makeMap(deps, callback).f);
332 | } else if (!deps.splice) {
333 | //deps is a config object, not an array.
334 | config = deps;
335 | if (callback.splice) {
336 | //callback is an array, which means it is a dependency list.
337 | //Adjust args if there are dependencies
338 | deps = callback;
339 | callback = relName;
340 | relName = null;
341 | } else {
342 | deps = undef;
343 | }
344 | }
345 |
346 | //Support require(['a'])
347 | callback = callback || function () {};
348 |
349 | //If relName is a function, it is an errback handler,
350 | //so remove it.
351 | if (typeof relName === 'function') {
352 | relName = forceSync;
353 | forceSync = alt;
354 | }
355 |
356 | //Simulate async callback;
357 | if (forceSync) {
358 | main(undef, deps, callback, relName);
359 | } else {
360 | //Using a non-zero value because of concern for what old browsers
361 | //do, and latest browsers "upgrade" to 4 if lower value is used:
362 | //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
363 | //If want a value immediately, use require('id') instead -- something
364 | //that works in almond on the global level, but not guaranteed and
365 | //unlikely to work in other AMD implementations.
366 | setTimeout(function () {
367 | main(undef, deps, callback, relName);
368 | }, 4);
369 | }
370 |
371 | return req;
372 | };
373 |
374 | /**
375 | * Just drops the config on the floor, but returns req in case
376 | * the config return value is used.
377 | */
378 | req.config = function (cfg) {
379 | config = cfg;
380 | if (config.deps) {
381 | req(config.deps, config.callback);
382 | }
383 | return req;
384 | };
385 |
386 | define = function (name, deps, callback) {
387 |
388 | //This module may not have dependencies
389 | if (!deps.splice) {
390 | //deps is not an array, so probably means
391 | //an object literal or factory function for
392 | //the value. Adjust args.
393 | callback = deps;
394 | deps = [];
395 | }
396 |
397 | if (!hasProp(defined, name) && !hasProp(waiting, name)) {
398 | waiting[name] = [name, deps, callback];
399 | }
400 | };
401 |
402 | define.amd = {
403 | jQuery: true
404 | };
405 | }());
--------------------------------------------------------------------------------
/app/inc/text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license RequireJS text 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
3 | * Available via the MIT or new BSD license.
4 | * see: http://github.com/requirejs/text for details
5 | */
6 | /*jslint regexp: true */
7 | /*global require, XMLHttpRequest, ActiveXObject,
8 | define, window, process, Packages,
9 | java, location, Components, FileUtils */
10 |
11 | define(['module'], function (module) {
12 | 'use strict';
13 |
14 | var text, fs, Cc, Ci,
15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im,
18 | hasLocation = typeof location !== 'undefined' && location.href,
19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
20 | defaultHostName = hasLocation && location.hostname,
21 | defaultPort = hasLocation && (location.port || undefined),
22 | buildMap = [],
23 | masterConfig = (module.config && module.config()) || {};
24 |
25 | text = {
26 | version: '2.0.6',
27 |
28 | strip: function (content) {
29 | //Strips declarations so that external SVG and XML
30 | //documents can be added to a document without worry. Also, if the string
31 | //is an HTML document, only the part inside the body tag is returned.
32 | if (content) {
33 | content = content.replace(xmlRegExp, "");
34 | var matches = content.match(bodyRegExp);
35 | if (matches) {
36 | content = matches[1];
37 | }
38 | } else {
39 | content = "";
40 | }
41 | return content;
42 | },
43 |
44 | jsEscape: function (content) {
45 | return content.replace(/(['\\])/g, '\\$1')
46 | .replace(/[\f]/g, "\\f")
47 | .replace(/[\b]/g, "\\b")
48 | .replace(/[\n]/g, "\\n")
49 | .replace(/[\t]/g, "\\t")
50 | .replace(/[\r]/g, "\\r")
51 | .replace(/[\u2028]/g, "\\u2028")
52 | .replace(/[\u2029]/g, "\\u2029");
53 | },
54 |
55 | createXhr: masterConfig.createXhr || function () {
56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
57 | var xhr, i, progId;
58 | if (typeof XMLHttpRequest !== "undefined") {
59 | return new XMLHttpRequest();
60 | } else if (typeof ActiveXObject !== "undefined") {
61 | for (i = 0; i < 3; i += 1) {
62 | progId = progIds[i];
63 | try {
64 | xhr = new ActiveXObject(progId);
65 | } catch (e) {}
66 |
67 | if (xhr) {
68 | progIds = [progId]; // so faster next time
69 | break;
70 | }
71 | }
72 | }
73 |
74 | return xhr;
75 | },
76 |
77 | /**
78 | * Parses a resource name into its component parts. Resource names
79 | * look like: module/name.ext!strip, where the !strip part is
80 | * optional.
81 | * @param {String} name the resource name
82 | * @returns {Object} with properties "moduleName", "ext" and "strip"
83 | * where strip is a boolean.
84 | */
85 | parseName: function (name) {
86 | var modName, ext, temp,
87 | strip = false,
88 | index = name.indexOf("."),
89 | isRelative = name.indexOf('./') === 0 ||
90 | name.indexOf('../') === 0;
91 |
92 | if (index !== -1 && (!isRelative || index > 1)) {
93 | modName = name.substring(0, index);
94 | ext = name.substring(index + 1, name.length);
95 | } else {
96 | modName = name;
97 | }
98 |
99 | temp = ext || modName;
100 | index = temp.indexOf("!");
101 | if (index !== -1) {
102 | //Pull off the strip arg.
103 | strip = temp.substring(index + 1) === "strip";
104 | temp = temp.substring(0, index);
105 | if (ext) {
106 | ext = temp;
107 | } else {
108 | modName = temp;
109 | }
110 | }
111 |
112 | return {
113 | moduleName: modName,
114 | ext: ext,
115 | strip: strip
116 | };
117 | },
118 |
119 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
120 |
121 | /**
122 | * Is an URL on another domain. Only works for browser use, returns
123 | * false in non-browser environments. Only used to know if an
124 | * optimized .js version of a text resource should be loaded
125 | * instead.
126 | * @param {String} url
127 | * @returns Boolean
128 | */
129 | useXhr: function (url, protocol, hostname, port) {
130 | var uProtocol, uHostName, uPort,
131 | match = text.xdRegExp.exec(url);
132 | if (!match) {
133 | return true;
134 | }
135 | uProtocol = match[2];
136 | uHostName = match[3];
137 |
138 | uHostName = uHostName.split(':');
139 | uPort = uHostName[1];
140 | uHostName = uHostName[0];
141 |
142 | return (!uProtocol || uProtocol === protocol) &&
143 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
144 | ((!uPort && !uHostName) || uPort === port);
145 | },
146 |
147 | finishLoad: function (name, strip, content, onLoad) {
148 | content = strip ? text.strip(content) : content;
149 | if (masterConfig.isBuild) {
150 | buildMap[name] = content;
151 | }
152 | onLoad(content);
153 | },
154 |
155 | load: function (name, req, onLoad, config) {
156 | //Name has format: some.module.filext!strip
157 | //The strip part is optional.
158 | //if strip is present, then that means only get the string contents
159 | //inside a body tag in an HTML string. For XML/SVG content it means
160 | //removing the declarations so the content can be inserted
161 | //into the current doc without problems.
162 |
163 | // Do not bother with the work if a build and text will
164 | // not be inlined.
165 | if (config.isBuild && !config.inlineText) {
166 | onLoad();
167 | return;
168 | }
169 |
170 | masterConfig.isBuild = config.isBuild;
171 |
172 | var parsed = text.parseName(name),
173 | nonStripName = parsed.moduleName +
174 | (parsed.ext ? '.' + parsed.ext : ''),
175 | url = req.toUrl(nonStripName),
176 | useXhr = (masterConfig.useXhr) ||
177 | text.useXhr;
178 |
179 | //Load the text. Use XHR if possible and in a browser.
180 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
181 | text.get(url, function (content) {
182 | text.finishLoad(name, parsed.strip, content, onLoad);
183 | }, function (err) {
184 | if (onLoad.error) {
185 | onLoad.error(err);
186 | }
187 | });
188 | } else {
189 | //Need to fetch the resource across domains. Assume
190 | //the resource has been optimized into a JS module. Fetch
191 | //by the module name + extension, but do not include the
192 | //!strip part to avoid file system issues.
193 | req([nonStripName], function (content) {
194 | text.finishLoad(parsed.moduleName + '.' + parsed.ext,
195 | parsed.strip, content, onLoad);
196 | });
197 | }
198 | },
199 |
200 | write: function (pluginName, moduleName, write, config) {
201 | if (buildMap.hasOwnProperty(moduleName)) {
202 | var content = text.jsEscape(buildMap[moduleName]);
203 | write.asModule(pluginName + "!" + moduleName,
204 | "define(function () { return '" +
205 | content +
206 | "';});\n");
207 | }
208 | },
209 |
210 | writeFile: function (pluginName, moduleName, req, write, config) {
211 | var parsed = text.parseName(moduleName),
212 | extPart = parsed.ext ? '.' + parsed.ext : '',
213 | nonStripName = parsed.moduleName + extPart,
214 | //Use a '.js' file name so that it indicates it is a
215 | //script that can be loaded across domains.
216 | fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
217 |
218 | //Leverage own load() method to load plugin value, but only
219 | //write out values that do not have the strip argument,
220 | //to avoid any potential issues with ! in file names.
221 | text.load(nonStripName, req, function (value) {
222 | //Use own write() method to construct full module value.
223 | //But need to create shell that translates writeFile's
224 | //write() to the right interface.
225 | var textWrite = function (contents) {
226 | return write(fileName, contents);
227 | };
228 | textWrite.asModule = function (moduleName, contents) {
229 | return write.asModule(moduleName, fileName, contents);
230 | };
231 |
232 | text.write(pluginName, nonStripName, textWrite, config);
233 | }, config);
234 | }
235 | };
236 |
237 | if (masterConfig.env === 'node' || (!masterConfig.env &&
238 | typeof process !== "undefined" &&
239 | process.versions &&
240 | !!process.versions.node)) {
241 | //Using special require.nodeRequire, something added by r.js.
242 | fs = require.nodeRequire('fs');
243 |
244 | text.get = function (url, callback) {
245 | var file = fs.readFileSync(url, 'utf8');
246 | //Remove BOM (Byte Mark Order) from utf8 files if it is there.
247 | if (file.indexOf('\uFEFF') === 0) {
248 | file = file.substring(1);
249 | }
250 | callback(file);
251 | };
252 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
253 | text.createXhr())) {
254 | text.get = function (url, callback, errback, headers) {
255 | var xhr = text.createXhr(), header;
256 | xhr.open('GET', url, true);
257 |
258 | //Allow plugins direct access to xhr headers
259 | if (headers) {
260 | for (header in headers) {
261 | if (headers.hasOwnProperty(header)) {
262 | xhr.setRequestHeader(header.toLowerCase(), headers[header]);
263 | }
264 | }
265 | }
266 |
267 | //Allow overrides specified in config
268 | if (masterConfig.onXhr) {
269 | masterConfig.onXhr(xhr, url);
270 | }
271 |
272 | xhr.onreadystatechange = function (evt) {
273 | var status, err;
274 | //Do not explicitly handle errors, those should be
275 | //visible via console output in the browser.
276 | if (xhr.readyState === 4) {
277 | status = xhr.status;
278 | if (status > 399 && status < 600) {
279 | //An http 4xx or 5xx error. Signal an error.
280 | err = new Error(url + ' HTTP status: ' + status);
281 | err.xhr = xhr;
282 | errback(err);
283 | } else {
284 | callback(xhr.responseText);
285 | }
286 |
287 | if (masterConfig.onXhrComplete) {
288 | masterConfig.onXhrComplete(xhr, url);
289 | }
290 | }
291 | };
292 | xhr.send(null);
293 | };
294 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
295 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
296 | //Why Java, why is this so awkward?
297 | text.get = function (url, callback) {
298 | var stringBuffer, line,
299 | encoding = "utf-8",
300 | file = new java.io.File(url),
301 | lineSeparator = java.lang.System.getProperty("line.separator"),
302 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
303 | content = '';
304 | try {
305 | stringBuffer = new java.lang.StringBuffer();
306 | line = input.readLine();
307 |
308 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
309 | // http://www.unicode.org/faq/utf_bom.html
310 |
311 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
312 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
313 | if (line && line.length() && line.charAt(0) === 0xfeff) {
314 | // Eat the BOM, since we've already found the encoding on this file,
315 | // and we plan to concatenating this buffer with others; the BOM should
316 | // only appear at the top of a file.
317 | line = line.substring(1);
318 | }
319 |
320 | stringBuffer.append(line);
321 |
322 | while ((line = input.readLine()) !== null) {
323 | stringBuffer.append(lineSeparator);
324 | stringBuffer.append(line);
325 | }
326 | //Make sure we return a JavaScript string and not a Java string.
327 | content = String(stringBuffer.toString()); //String
328 | } finally {
329 | input.close();
330 | }
331 | callback(content);
332 | };
333 | } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
334 | typeof Components !== 'undefined' && Components.classes &&
335 | Components.interfaces)) {
336 | //Avert your gaze!
337 | Cc = Components.classes,
338 | Ci = Components.interfaces;
339 | Components.utils['import']('resource://gre/modules/FileUtils.jsm');
340 |
341 | text.get = function (url, callback) {
342 | var inStream, convertStream,
343 | readData = {},
344 | fileObj = new FileUtils.File(url);
345 |
346 | //XPCOM, you so crazy
347 | try {
348 | inStream = Cc['@mozilla.org/network/file-input-stream;1']
349 | .createInstance(Ci.nsIFileInputStream);
350 | inStream.init(fileObj, 1, 0, false);
351 |
352 | convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
353 | .createInstance(Ci.nsIConverterInputStream);
354 | convertStream.init(inStream, "utf-8", inStream.available(),
355 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
356 |
357 | convertStream.readString(inStream.available(), readData);
358 | convertStream.close();
359 | inStream.close();
360 | callback(readData.value);
361 | } catch (e) {
362 | throw new Error((fileObj && fileObj.path || '') + ': ' + e);
363 | }
364 | };
365 | }
366 | return text;
367 | });
368 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This software is copyright (c) 2010 by Olaf Alders.
2 |
3 | This is free software; you can redistribute it and/or modify it under
4 | the same terms as the Perl 5 programming language system itself.
5 |
6 | Terms of the Perl programming language system itself
7 |
8 | a) the GNU General Public License as published by the Free
9 | Software Foundation; either version 1, or (at your option) any
10 | later version, or
11 | b) the "Artistic License"
12 |
13 | --- The GNU General Public License, Version 1, February 1989 ---
14 |
15 | This software is Copyright (c) 2010 by Olaf Alders.
16 |
17 | This is free software, licensed under:
18 |
19 | The GNU General Public License, Version 1, February 1989
20 |
21 | GNU GENERAL PUBLIC LICENSE
22 | Version 1, February 1989
23 |
24 | Copyright (C) 1989 Free Software Foundation, Inc.
25 | 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA
26 |
27 | Everyone is permitted to copy and distribute verbatim copies
28 | of this license document, but changing it is not allowed.
29 |
30 | Preamble
31 |
32 | The license agreements of most software companies try to keep users
33 | at the mercy of those companies. By contrast, our General Public
34 | License is intended to guarantee your freedom to share and change free
35 | software--to make sure the software is free for all its users. The
36 | General Public License applies to the Free Software Foundation's
37 | software and to any other program whose authors commit to using it.
38 | You can use it for your programs, too.
39 |
40 | When we speak of free software, we are referring to freedom, not
41 | price. Specifically, the General Public License is designed to make
42 | sure that you have the freedom to give away or sell copies of free
43 | software, that you receive source code or can get it if you want it,
44 | that you can change the software or use pieces of it in new free
45 | programs; and that you know you can do these things.
46 |
47 | To protect your rights, we need to make restrictions that forbid
48 | anyone to deny you these rights or to ask you to surrender the rights.
49 | These restrictions translate to certain responsibilities for you if you
50 | distribute copies of the software, or if you modify it.
51 |
52 | For example, if you distribute copies of a such a program, whether
53 | gratis or for a fee, you must give the recipients all the rights that
54 | you have. You must make sure that they, too, receive or can get the
55 | source code. And you must tell them their rights.
56 |
57 | We protect your rights with two steps: (1) copyright the software, and
58 | (2) offer you this license which gives you legal permission to copy,
59 | distribute and/or modify the software.
60 |
61 | Also, for each author's protection and ours, we want to make certain
62 | that everyone understands that there is no warranty for this free
63 | software. If the software is modified by someone else and passed on, we
64 | want its recipients to know that what they have is not the original, so
65 | that any problems introduced by others will not reflect on the original
66 | authors' reputations.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | GNU GENERAL PUBLIC LICENSE
72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
73 |
74 | 0. This License Agreement applies to any program or other work which
75 | contains a notice placed by the copyright holder saying it may be
76 | distributed under the terms of this General Public License. The
77 | "Program", below, refers to any such program or work, and a "work based
78 | on the Program" means either the Program or any work containing the
79 | Program or a portion of it, either verbatim or with modifications. Each
80 | licensee is addressed as "you".
81 |
82 | 1. You may copy and distribute verbatim copies of the Program's source
83 | code as you receive it, in any medium, provided that you conspicuously and
84 | appropriately publish on each copy an appropriate copyright notice and
85 | disclaimer of warranty; keep intact all the notices that refer to this
86 | General Public License and to the absence of any warranty; and give any
87 | other recipients of the Program a copy of this General Public License
88 | along with the Program. You may charge a fee for the physical act of
89 | transferring a copy.
90 |
91 | 2. You may modify your copy or copies of the Program or any portion of
92 | it, and copy and distribute such modifications under the terms of Paragraph
93 | 1 above, provided that you also do the following:
94 |
95 | a) cause the modified files to carry prominent notices stating that
96 | you changed the files and the date of any change; and
97 |
98 | b) cause the whole of any work that you distribute or publish, that
99 | in whole or in part contains the Program or any part thereof, either
100 | with or without modifications, to be licensed at no charge to all
101 | third parties under the terms of this General Public License (except
102 | that you may choose to grant warranty protection to some or all
103 | third parties, at your option).
104 |
105 | c) If the modified program normally reads commands interactively when
106 | run, you must cause it, when started running for such interactive use
107 | in the simplest and most usual way, to print or display an
108 | announcement including an appropriate copyright notice and a notice
109 | that there is no warranty (or else, saying that you provide a
110 | warranty) and that users may redistribute the program under these
111 | conditions, and telling the user how to view a copy of this General
112 | Public License.
113 |
114 | d) You may charge a fee for the physical act of transferring a
115 | copy, and you may at your option offer warranty protection in
116 | exchange for a fee.
117 |
118 | Mere aggregation of another independent work with the Program (or its
119 | derivative) on a volume of a storage or distribution medium does not bring
120 | the other work under the scope of these terms.
121 |
122 | 3. You may copy and distribute the Program (or a portion or derivative of
123 | it, under Paragraph 2) in object code or executable form under the terms of
124 | Paragraphs 1 and 2 above provided that you also do one of the following:
125 |
126 | a) accompany it with the complete corresponding machine-readable
127 | source code, which must be distributed under the terms of
128 | Paragraphs 1 and 2 above; or,
129 |
130 | b) accompany it with a written offer, valid for at least three
131 | years, to give any third party free (except for a nominal charge
132 | for the cost of distribution) a complete machine-readable copy of the
133 | corresponding source code, to be distributed under the terms of
134 | Paragraphs 1 and 2 above; or,
135 |
136 | c) accompany it with the information you received as to where the
137 | corresponding source code may be obtained. (This alternative is
138 | allowed only for noncommercial distribution and only if you
139 | received the program in object code or executable form alone.)
140 |
141 | Source code for a work means the preferred form of the work for making
142 | modifications to it. For an executable file, complete source code means
143 | all the source code for all modules it contains; but, as a special
144 | exception, it need not include source code for modules which are standard
145 | libraries that accompany the operating system on which the executable
146 | file runs, or for standard header files or definitions files that
147 | accompany that operating system.
148 |
149 | 4. You may not copy, modify, sublicense, distribute or transfer the
150 | Program except as expressly provided under this General Public License.
151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer
152 | the Program is void, and will automatically terminate your rights to use
153 | the Program under this License. However, parties who have received
154 | copies, or rights to use copies, from you under this General Public
155 | License will not have their licenses terminated so long as such parties
156 | remain in full compliance.
157 |
158 | 5. By copying, distributing or modifying the Program (or any work based
159 | on the Program) you indicate your acceptance of this license to do so,
160 | and all its terms and conditions.
161 |
162 | 6. Each time you redistribute the Program (or any work based on the
163 | Program), the recipient automatically receives a license from the original
164 | licensor to copy, distribute or modify the Program subject to these
165 | terms and conditions. You may not impose any further restrictions on the
166 | recipients' exercise of the rights granted herein.
167 |
168 | 7. The Free Software Foundation may publish revised and/or new versions
169 | of the General Public License from time to time. Such new versions will
170 | be similar in spirit to the present version, but may differ in detail to
171 | address new problems or concerns.
172 |
173 | Each version is given a distinguishing version number. If the Program
174 | specifies a version number of the license which applies to it and "any
175 | later version", you have the option of following the terms and conditions
176 | either of that version or of any later version published by the Free
177 | Software Foundation. If the Program does not specify a version number of
178 | the license, you may choose any version ever published by the Free Software
179 | Foundation.
180 |
181 | 8. If you wish to incorporate parts of the Program into other free
182 | programs whose distribution conditions are different, write to the author
183 | to ask for permission. For software which is copyrighted by the Free
184 | Software Foundation, write to the Free Software Foundation; we sometimes
185 | make exceptions for this. Our decision will be guided by the two goals
186 | of preserving the free status of all derivatives of our free software and
187 | of promoting the sharing and reuse of software generally.
188 |
189 | NO WARRANTY
190 |
191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
199 | REPAIR OR CORRECTION.
200 |
201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
209 | POSSIBILITY OF SUCH DAMAGES.
210 |
211 | END OF TERMS AND CONDITIONS
212 |
213 | Appendix: How to Apply These Terms to Your New Programs
214 |
215 | If you develop a new program, and you want it to be of the greatest
216 | possible use to humanity, the best way to achieve this is to make it
217 | free software which everyone can redistribute and change under these
218 | terms.
219 |
220 | To do so, attach the following notices to the program. It is safest to
221 | attach them to the start of each source file to most effectively convey
222 | the exclusion of warranty; and each file should have at least the
223 | "copyright" line and a pointer to where the full notice is found.
224 |
225 |
226 | Copyright (C) 19yy
227 |
228 | This program is free software; you can redistribute it and/or modify
229 | it under the terms of the GNU General Public License as published by
230 | the Free Software Foundation; either version 1, or (at your option)
231 | any later version.
232 |
233 | This program is distributed in the hope that it will be useful,
234 | but WITHOUT ANY WARRANTY; without even the implied warranty of
235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
236 | GNU General Public License for more details.
237 |
238 | You should have received a copy of the GNU General Public License
239 | along with this program; if not, write to the Free Software
240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
241 |
242 |
243 | Also add information on how to contact you by electronic and paper mail.
244 |
245 | If the program is interactive, make it output a short notice like this
246 | when it starts in an interactive mode:
247 |
248 | Gnomovision version 69, Copyright (C) 19xx name of author
249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
250 | This is free software, and you are welcome to redistribute it
251 | under certain conditions; type `show c' for details.
252 |
253 | The hypothetical commands `show w' and `show c' should show the
254 | appropriate parts of the General Public License. Of course, the
255 | commands you use may be called something other than `show w' and `show
256 | c'; they could even be mouse-clicks or menu items--whatever suits your
257 | program.
258 |
259 | You should also get your employer (if you work as a programmer) or your
260 | school, if any, to sign a "copyright disclaimer" for the program, if
261 | necessary. Here a sample; alter the names:
262 |
263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the
264 | program `Gnomovision' (a program to direct compilers to make passes
265 | at assemblers) written by James Hacker.
266 |
267 | , 1 April 1989
268 | Ty Coon, President of Vice
269 |
270 | That's all there is to it!
271 |
272 |
273 | --- The Artistic License 1.0 ---
274 |
275 | This software is Copyright (c) 2010 by Olaf Alders.
276 |
277 | This is free software, licensed under:
278 |
279 | The Artistic License 1.0
280 |
281 | The Artistic License
282 |
283 | Preamble
284 |
285 | The intent of this document is to state the conditions under which a Package
286 | may be copied, such that the Copyright Holder maintains some semblance of
287 | artistic control over the development of the package, while giving the users of
288 | the package the right to use and distribute the Package in a more-or-less
289 | customary fashion, plus the right to make reasonable modifications.
290 |
291 | Definitions:
292 |
293 | - "Package" refers to the collection of files distributed by the Copyright
294 | Holder, and derivatives of that collection of files created through
295 | textual modification.
296 | - "Standard Version" refers to such a Package if it has not been modified,
297 | or has been modified in accordance with the wishes of the Copyright
298 | Holder.
299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for
300 | the package.
301 | - "You" is you, if you're thinking about copying or distributing this Package.
302 | - "Reasonable copying fee" is whatever you can justify on the basis of media
303 | cost, duplication charges, time of people involved, and so on. (You will
304 | not be required to justify it to the Copyright Holder, but only to the
305 | computing community at large as a market that must bear the fee.)
306 | - "Freely Available" means that no fee is charged for the item itself, though
307 | there may be fees involved in handling the item. It also means that
308 | recipients of the item may redistribute it under the same conditions they
309 | received it.
310 |
311 | 1. You may make and give away verbatim copies of the source form of the
312 | Standard Version of this Package without restriction, provided that you
313 | duplicate all of the original copyright notices and associated disclaimers.
314 |
315 | 2. You may apply bug fixes, portability fixes and other modifications derived
316 | from the Public Domain or from the Copyright Holder. A Package modified in such
317 | a way shall still be considered the Standard Version.
318 |
319 | 3. You may otherwise modify your copy of this Package in any way, provided that
320 | you insert a prominent notice in each changed file stating how and when you
321 | changed that file, and provided that you do at least ONE of the following:
322 |
323 | a) place your modifications in the Public Domain or otherwise make them
324 | Freely Available, such as by posting said modifications to Usenet or an
325 | equivalent medium, or placing the modifications on a major archive site
326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your
327 | modifications in the Standard Version of the Package.
328 |
329 | b) use the modified Package only within your corporation or organization.
330 |
331 | c) rename any non-standard executables so the names do not conflict with
332 | standard executables, which must also be provided, and provide a separate
333 | manual page for each non-standard executable that clearly documents how it
334 | differs from the Standard Version.
335 |
336 | d) make other distribution arrangements with the Copyright Holder.
337 |
338 | 4. You may distribute the programs of this Package in object code or executable
339 | form, provided that you do at least ONE of the following:
340 |
341 | a) distribute a Standard Version of the executables and library files,
342 | together with instructions (in the manual page or equivalent) on where to
343 | get the Standard Version.
344 |
345 | b) accompany the distribution with the machine-readable source of the Package
346 | with your modifications.
347 |
348 | c) accompany any non-standard executables with their corresponding Standard
349 | Version executables, giving the non-standard executables non-standard
350 | names, and clearly documenting the differences in manual pages (or
351 | equivalent), together with instructions on where to get the Standard
352 | Version.
353 |
354 | d) make other distribution arrangements with the Copyright Holder.
355 |
356 | 5. You may charge a reasonable copying fee for any distribution of this
357 | Package. You may charge any fee you choose for support of this Package. You
358 | may not charge a fee for this Package itself. However, you may distribute this
359 | Package in aggregate with other (possibly commercial) programs as part of a
360 | larger (possibly commercial) software distribution provided that you do not
361 | advertise this Package as a product of your own.
362 |
363 | 6. The scripts and library files supplied as input to or produced as output
364 | from the programs of this Package do not automatically fall under the copyright
365 | of this Package, but belong to whomever generated them, and may be sold
366 | commercially, and may be aggregated with this Package.
367 |
368 | 7. C or perl subroutines supplied by you and linked into this Package shall not
369 | be considered part of this Package.
370 |
371 | 8. The name of the Copyright Holder may not be used to endorse or promote
372 | products derived from this software without specific prior written permission.
373 |
374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
377 |
378 | The End
379 |
380 |
--------------------------------------------------------------------------------
/app/inc/behave.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Behave.js
3 | *
4 | * Copyright 2013, Jacob Kelley - http://jakiestfu.com/
5 | * Released under the MIT Licence
6 | * http://opensource.org/licenses/MIT
7 | *
8 | * Github: http://github.com/jakiestfu/Behave.js/
9 | * Version: 1.5
10 | */
11 |
12 |
13 | (function(undefined){
14 |
15 | 'use strict';
16 |
17 | var BehaveHooks = BehaveHooks || (function(){
18 | var hooks = {};
19 |
20 | return {
21 | add: function(hookName, fn){
22 | if(typeof hookName == "object"){
23 | var i;
24 | for(i=0; i>> 0;
69 | if (typeof func != "function"){
70 | throw new TypeError();
71 | }
72 | var res = [],
73 | thisp = arguments[1];
74 | for (var i = 0; i < len; i++) {
75 | if (i in t) {
76 | var val = t[i];
77 | if (func.call(thisp, val, i, t)) {
78 | res.push(val);
79 | }
80 | }
81 | }
82 | return res;
83 | };
84 | }
85 |
86 | var defaults = {
87 | textarea: null,
88 | replaceTab: true,
89 | softTabs: true,
90 | tabSize: 4,
91 | autoOpen: true,
92 | overwrite: true,
93 | autoStrip: true,
94 | autoIndent: true,
95 | fence: false
96 | },
97 | tab,
98 | newLine,
99 | charSettings = {
100 |
101 | keyMap: [
102 | { open: "\"", close: "\"", canBreak: false },
103 | { open: "'", close: "'", canBreak: false },
104 | { open: "(", close: ")", canBreak: false },
105 | { open: "[", close: "]", canBreak: true },
106 | { open: "{", close: "}", canBreak: true }
107 | ]
108 |
109 | },
110 | utils = {
111 |
112 | _callHook: function(hookName, passData){
113 | var hooks = BehaveHooks.get(hookName);
114 | passData = typeof passData=="boolean" && passData === false ? false : true;
115 |
116 | if(hooks){
117 | if(passData){
118 | var theEditor = defaults.textarea,
119 | textVal = theEditor.value,
120 | caretPos = utils.cursor.get(),
121 | i;
122 |
123 | for(i=0; i -1) {
232 | start = end = len;
233 | } else {
234 | start = -textInputRange.moveStart("character", -len);
235 | start += normalizedValue.slice(0, start).split(newLine).length - 1;
236 |
237 | if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
238 | end = len;
239 | } else {
240 | end = -textInputRange.moveEnd("character", -len);
241 | end += normalizedValue.slice(0, end).split(newLine).length - 1;
242 | }
243 | }
244 | }
245 | }
246 |
247 | return start==end ? false : {
248 | start: start,
249 | end: end
250 | };
251 | }
252 | },
253 | editor: {
254 | getLines: function(textVal){
255 | return (textVal).split("\n").length;
256 | },
257 | get: function(){
258 | return defaults.textarea.value.replace(/\r/g,'');
259 | },
260 | set: function(data){
261 | defaults.textarea.value = data;
262 | }
263 | },
264 | fenceRange: function(){
265 | if(typeof defaults.fence == "string"){
266 |
267 | var data = utils.editor.get(),
268 | pos = utils.cursor.get(),
269 | hacked = 0,
270 | matchedFence = data.indexOf(defaults.fence),
271 | matchCase = 0;
272 |
273 | while(matchedFence>=0){
274 | matchCase++;
275 | if( pos < (matchedFence+hacked) ){
276 | break;
277 | }
278 |
279 | hacked += matchedFence+defaults.fence.length;
280 | data = data.substring(matchedFence+defaults.fence.length);
281 | matchedFence = data.indexOf(defaults.fence);
282 |
283 | }
284 |
285 | if( (hacked) < pos && ( (matchedFence+hacked) > pos ) && matchCase%2===0){
286 | return true;
287 | }
288 | return false;
289 | } else {
290 | return true;
291 | }
292 | },
293 | isEven: function(_this,i){
294 | return i%2;
295 | },
296 | levelsDeep: function(){
297 | var pos = utils.cursor.get(),
298 | val = utils.editor.get();
299 |
300 | var left = val.substring(0, pos),
301 | levels = 0,
302 | i, j;
303 |
304 | for(i=0; i=0 ? finalLevels : 0;
331 | },
332 | deepExtend: function(destination, source) {
333 | for (var property in source) {
334 | if (source[property] && source[property].constructor &&
335 | source[property].constructor === Object) {
336 | destination[property] = destination[property] || {};
337 | utils.deepExtend(destination[property], source[property]);
338 | } else {
339 | destination[property] = source[property];
340 | }
341 | }
342 | return destination;
343 | },
344 | addEvent: function addEvent(element, eventName, func) {
345 | if (element.addEventListener){
346 | element.addEventListener(eventName,func,false);
347 | } else if (element.attachEvent) {
348 | element.attachEvent("on"+eventName, func);
349 | }
350 | },
351 | removeEvent: function addEvent(element, eventName, func){
352 | if (element.addEventListener){
353 | element.removeEventListener(eventName,func,false);
354 | } else if (element.attachEvent) {
355 | element.detachEvent("on"+eventName, func);
356 | }
357 | },
358 |
359 | preventDefaultEvent: function(e){
360 | if(e.preventDefault){
361 | e.preventDefault();
362 | } else {
363 | e.returnValue = false;
364 | }
365 | }
366 | },
367 | intercept = {
368 | tabKey: function (e) {
369 |
370 | if(!utils.fenceRange()){ return; }
371 |
372 | if (e.keyCode == 9) {
373 | utils.preventDefaultEvent(e);
374 |
375 | var toReturn = true;
376 | utils._callHook('tab:before');
377 |
378 | var selection = utils.cursor.selection(),
379 | pos = utils.cursor.get(),
380 | val = utils.editor.get();
381 |
382 | if(selection){
383 |
384 | var tempStart = selection.start;
385 | while(tempStart--){
386 | if(val.charAt(tempStart)=="\n"){
387 | selection.start = tempStart + 1;
388 | break;
389 | }
390 | }
391 |
392 | var toIndent = val.substring(selection.start, selection.end),
393 | lines = toIndent.split("\n"),
394 | i;
395 |
396 | if(e.shiftKey){
397 | for(i = 0; i http://underscorejs.org
5 | // > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
6 | // > Underscore may be freely distributed under the MIT license.
7 |
8 | // Baseline setup
9 | // --------------
10 | (function() {
11 |
12 | // Establish the root object, `window` in the browser, or `global` on the server.
13 | var root = this;
14 |
15 | // Save the previous value of the `_` variable.
16 | var previousUnderscore = root._;
17 |
18 | // Establish the object that gets returned to break out of a loop iteration.
19 | var breaker = {};
20 |
21 | // Save bytes in the minified (but not gzipped) version:
22 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
23 |
24 | // Create quick reference variables for speed access to core prototypes.
25 | var push = ArrayProto.push,
26 | slice = ArrayProto.slice,
27 | concat = ArrayProto.concat,
28 | toString = ObjProto.toString,
29 | hasOwnProperty = ObjProto.hasOwnProperty;
30 |
31 | // All **ECMAScript 5** native function implementations that we hope to use
32 | // are declared here.
33 | var
34 | nativeForEach = ArrayProto.forEach,
35 | nativeMap = ArrayProto.map,
36 | nativeReduce = ArrayProto.reduce,
37 | nativeReduceRight = ArrayProto.reduceRight,
38 | nativeFilter = ArrayProto.filter,
39 | nativeEvery = ArrayProto.every,
40 | nativeSome = ArrayProto.some,
41 | nativeIndexOf = ArrayProto.indexOf,
42 | nativeLastIndexOf = ArrayProto.lastIndexOf,
43 | nativeIsArray = Array.isArray,
44 | nativeKeys = Object.keys,
45 | nativeBind = FuncProto.bind;
46 |
47 | // Create a safe reference to the Underscore object for use below.
48 | var _ = function(obj) {
49 | if (obj instanceof _) return obj;
50 | if (!(this instanceof _)) return new _(obj);
51 | this._wrapped = obj;
52 | };
53 |
54 | // Export the Underscore object for **Node.js**, with
55 | // backwards-compatibility for the old `require()` API. If we're in
56 | // the browser, add `_` as a global object via a string identifier,
57 | // for Closure Compiler "advanced" mode.
58 | if (typeof exports !== 'undefined') {
59 | if (typeof module !== 'undefined' && module.exports) {
60 | exports = module.exports = _;
61 | }
62 | exports._ = _;
63 | } else {
64 | root._ = _;
65 | }
66 |
67 | // Current version.
68 | _.VERSION = '1.4.4';
69 |
70 | // Collection Functions
71 | // --------------------
72 |
73 | // The cornerstone, an `each` implementation, aka `forEach`.
74 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
75 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
76 | var each = _.each = _.forEach = function(obj, iterator, context) {
77 | if (obj == null) return;
78 | if (nativeForEach && obj.forEach === nativeForEach) {
79 | obj.forEach(iterator, context);
80 | } else if (obj.length === +obj.length) {
81 | for (var i = 0, l = obj.length; i < l; i++) {
82 | if (iterator.call(context, obj[i], i, obj) === breaker) return;
83 | }
84 | } else {
85 | for (var key in obj) {
86 | if (_.has(obj, key)) {
87 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
88 | }
89 | }
90 | }
91 | };
92 |
93 | // Return the results of applying the iterator to each element.
94 | // Delegates to **ECMAScript 5**'s native `map` if available.
95 | _.map = _.collect = function(obj, iterator, context) {
96 | var results = [];
97 | if (obj == null) return results;
98 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
99 | each(obj, function(value, index, list) {
100 | results[results.length] = iterator.call(context, value, index, list);
101 | });
102 | return results;
103 | };
104 |
105 | var reduceError = 'Reduce of empty array with no initial value';
106 |
107 | // **Reduce** builds up a single result from a list of values, aka `inject`,
108 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
109 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
110 | var initial = arguments.length > 2;
111 | if (obj == null) obj = [];
112 | if (nativeReduce && obj.reduce === nativeReduce) {
113 | if (context) iterator = _.bind(iterator, context);
114 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
115 | }
116 | each(obj, function(value, index, list) {
117 | if (!initial) {
118 | memo = value;
119 | initial = true;
120 | } else {
121 | memo = iterator.call(context, memo, value, index, list);
122 | }
123 | });
124 | if (!initial) throw new TypeError(reduceError);
125 | return memo;
126 | };
127 |
128 | // The right-associative version of reduce, also known as `foldr`.
129 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
130 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
131 | var initial = arguments.length > 2;
132 | if (obj == null) obj = [];
133 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
134 | if (context) iterator = _.bind(iterator, context);
135 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
136 | }
137 | var length = obj.length;
138 | if (length !== +length) {
139 | var keys = _.keys(obj);
140 | length = keys.length;
141 | }
142 | each(obj, function(value, index, list) {
143 | index = keys ? keys[--length] : --length;
144 | if (!initial) {
145 | memo = obj[index];
146 | initial = true;
147 | } else {
148 | memo = iterator.call(context, memo, obj[index], index, list);
149 | }
150 | });
151 | if (!initial) throw new TypeError(reduceError);
152 | return memo;
153 | };
154 |
155 | // Return the first value which passes a truth test. Aliased as `detect`.
156 | _.find = _.detect = function(obj, iterator, context) {
157 | var result;
158 | any(obj, function(value, index, list) {
159 | if (iterator.call(context, value, index, list)) {
160 | result = value;
161 | return true;
162 | }
163 | });
164 | return result;
165 | };
166 |
167 | // Return all the elements that pass a truth test.
168 | // Delegates to **ECMAScript 5**'s native `filter` if available.
169 | // Aliased as `select`.
170 | _.filter = _.select = function(obj, iterator, context) {
171 | var results = [];
172 | if (obj == null) return results;
173 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
174 | each(obj, function(value, index, list) {
175 | if (iterator.call(context, value, index, list)) results[results.length] = value;
176 | });
177 | return results;
178 | };
179 |
180 | // Return all the elements for which a truth test fails.
181 | _.reject = function(obj, iterator, context) {
182 | return _.filter(obj, function(value, index, list) {
183 | return !iterator.call(context, value, index, list);
184 | }, context);
185 | };
186 |
187 | // Determine whether all of the elements match a truth test.
188 | // Delegates to **ECMAScript 5**'s native `every` if available.
189 | // Aliased as `all`.
190 | _.every = _.all = function(obj, iterator, context) {
191 | iterator || (iterator = _.identity);
192 | var result = true;
193 | if (obj == null) return result;
194 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
195 | each(obj, function(value, index, list) {
196 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
197 | });
198 | return !!result;
199 | };
200 |
201 | // Determine if at least one element in the object matches a truth test.
202 | // Delegates to **ECMAScript 5**'s native `some` if available.
203 | // Aliased as `any`.
204 | var any = _.some = _.any = function(obj, iterator, context) {
205 | iterator || (iterator = _.identity);
206 | var result = false;
207 | if (obj == null) return result;
208 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
209 | each(obj, function(value, index, list) {
210 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
211 | });
212 | return !!result;
213 | };
214 |
215 | // Determine if the array or object contains a given value (using `===`).
216 | // Aliased as `include`.
217 | _.contains = _.include = function(obj, target) {
218 | if (obj == null) return false;
219 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
220 | return any(obj, function(value) {
221 | return value === target;
222 | });
223 | };
224 |
225 | // Invoke a method (with arguments) on every item in a collection.
226 | _.invoke = function(obj, method) {
227 | var args = slice.call(arguments, 2);
228 | var isFunc = _.isFunction(method);
229 | return _.map(obj, function(value) {
230 | return (isFunc ? method : value[method]).apply(value, args);
231 | });
232 | };
233 |
234 | // Convenience version of a common use case of `map`: fetching a property.
235 | _.pluck = function(obj, key) {
236 | return _.map(obj, function(value){ return value[key]; });
237 | };
238 |
239 | // Convenience version of a common use case of `filter`: selecting only objects
240 | // containing specific `key:value` pairs.
241 | _.where = function(obj, attrs, first) {
242 | if (_.isEmpty(attrs)) return first ? null : [];
243 | return _[first ? 'find' : 'filter'](obj, function(value) {
244 | for (var key in attrs) {
245 | if (attrs[key] !== value[key]) return false;
246 | }
247 | return true;
248 | });
249 | };
250 |
251 | // Convenience version of a common use case of `find`: getting the first object
252 | // containing specific `key:value` pairs.
253 | _.findWhere = function(obj, attrs) {
254 | return _.where(obj, attrs, true);
255 | };
256 |
257 | // Return the maximum element or (element-based computation).
258 | // Can't optimize arrays of integers longer than 65,535 elements.
259 | // See: https://bugs.webkit.org/show_bug.cgi?id=80797
260 | _.max = function(obj, iterator, context) {
261 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
262 | return Math.max.apply(Math, obj);
263 | }
264 | if (!iterator && _.isEmpty(obj)) return -Infinity;
265 | var result = {computed : -Infinity, value: -Infinity};
266 | each(obj, function(value, index, list) {
267 | var computed = iterator ? iterator.call(context, value, index, list) : value;
268 | computed >= result.computed && (result = {value : value, computed : computed});
269 | });
270 | return result.value;
271 | };
272 |
273 | // Return the minimum element (or element-based computation).
274 | _.min = function(obj, iterator, context) {
275 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
276 | return Math.min.apply(Math, obj);
277 | }
278 | if (!iterator && _.isEmpty(obj)) return Infinity;
279 | var result = {computed : Infinity, value: Infinity};
280 | each(obj, function(value, index, list) {
281 | var computed = iterator ? iterator.call(context, value, index, list) : value;
282 | computed < result.computed && (result = {value : value, computed : computed});
283 | });
284 | return result.value;
285 | };
286 |
287 | // Shuffle an array.
288 | _.shuffle = function(obj) {
289 | var rand;
290 | var index = 0;
291 | var shuffled = [];
292 | each(obj, function(value) {
293 | rand = _.random(index++);
294 | shuffled[index - 1] = shuffled[rand];
295 | shuffled[rand] = value;
296 | });
297 | return shuffled;
298 | };
299 |
300 | // An internal function to generate lookup iterators.
301 | var lookupIterator = function(value) {
302 | return _.isFunction(value) ? value : function(obj){ return obj[value]; };
303 | };
304 |
305 | // Sort the object's values by a criterion produced by an iterator.
306 | _.sortBy = function(obj, value, context) {
307 | var iterator = lookupIterator(value);
308 | return _.pluck(_.map(obj, function(value, index, list) {
309 | return {
310 | value : value,
311 | index : index,
312 | criteria : iterator.call(context, value, index, list)
313 | };
314 | }).sort(function(left, right) {
315 | var a = left.criteria;
316 | var b = right.criteria;
317 | if (a !== b) {
318 | if (a > b || a === void 0) return 1;
319 | if (a < b || b === void 0) return -1;
320 | }
321 | return left.index < right.index ? -1 : 1;
322 | }), 'value');
323 | };
324 |
325 | // An internal function used for aggregate "group by" operations.
326 | var group = function(obj, value, context, behavior) {
327 | var result = {};
328 | var iterator = lookupIterator(value || _.identity);
329 | each(obj, function(value, index) {
330 | var key = iterator.call(context, value, index, obj);
331 | behavior(result, key, value);
332 | });
333 | return result;
334 | };
335 |
336 | // Groups the object's values by a criterion. Pass either a string attribute
337 | // to group by, or a function that returns the criterion.
338 | _.groupBy = function(obj, value, context) {
339 | return group(obj, value, context, function(result, key, value) {
340 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
341 | });
342 | };
343 |
344 | // Counts instances of an object that group by a certain criterion. Pass
345 | // either a string attribute to count by, or a function that returns the
346 | // criterion.
347 | _.countBy = function(obj, value, context) {
348 | return group(obj, value, context, function(result, key) {
349 | if (!_.has(result, key)) result[key] = 0;
350 | result[key]++;
351 | });
352 | };
353 |
354 | // Use a comparator function to figure out the smallest index at which
355 | // an object should be inserted so as to maintain order. Uses binary search.
356 | _.sortedIndex = function(array, obj, iterator, context) {
357 | iterator = iterator == null ? _.identity : lookupIterator(iterator);
358 | var value = iterator.call(context, obj);
359 | var low = 0, high = array.length;
360 | while (low < high) {
361 | var mid = (low + high) >>> 1;
362 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
363 | }
364 | return low;
365 | };
366 |
367 | // Safely convert anything iterable into a real, live array.
368 | _.toArray = function(obj) {
369 | if (!obj) return [];
370 | if (_.isArray(obj)) return slice.call(obj);
371 | if (obj.length === +obj.length) return _.map(obj, _.identity);
372 | return _.values(obj);
373 | };
374 |
375 | // Return the number of elements in an object.
376 | _.size = function(obj) {
377 | if (obj == null) return 0;
378 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
379 | };
380 |
381 | // Array Functions
382 | // ---------------
383 |
384 | // Get the first element of an array. Passing **n** will return the first N
385 | // values in the array. Aliased as `head` and `take`. The **guard** check
386 | // allows it to work with `_.map`.
387 | _.first = _.head = _.take = function(array, n, guard) {
388 | if (array == null) return void 0;
389 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
390 | };
391 |
392 | // Returns everything but the last entry of the array. Especially useful on
393 | // the arguments object. Passing **n** will return all the values in
394 | // the array, excluding the last N. The **guard** check allows it to work with
395 | // `_.map`.
396 | _.initial = function(array, n, guard) {
397 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
398 | };
399 |
400 | // Get the last element of an array. Passing **n** will return the last N
401 | // values in the array. The **guard** check allows it to work with `_.map`.
402 | _.last = function(array, n, guard) {
403 | if (array == null) return void 0;
404 | if ((n != null) && !guard) {
405 | return slice.call(array, Math.max(array.length - n, 0));
406 | } else {
407 | return array[array.length - 1];
408 | }
409 | };
410 |
411 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
412 | // Especially useful on the arguments object. Passing an **n** will return
413 | // the rest N values in the array. The **guard**
414 | // check allows it to work with `_.map`.
415 | _.rest = _.tail = _.drop = function(array, n, guard) {
416 | return slice.call(array, (n == null) || guard ? 1 : n);
417 | };
418 |
419 | // Trim out all falsy values from an array.
420 | _.compact = function(array) {
421 | return _.filter(array, _.identity);
422 | };
423 |
424 | // Internal implementation of a recursive `flatten` function.
425 | var flatten = function(input, shallow, output) {
426 | each(input, function(value) {
427 | if (_.isArray(value)) {
428 | shallow ? push.apply(output, value) : flatten(value, shallow, output);
429 | } else {
430 | output.push(value);
431 | }
432 | });
433 | return output;
434 | };
435 |
436 | // Return a completely flattened version of an array.
437 | _.flatten = function(array, shallow) {
438 | return flatten(array, shallow, []);
439 | };
440 |
441 | // Return a version of the array that does not contain the specified value(s).
442 | _.without = function(array) {
443 | return _.difference(array, slice.call(arguments, 1));
444 | };
445 |
446 | // Produce a duplicate-free version of the array. If the array has already
447 | // been sorted, you have the option of using a faster algorithm.
448 | // Aliased as `unique`.
449 | _.uniq = _.unique = function(array, isSorted, iterator, context) {
450 | if (_.isFunction(isSorted)) {
451 | context = iterator;
452 | iterator = isSorted;
453 | isSorted = false;
454 | }
455 | var initial = iterator ? _.map(array, iterator, context) : array;
456 | var results = [];
457 | var seen = [];
458 | each(initial, function(value, index) {
459 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
460 | seen.push(value);
461 | results.push(array[index]);
462 | }
463 | });
464 | return results;
465 | };
466 |
467 | // Produce an array that contains the union: each distinct element from all of
468 | // the passed-in arrays.
469 | _.union = function() {
470 | return _.uniq(concat.apply(ArrayProto, arguments));
471 | };
472 |
473 | // Produce an array that contains every item shared between all the
474 | // passed-in arrays.
475 | _.intersection = function(array) {
476 | var rest = slice.call(arguments, 1);
477 | return _.filter(_.uniq(array), function(item) {
478 | return _.every(rest, function(other) {
479 | return _.indexOf(other, item) >= 0;
480 | });
481 | });
482 | };
483 |
484 | // Take the difference between one array and a number of other arrays.
485 | // Only the elements present in just the first array will remain.
486 | _.difference = function(array) {
487 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
488 | return _.filter(array, function(value){ return !_.contains(rest, value); });
489 | };
490 |
491 | // Zip together multiple lists into a single array -- elements that share
492 | // an index go together.
493 | _.zip = function() {
494 | var args = slice.call(arguments);
495 | var length = _.max(_.pluck(args, 'length'));
496 | var results = new Array(length);
497 | for (var i = 0; i < length; i++) {
498 | results[i] = _.pluck(args, "" + i);
499 | }
500 | return results;
501 | };
502 |
503 | // Converts lists into objects. Pass either a single array of `[key, value]`
504 | // pairs, or two parallel arrays of the same length -- one of keys, and one of
505 | // the corresponding values.
506 | _.object = function(list, values) {
507 | if (list == null) return {};
508 | var result = {};
509 | for (var i = 0, l = list.length; i < l; i++) {
510 | if (values) {
511 | result[list[i]] = values[i];
512 | } else {
513 | result[list[i][0]] = list[i][1];
514 | }
515 | }
516 | return result;
517 | };
518 |
519 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
520 | // we need this function. Return the position of the first occurrence of an
521 | // item in an array, or -1 if the item is not included in the array.
522 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
523 | // If the array is large and already in sort order, pass `true`
524 | // for **isSorted** to use binary search.
525 | _.indexOf = function(array, item, isSorted) {
526 | if (array == null) return -1;
527 | var i = 0, l = array.length;
528 | if (isSorted) {
529 | if (typeof isSorted == 'number') {
530 | i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
531 | } else {
532 | i = _.sortedIndex(array, item);
533 | return array[i] === item ? i : -1;
534 | }
535 | }
536 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
537 | for (; i < l; i++) if (array[i] === item) return i;
538 | return -1;
539 | };
540 |
541 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
542 | _.lastIndexOf = function(array, item, from) {
543 | if (array == null) return -1;
544 | var hasIndex = from != null;
545 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
546 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
547 | }
548 | var i = (hasIndex ? from : array.length);
549 | while (i--) if (array[i] === item) return i;
550 | return -1;
551 | };
552 |
553 | // Generate an integer Array containing an arithmetic progression. A port of
554 | // the native Python `range()` function. See
555 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
556 | _.range = function(start, stop, step) {
557 | if (arguments.length <= 1) {
558 | stop = start || 0;
559 | start = 0;
560 | }
561 | step = arguments[2] || 1;
562 |
563 | var len = Math.max(Math.ceil((stop - start) / step), 0);
564 | var idx = 0;
565 | var range = new Array(len);
566 |
567 | while(idx < len) {
568 | range[idx++] = start;
569 | start += step;
570 | }
571 |
572 | return range;
573 | };
574 |
575 | // Function (ahem) Functions
576 | // ------------------
577 |
578 | // Create a function bound to a given object (assigning `this`, and arguments,
579 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
580 | // available.
581 | _.bind = function(func, context) {
582 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
583 | var args = slice.call(arguments, 2);
584 | return function() {
585 | return func.apply(context, args.concat(slice.call(arguments)));
586 | };
587 | };
588 |
589 | // Partially apply a function by creating a version that has had some of its
590 | // arguments pre-filled, without changing its dynamic `this` context.
591 | _.partial = function(func) {
592 | var args = slice.call(arguments, 1);
593 | return function() {
594 | return func.apply(this, args.concat(slice.call(arguments)));
595 | };
596 | };
597 |
598 | // Bind all of an object's methods to that object. Useful for ensuring that
599 | // all callbacks defined on an object belong to it.
600 | _.bindAll = function(obj) {
601 | var funcs = slice.call(arguments, 1);
602 | if (funcs.length === 0) funcs = _.functions(obj);
603 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
604 | return obj;
605 | };
606 |
607 | // Memoize an expensive function by storing its results.
608 | _.memoize = function(func, hasher) {
609 | var memo = {};
610 | hasher || (hasher = _.identity);
611 | return function() {
612 | var key = hasher.apply(this, arguments);
613 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
614 | };
615 | };
616 |
617 | // Delays a function for the given number of milliseconds, and then calls
618 | // it with the arguments supplied.
619 | _.delay = function(func, wait) {
620 | var args = slice.call(arguments, 2);
621 | return setTimeout(function(){ return func.apply(null, args); }, wait);
622 | };
623 |
624 | // Defers a function, scheduling it to run after the current call stack has
625 | // cleared.
626 | _.defer = function(func) {
627 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
628 | };
629 |
630 | // Returns a function, that, when invoked, will only be triggered at most once
631 | // during a given window of time.
632 | _.throttle = function(func, wait) {
633 | var context, args, timeout, result;
634 | var previous = 0;
635 | var later = function() {
636 | previous = new Date;
637 | timeout = null;
638 | result = func.apply(context, args);
639 | };
640 | return function() {
641 | var now = new Date;
642 | var remaining = wait - (now - previous);
643 | context = this;
644 | args = arguments;
645 | if (remaining <= 0) {
646 | clearTimeout(timeout);
647 | timeout = null;
648 | previous = now;
649 | result = func.apply(context, args);
650 | } else if (!timeout) {
651 | timeout = setTimeout(later, remaining);
652 | }
653 | return result;
654 | };
655 | };
656 |
657 | // Returns a function, that, as long as it continues to be invoked, will not
658 | // be triggered. The function will be called after it stops being called for
659 | // N milliseconds. If `immediate` is passed, trigger the function on the
660 | // leading edge, instead of the trailing.
661 | _.debounce = function(func, wait, immediate) {
662 | var timeout, result;
663 | return function() {
664 | var context = this, args = arguments;
665 | var later = function() {
666 | timeout = null;
667 | if (!immediate) result = func.apply(context, args);
668 | };
669 | var callNow = immediate && !timeout;
670 | clearTimeout(timeout);
671 | timeout = setTimeout(later, wait);
672 | if (callNow) result = func.apply(context, args);
673 | return result;
674 | };
675 | };
676 |
677 | // Returns a function that will be executed at most one time, no matter how
678 | // often you call it. Useful for lazy initialization.
679 | _.once = function(func) {
680 | var ran = false, memo;
681 | return function() {
682 | if (ran) return memo;
683 | ran = true;
684 | memo = func.apply(this, arguments);
685 | func = null;
686 | return memo;
687 | };
688 | };
689 |
690 | // Returns the first function passed as an argument to the second,
691 | // allowing you to adjust arguments, run code before and after, and
692 | // conditionally execute the original function.
693 | _.wrap = function(func, wrapper) {
694 | return function() {
695 | var args = [func];
696 | push.apply(args, arguments);
697 | return wrapper.apply(this, args);
698 | };
699 | };
700 |
701 | // Returns a function that is the composition of a list of functions, each
702 | // consuming the return value of the function that follows.
703 | _.compose = function() {
704 | var funcs = arguments;
705 | return function() {
706 | var args = arguments;
707 | for (var i = funcs.length - 1; i >= 0; i--) {
708 | args = [funcs[i].apply(this, args)];
709 | }
710 | return args[0];
711 | };
712 | };
713 |
714 | // Returns a function that will only be executed after being called N times.
715 | _.after = function(times, func) {
716 | if (times <= 0) return func();
717 | return function() {
718 | if (--times < 1) {
719 | return func.apply(this, arguments);
720 | }
721 | };
722 | };
723 |
724 | // Object Functions
725 | // ----------------
726 |
727 | // Retrieve the names of an object's properties.
728 | // Delegates to **ECMAScript 5**'s native `Object.keys`
729 | _.keys = nativeKeys || function(obj) {
730 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
731 | var keys = [];
732 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
733 | return keys;
734 | };
735 |
736 | // Retrieve the values of an object's properties.
737 | _.values = function(obj) {
738 | var values = [];
739 | for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
740 | return values;
741 | };
742 |
743 | // Convert an object into a list of `[key, value]` pairs.
744 | _.pairs = function(obj) {
745 | var pairs = [];
746 | for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
747 | return pairs;
748 | };
749 |
750 | // Invert the keys and values of an object. The values must be serializable.
751 | _.invert = function(obj) {
752 | var result = {};
753 | for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
754 | return result;
755 | };
756 |
757 | // Return a sorted list of the function names available on the object.
758 | // Aliased as `methods`
759 | _.functions = _.methods = function(obj) {
760 | var names = [];
761 | for (var key in obj) {
762 | if (_.isFunction(obj[key])) names.push(key);
763 | }
764 | return names.sort();
765 | };
766 |
767 | // Extend a given object with all the properties in passed-in object(s).
768 | _.extend = function(obj) {
769 | each(slice.call(arguments, 1), function(source) {
770 | if (source) {
771 | for (var prop in source) {
772 | obj[prop] = source[prop];
773 | }
774 | }
775 | });
776 | return obj;
777 | };
778 |
779 | // Return a copy of the object only containing the whitelisted properties.
780 | _.pick = function(obj) {
781 | var copy = {};
782 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
783 | each(keys, function(key) {
784 | if (key in obj) copy[key] = obj[key];
785 | });
786 | return copy;
787 | };
788 |
789 | // Return a copy of the object without the blacklisted properties.
790 | _.omit = function(obj) {
791 | var copy = {};
792 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
793 | for (var key in obj) {
794 | if (!_.contains(keys, key)) copy[key] = obj[key];
795 | }
796 | return copy;
797 | };
798 |
799 | // Fill in a given object with default properties.
800 | _.defaults = function(obj) {
801 | each(slice.call(arguments, 1), function(source) {
802 | if (source) {
803 | for (var prop in source) {
804 | if (obj[prop] == null) obj[prop] = source[prop];
805 | }
806 | }
807 | });
808 | return obj;
809 | };
810 |
811 | // Create a (shallow-cloned) duplicate of an object.
812 | _.clone = function(obj) {
813 | if (!_.isObject(obj)) return obj;
814 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
815 | };
816 |
817 | // Invokes interceptor with the obj, and then returns obj.
818 | // The primary purpose of this method is to "tap into" a method chain, in
819 | // order to perform operations on intermediate results within the chain.
820 | _.tap = function(obj, interceptor) {
821 | interceptor(obj);
822 | return obj;
823 | };
824 |
825 | // Internal recursive comparison function for `isEqual`.
826 | var eq = function(a, b, aStack, bStack) {
827 | // Identical objects are equal. `0 === -0`, but they aren't identical.
828 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
829 | if (a === b) return a !== 0 || 1 / a == 1 / b;
830 | // A strict comparison is necessary because `null == undefined`.
831 | if (a == null || b == null) return a === b;
832 | // Unwrap any wrapped objects.
833 | if (a instanceof _) a = a._wrapped;
834 | if (b instanceof _) b = b._wrapped;
835 | // Compare `[[Class]]` names.
836 | var className = toString.call(a);
837 | if (className != toString.call(b)) return false;
838 | switch (className) {
839 | // Strings, numbers, dates, and booleans are compared by value.
840 | case '[object String]':
841 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
842 | // equivalent to `new String("5")`.
843 | return a == String(b);
844 | case '[object Number]':
845 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
846 | // other numeric values.
847 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
848 | case '[object Date]':
849 | case '[object Boolean]':
850 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
851 | // millisecond representations. Note that invalid dates with millisecond representations
852 | // of `NaN` are not equivalent.
853 | return +a == +b;
854 | // RegExps are compared by their source patterns and flags.
855 | case '[object RegExp]':
856 | return a.source == b.source &&
857 | a.global == b.global &&
858 | a.multiline == b.multiline &&
859 | a.ignoreCase == b.ignoreCase;
860 | }
861 | if (typeof a != 'object' || typeof b != 'object') return false;
862 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
863 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
864 | var length = aStack.length;
865 | while (length--) {
866 | // Linear search. Performance is inversely proportional to the number of
867 | // unique nested structures.
868 | if (aStack[length] == a) return bStack[length] == b;
869 | }
870 | // Add the first object to the stack of traversed objects.
871 | aStack.push(a);
872 | bStack.push(b);
873 | var size = 0, result = true;
874 | // Recursively compare objects and arrays.
875 | if (className == '[object Array]') {
876 | // Compare array lengths to determine if a deep comparison is necessary.
877 | size = a.length;
878 | result = size == b.length;
879 | if (result) {
880 | // Deep compare the contents, ignoring non-numeric properties.
881 | while (size--) {
882 | if (!(result = eq(a[size], b[size], aStack, bStack))) break;
883 | }
884 | }
885 | } else {
886 | // Objects with different constructors are not equivalent, but `Object`s
887 | // from different frames are.
888 | var aCtor = a.constructor, bCtor = b.constructor;
889 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
890 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
891 | return false;
892 | }
893 | // Deep compare objects.
894 | for (var key in a) {
895 | if (_.has(a, key)) {
896 | // Count the expected number of properties.
897 | size++;
898 | // Deep compare each member.
899 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
900 | }
901 | }
902 | // Ensure that both objects contain the same number of properties.
903 | if (result) {
904 | for (key in b) {
905 | if (_.has(b, key) && !(size--)) break;
906 | }
907 | result = !size;
908 | }
909 | }
910 | // Remove the first object from the stack of traversed objects.
911 | aStack.pop();
912 | bStack.pop();
913 | return result;
914 | };
915 |
916 | // Perform a deep comparison to check if two objects are equal.
917 | _.isEqual = function(a, b) {
918 | return eq(a, b, [], []);
919 | };
920 |
921 | // Is a given array, string, or object empty?
922 | // An "empty" object has no enumerable own-properties.
923 | _.isEmpty = function(obj) {
924 | if (obj == null) return true;
925 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
926 | for (var key in obj) if (_.has(obj, key)) return false;
927 | return true;
928 | };
929 |
930 | // Is a given value a DOM element?
931 | _.isElement = function(obj) {
932 | return !!(obj && obj.nodeType === 1);
933 | };
934 |
935 | // Is a given value an array?
936 | // Delegates to ECMA5's native Array.isArray
937 | _.isArray = nativeIsArray || function(obj) {
938 | return toString.call(obj) == '[object Array]';
939 | };
940 |
941 | // Is a given variable an object?
942 | _.isObject = function(obj) {
943 | return obj === Object(obj);
944 | };
945 |
946 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
947 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
948 | _['is' + name] = function(obj) {
949 | return toString.call(obj) == '[object ' + name + ']';
950 | };
951 | });
952 |
953 | // Define a fallback version of the method in browsers (ahem, IE), where
954 | // there isn't any inspectable "Arguments" type.
955 | if (!_.isArguments(arguments)) {
956 | _.isArguments = function(obj) {
957 | return !!(obj && _.has(obj, 'callee'));
958 | };
959 | }
960 |
961 | // Optimize `isFunction` if appropriate.
962 | if (typeof (/./) !== 'function') {
963 | _.isFunction = function(obj) {
964 | return typeof obj === 'function';
965 | };
966 | }
967 |
968 | // Is a given object a finite number?
969 | _.isFinite = function(obj) {
970 | return isFinite(obj) && !isNaN(parseFloat(obj));
971 | };
972 |
973 | // Is the given value `NaN`? (NaN is the only number which does not equal itself).
974 | _.isNaN = function(obj) {
975 | return _.isNumber(obj) && obj != +obj;
976 | };
977 |
978 | // Is a given value a boolean?
979 | _.isBoolean = function(obj) {
980 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
981 | };
982 |
983 | // Is a given value equal to null?
984 | _.isNull = function(obj) {
985 | return obj === null;
986 | };
987 |
988 | // Is a given variable undefined?
989 | _.isUndefined = function(obj) {
990 | return obj === void 0;
991 | };
992 |
993 | // Shortcut function for checking if an object has a given property directly
994 | // on itself (in other words, not on a prototype).
995 | _.has = function(obj, key) {
996 | return hasOwnProperty.call(obj, key);
997 | };
998 |
999 | // Utility Functions
1000 | // -----------------
1001 |
1002 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1003 | // previous owner. Returns a reference to the Underscore object.
1004 | _.noConflict = function() {
1005 | root._ = previousUnderscore;
1006 | return this;
1007 | };
1008 |
1009 | // Keep the identity function around for default iterators.
1010 | _.identity = function(value) {
1011 | return value;
1012 | };
1013 |
1014 | // Run a function **n** times.
1015 | _.times = function(n, iterator, context) {
1016 | var accum = Array(n);
1017 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1018 | return accum;
1019 | };
1020 |
1021 | // Return a random integer between min and max (inclusive).
1022 | _.random = function(min, max) {
1023 | if (max == null) {
1024 | max = min;
1025 | min = 0;
1026 | }
1027 | return min + Math.floor(Math.random() * (max - min + 1));
1028 | };
1029 |
1030 | // List of HTML entities for escaping.
1031 | var entityMap = {
1032 | escape: {
1033 | '&': '&',
1034 | '<': '<',
1035 | '>': '>',
1036 | '"': '"',
1037 | "'": ''',
1038 | '/': '/'
1039 | }
1040 | };
1041 | entityMap.unescape = _.invert(entityMap.escape);
1042 |
1043 | // Regexes containing the keys and values listed immediately above.
1044 | var entityRegexes = {
1045 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1046 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1047 | };
1048 |
1049 | // Functions for escaping and unescaping strings to/from HTML interpolation.
1050 | _.each(['escape', 'unescape'], function(method) {
1051 | _[method] = function(string) {
1052 | if (string == null) return '';
1053 | return ('' + string).replace(entityRegexes[method], function(match) {
1054 | return entityMap[method][match];
1055 | });
1056 | };
1057 | });
1058 |
1059 | // If the value of the named property is a function then invoke it;
1060 | // otherwise, return it.
1061 | _.result = function(object, property) {
1062 | if (object == null) return null;
1063 | var value = object[property];
1064 | return _.isFunction(value) ? value.call(object) : value;
1065 | };
1066 |
1067 | // Add your own custom functions to the Underscore object.
1068 | _.mixin = function(obj) {
1069 | each(_.functions(obj), function(name){
1070 | var func = _[name] = obj[name];
1071 | _.prototype[name] = function() {
1072 | var args = [this._wrapped];
1073 | push.apply(args, arguments);
1074 | return result.call(this, func.apply(_, args));
1075 | };
1076 | });
1077 | };
1078 |
1079 | // Generate a unique integer id (unique within the entire client session).
1080 | // Useful for temporary DOM ids.
1081 | var idCounter = 0;
1082 | _.uniqueId = function(prefix) {
1083 | var id = ++idCounter + '';
1084 | return prefix ? prefix + id : id;
1085 | };
1086 |
1087 | // By default, Underscore uses ERB-style template delimiters, change the
1088 | // following template settings to use alternative delimiters.
1089 | _.templateSettings = {
1090 | evaluate : /<%([\s\S]+?)%>/g,
1091 | interpolate : /<%=([\s\S]+?)%>/g,
1092 | escape : /<%-([\s\S]+?)%>/g
1093 | };
1094 |
1095 | // When customizing `templateSettings`, if you don't want to define an
1096 | // interpolation, evaluation or escaping regex, we need one that is
1097 | // guaranteed not to match.
1098 | var noMatch = /(.)^/;
1099 |
1100 | // Certain characters need to be escaped so that they can be put into a
1101 | // string literal.
1102 | var escapes = {
1103 | "'": "'",
1104 | '\\': '\\',
1105 | '\r': 'r',
1106 | '\n': 'n',
1107 | '\t': 't',
1108 | '\u2028': 'u2028',
1109 | '\u2029': 'u2029'
1110 | };
1111 |
1112 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
1113 |
1114 | // JavaScript micro-templating, similar to John Resig's implementation.
1115 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
1116 | // and correctly escapes quotes within interpolated code.
1117 | _.template = function(text, data, settings) {
1118 | var render;
1119 | settings = _.defaults({}, settings, _.templateSettings);
1120 |
1121 | // Combine delimiters into one regular expression via alternation.
1122 | var matcher = new RegExp([
1123 | (settings.escape || noMatch).source,
1124 | (settings.interpolate || noMatch).source,
1125 | (settings.evaluate || noMatch).source
1126 | ].join('|') + '|$', 'g');
1127 |
1128 | // Compile the template source, escaping string literals appropriately.
1129 | var index = 0;
1130 | var source = "__p+='";
1131 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1132 | source += text.slice(index, offset)
1133 | .replace(escaper, function(match) { return '\\' + escapes[match]; });
1134 |
1135 | if (escape) {
1136 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1137 | }
1138 | if (interpolate) {
1139 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1140 | }
1141 | if (evaluate) {
1142 | source += "';\n" + evaluate + "\n__p+='";
1143 | }
1144 | index = offset + match.length;
1145 | return match;
1146 | });
1147 | source += "';\n";
1148 |
1149 | // If a variable is not specified, place data values in local scope.
1150 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1151 |
1152 | source = "var __t,__p='',__j=Array.prototype.join," +
1153 | "print=function(){__p+=__j.call(arguments,'');};\n" +
1154 | source + "return __p;\n";
1155 |
1156 | try {
1157 | render = new Function(settings.variable || 'obj', '_', source);
1158 | } catch (e) {
1159 | e.source = source;
1160 | throw e;
1161 | }
1162 |
1163 | if (data) return render(data, _);
1164 | var template = function(data) {
1165 | return render.call(this, data, _);
1166 | };
1167 |
1168 | // Provide the compiled function source as a convenience for precompilation.
1169 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1170 |
1171 | return template;
1172 | };
1173 |
1174 | // Add a "chain" function, which will delegate to the wrapper.
1175 | _.chain = function(obj) {
1176 | return _(obj).chain();
1177 | };
1178 |
1179 | // OOP
1180 | // ---------------
1181 | // If Underscore is called as a function, it returns a wrapped object that
1182 | // can be used OO-style. This wrapper holds altered versions of all the
1183 | // underscore functions. Wrapped objects may be chained.
1184 |
1185 | // Helper function to continue chaining intermediate results.
1186 | var result = function(obj) {
1187 | return this._chain ? _(obj).chain() : obj;
1188 | };
1189 |
1190 | // Add all of the Underscore functions to the wrapper object.
1191 | _.mixin(_);
1192 |
1193 | // Add all mutator Array functions to the wrapper.
1194 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1195 | var method = ArrayProto[name];
1196 | _.prototype[name] = function() {
1197 | var obj = this._wrapped;
1198 | method.apply(obj, arguments);
1199 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1200 | return result.call(this, obj);
1201 | };
1202 | });
1203 |
1204 | // Add all accessor Array functions to the wrapper.
1205 | each(['concat', 'join', 'slice'], function(name) {
1206 | var method = ArrayProto[name];
1207 | _.prototype[name] = function() {
1208 | return result.call(this, method.apply(this._wrapped, arguments));
1209 | };
1210 | });
1211 |
1212 | _.extend(_.prototype, {
1213 |
1214 | // Start chaining a wrapped Underscore object.
1215 | chain: function() {
1216 | this._chain = true;
1217 | return this;
1218 | },
1219 |
1220 | // Extracts the result from a wrapped and chained object.
1221 | value: function() {
1222 | return this._wrapped;
1223 | }
1224 |
1225 | });
1226 |
1227 | }).call(this);
--------------------------------------------------------------------------------