2013-08-24 10:06:45 +00:00
|
|
|
|
/* global window */
|
2014-12-31 19:20:50 +00:00
|
|
|
|
(function(m, document, _, moment, jQuery, Cookies){
|
2013-08-24 10:06:45 +00:00
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// TODO Disqus
|
|
|
|
|
// TODO Show compatible emacs versions for any package
|
|
|
|
|
// TODO Google Analytics http://stackoverflow.com/questions/10713708/tracking-google-analytics-page-views-with-angular-js
|
|
|
|
|
// TODO D3 visualisation for deps
|
2013-10-06 07:51:15 +00:00
|
|
|
|
// TODO Show recent github events on package pages where applicable
|
|
|
|
|
// TODO Voting / starring
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2014-05-24 14:57:50 +00:00
|
|
|
|
// Helpers
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
function intersperse(seq, sep) {
|
|
|
|
|
var res = seq.slice(0,1);
|
|
|
|
|
for(var i=1; i < seq.length; ++i) {
|
|
|
|
|
res.push(sep);
|
|
|
|
|
res.push(seq[i]);
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Models
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
var melpa = {};
|
2014-10-23 09:18:20 +00:00
|
|
|
|
melpa.rootURL = window.location.protocol + "//" + window.location.host;
|
2014-05-24 14:57:50 +00:00
|
|
|
|
|
|
|
|
|
melpa.Package = function(data) {
|
|
|
|
|
["name", "description", "version", "dependencies", "source",
|
|
|
|
|
"downloads", "fetcher", "recipeURL", "packageURL", "sourceURL", "oldNames"].map(function(p) {
|
2014-06-18 13:29:04 +00:00
|
|
|
|
this[p] = data[p];
|
2014-05-24 14:57:50 +00:00
|
|
|
|
}.bind(this));
|
2014-08-02 11:04:16 +00:00
|
|
|
|
this._searchText = _([data.name, data.description, data.version])
|
2014-05-24 14:57:50 +00:00
|
|
|
|
.compact().valueOf().join(' ').toLowerCase();
|
2014-06-18 13:29:04 +00:00
|
|
|
|
this.readmeURL = "/packages/" + data.name + "-readme.txt";
|
2014-10-23 09:18:20 +00:00
|
|
|
|
this.badgeURL = "/packages/" + data.name + "-badge.svg";
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.matchesTerm = function(term) {
|
|
|
|
|
return this._searchText.indexOf(term) != -1;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
melpa.PackageList = function(packages) {
|
|
|
|
|
this.packages = packages;
|
2014-06-18 13:29:04 +00:00
|
|
|
|
this.totalDownloads = m.prop(_.reduce(_.map(packages, function(p) { return p.downloads || 0; }),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
function (a, b) { return b === undefined ? a : a + b; }, 0));
|
|
|
|
|
this.totalPackages = m.prop(packages.length);
|
|
|
|
|
var savedSearches = {};
|
|
|
|
|
function preFilteredPackages(term) {
|
|
|
|
|
var prefixes = _.sortBy(_.filter(_.keys(savedSearches),
|
|
|
|
|
function(k) { return term.indexOf(k) === 0; }),
|
|
|
|
|
'length').reverse();
|
2014-08-03 18:35:42 +00:00
|
|
|
|
return prefixes.length > 0 ? savedSearches[prefixes[0]] : packages;
|
2014-05-24 14:57:50 +00:00
|
|
|
|
}
|
2014-08-03 18:35:42 +00:00
|
|
|
|
this.sortedPackages = function(sortBy, sortAscending) {
|
2014-05-24 14:57:50 +00:00
|
|
|
|
var sortKey = sortBy + "-" + sortAscending;
|
2014-08-03 18:35:42 +00:00
|
|
|
|
if (this.packages.sortKey === sortKey) return this.packages;
|
|
|
|
|
if (this.packages.sortKey === sortBy + "-" + !sortAscending) {
|
|
|
|
|
this.packages = this.packages.reverse();
|
2014-05-28 12:49:42 +00:00
|
|
|
|
} else {
|
2014-08-03 18:35:42 +00:00
|
|
|
|
var sorted = _.sortBy(this.packages, function(p) { return p[sortBy]; });
|
|
|
|
|
this.packages = sortAscending ? sorted : sorted.reverse();
|
|
|
|
|
}
|
|
|
|
|
this.packages.sortKey = sortKey;
|
|
|
|
|
return this.packages;
|
|
|
|
|
};
|
|
|
|
|
this.matchingPackages = function(terms) {
|
|
|
|
|
var t = terms.trim().toLowerCase();
|
|
|
|
|
var matching = savedSearches[t];
|
|
|
|
|
if (!matching) {
|
|
|
|
|
matching = savedSearches[t] = _.filter(preFilteredPackages(t),
|
2014-10-26 14:41:36 +00:00
|
|
|
|
function(p) { return p.matchesTerm(t); });
|
2014-05-28 12:49:42 +00:00
|
|
|
|
}
|
2014-08-03 18:35:42 +00:00
|
|
|
|
var visible = {};
|
|
|
|
|
_.each(matching, function(p){ visible[p.name] = true; });
|
|
|
|
|
return visible;
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
|
|
|
|
var packagesByName = {};
|
2014-07-11 13:29:03 +00:00
|
|
|
|
_.each(packages, function(p) {
|
|
|
|
|
packagesByName[p.name] = p;
|
|
|
|
|
if(p.oldNames) {
|
|
|
|
|
_.each(p.oldNames, function(n) { packagesByName[n] = p; });
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.packageWithName = function(name) {
|
|
|
|
|
return packagesByName[name];
|
2013-08-24 10:06:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2014-06-18 13:29:04 +00:00
|
|
|
|
var downloadCounts = _.pluck(packages, 'downloads');
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.downloadsPercentileForPackage = function(p) {
|
2014-06-18 13:29:04 +00:00
|
|
|
|
return _.filter(downloadCounts, function(d) { return d < p.downloads; }).length * 100.0 / downloadCounts.length;
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.dependenciesOnPackageName = function(packageName) {
|
|
|
|
|
return (_.filter(packages, function(p) {
|
2014-06-18 13:29:04 +00:00
|
|
|
|
return _.findWhere(p.dependencies, {name: packageName});
|
2014-05-24 14:57:50 +00:00
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
};
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2014-05-24 14:57:50 +00:00
|
|
|
|
// Gather remote info about packages
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
melpa.packageList = m.sync([
|
|
|
|
|
m.request({method: 'GET', url: "/recipes.json"}),
|
|
|
|
|
m.request({method: 'GET', url: "/archive.json"}),
|
|
|
|
|
m.request({method: 'GET', url: "/download_counts.json"})
|
|
|
|
|
]).then(function (info) {
|
|
|
|
|
var recipes = info[0], archive = info[1], downloads = info[2];
|
2013-10-14 16:25:12 +00:00
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
var calculateSourceURL = function(name, recipe) {
|
|
|
|
|
if (recipe.fetcher == "github") {
|
2015-01-12 21:12:54 +00:00
|
|
|
|
if (/\//.test(recipe.repo)) {
|
|
|
|
|
return "https://github.com/" + recipe.repo +
|
|
|
|
|
(recipe.branch ? "/tree/" + recipe.branch : "");
|
|
|
|
|
} else {
|
|
|
|
|
return "https://gist.github.com/" + recipe.repo;
|
|
|
|
|
}
|
2014-05-24 14:57:50 +00:00
|
|
|
|
} else if (recipe.fetcher == "wiki" && !recipe.files) {
|
|
|
|
|
return "http://www.emacswiki.org/emacs/" + name + ".el";
|
|
|
|
|
} else if (recipe.url) {
|
|
|
|
|
var urlMatch = function(re, prefix) {
|
|
|
|
|
var m = recipe.url.match(re);
|
|
|
|
|
return m !== null ? prefix + m[0] : null;
|
|
|
|
|
};
|
|
|
|
|
return (urlMatch(/(bitbucket\.org\/[^\/]+\/[^\/\?]+)/, "https://") ||
|
|
|
|
|
urlMatch(/(gitorious\.org\/[^\/]+\/[^.]+)/, "https://") ||
|
2014-10-07 21:20:30 +00:00
|
|
|
|
urlMatch(/^lp:(.*)/, "https://launchpad.net/") ||
|
|
|
|
|
urlMatch(/^(https?:\/\/code\.google\.com\/p\/[^\/]+\/)/) ||
|
|
|
|
|
urlMatch(/^(https?:\/\/[^.]+\.googlecode\.com\/)/));
|
2014-05-24 14:57:50 +00:00
|
|
|
|
}
|
|
|
|
|
return null;
|
2013-08-24 10:06:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
var listed = _.intersection(_.keys(archive), _.keys(recipes));
|
|
|
|
|
return new melpa.PackageList(_(listed).reduce(function(pkgs, name) {
|
|
|
|
|
var built = archive[name];
|
|
|
|
|
var recipe = recipes[name];
|
2014-06-02 18:59:24 +00:00
|
|
|
|
var version = built.ver.join(".");
|
|
|
|
|
var deps = _.map(built.deps || [], function (ver, name) {
|
|
|
|
|
return {name: name, version: ver.join('.')};
|
2013-08-24 10:06:45 +00:00
|
|
|
|
});
|
2014-05-24 14:57:50 +00:00
|
|
|
|
var oldNames = recipe['old-names'] || [];
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
pkgs.push(new melpa.Package({
|
|
|
|
|
name: name,
|
|
|
|
|
version: version,
|
|
|
|
|
dependencies: deps,
|
2014-06-02 18:59:24 +00:00
|
|
|
|
description: built.desc.replace(/\s*\[((?:source: )?\w+)\]$/, ""),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
source: recipe.fetcher,
|
|
|
|
|
downloads: _.reduce(oldNames.concat(name), function(sum, n) { return sum + (downloads[n] || 0); }, 0),
|
|
|
|
|
fetcher: recipe.fetcher,
|
|
|
|
|
recipeURL: "https://github.com/milkypostman/melpa/blob/master/recipes/" + name,
|
2014-06-02 18:59:24 +00:00
|
|
|
|
packageURL: "packages/" + name + "-" + version + "." + (built.type == "single" ? "el" : "tar"),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
sourceURL: calculateSourceURL(name, recipe),
|
|
|
|
|
oldNames: oldNames
|
|
|
|
|
}));
|
|
|
|
|
return pkgs;
|
|
|
|
|
}, []));
|
2013-08-24 10:06:45 +00:00
|
|
|
|
});
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// View helpers
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
function glyphicon(name) {
|
|
|
|
|
return m("span.glyphicon.glyphicon-" + name);
|
|
|
|
|
}
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
2014-08-16 15:09:44 +00:00
|
|
|
|
function packageLink(pkg, contents) {
|
2014-08-17 07:44:27 +00:00
|
|
|
|
return m("a", {href: "/" + encodeURIComponent(pkg.name), config: m.route},
|
2014-08-16 15:09:44 +00:00
|
|
|
|
contents || pkg.name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-23 09:18:20 +00:00
|
|
|
|
function packagePath(pkg) {
|
|
|
|
|
if (m.route.mode !== "hash") throw "FIXME: unsupported route mode";
|
|
|
|
|
return "/#/" + encodeURIComponent(pkg.name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-31 18:20:28 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Pagination
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
melpa.paginator = {};
|
|
|
|
|
melpa.paginator.controller = function(getItemList) {
|
2015-01-12 21:12:15 +00:00
|
|
|
|
this.pageLength = m.prop(50);
|
2014-12-31 18:20:28 +00:00
|
|
|
|
this.windowSize = m.prop(7);
|
|
|
|
|
this.pageNumber = m.prop(1);
|
|
|
|
|
this.items = getItemList;
|
|
|
|
|
this.paginatedItems = function() {
|
|
|
|
|
if (this.pageNumber() !== null) {
|
|
|
|
|
return this.items().slice(this.pageLength() * (this.pageNumber() - 1),
|
|
|
|
|
this.pageLength() * this.pageNumber());
|
|
|
|
|
} else {
|
|
|
|
|
return this.items();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.maxPage = function() {
|
|
|
|
|
return Math.floor(this.items().length / this.pageLength());
|
|
|
|
|
};
|
|
|
|
|
this.prevPages = function() {
|
|
|
|
|
return _.last(_.range(1, this.pageNumber()),
|
|
|
|
|
Math.floor((this.windowSize() - 1) / 2));
|
|
|
|
|
};
|
|
|
|
|
this.nextPages = function() {
|
|
|
|
|
return _.first(_.range(this.pageNumber() + 1, this.maxPage()),
|
|
|
|
|
this.windowSize() - 1 - this.prevPages().length);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
melpa.paginator.view = function(ctrl) {
|
|
|
|
|
var prevPage = _.last(ctrl.prevPages());
|
|
|
|
|
var nextPage = _.first(ctrl.nextPages());
|
|
|
|
|
var pageLinkAttrs = function(n) {
|
|
|
|
|
if (n)
|
|
|
|
|
return { onclick: function(){ ctrl.pageNumber(n); } };
|
|
|
|
|
};
|
|
|
|
|
var pageLink = function(n) {
|
|
|
|
|
return m("li", m("a", pageLinkAttrs(n), m("span", n)));
|
|
|
|
|
};
|
|
|
|
|
return m("nav",
|
|
|
|
|
m("ul.pagination", [
|
|
|
|
|
m("li", { class: (prevPage ? "" : "disabled") },
|
|
|
|
|
m("a", pageLinkAttrs(prevPage), [
|
|
|
|
|
m("span", {"aria-hidden": "true"}, m.trust("«")),
|
|
|
|
|
m("span.sr-only", "Previous")
|
|
|
|
|
])),
|
|
|
|
|
ctrl.prevPages().map(pageLink),
|
|
|
|
|
m("li.active", m("a", m("span", [ctrl.pageNumber(), " ", m("span.sr-only", "(current)")]))),
|
|
|
|
|
ctrl.nextPages().map(pageLink),
|
|
|
|
|
m("li", { class: (nextPage ? "" : "disabled") },
|
|
|
|
|
m("a", pageLinkAttrs(nextPage), [
|
|
|
|
|
m("span", {"aria-hidden": "true"}, m.trust("»")),
|
|
|
|
|
m("span.sr-only", "Next")
|
|
|
|
|
]))
|
|
|
|
|
]));
|
|
|
|
|
};
|
|
|
|
|
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2014-05-24 14:57:50 +00:00
|
|
|
|
// Package list
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
melpa.packagelist = {};
|
|
|
|
|
melpa.packagelist.controller = function() {
|
|
|
|
|
this.filterTerms = m.prop(m.route.param('q') || '');
|
|
|
|
|
this.sortBy = m.prop("name");
|
|
|
|
|
this.sortAscending = m.prop(true);
|
2014-10-26 14:42:52 +00:00
|
|
|
|
this.packageList = melpa.packageList;
|
2014-08-03 18:35:42 +00:00
|
|
|
|
this.matchingPackages = function() {
|
|
|
|
|
return this.packageList().matchingPackages(this.filterTerms());
|
|
|
|
|
};
|
|
|
|
|
this.sortedPackages = function() {
|
2014-12-31 18:20:28 +00:00
|
|
|
|
var visible = this.matchingPackages();
|
|
|
|
|
return this.packageList()
|
|
|
|
|
.sortedPackages(this.sortBy(), this.sortAscending())
|
|
|
|
|
.filter(function(p) { return visible[p.name]; });
|
|
|
|
|
}.bind(this);
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.toggleSort = function(field) {
|
|
|
|
|
if (this.sortBy() == field) {
|
|
|
|
|
this.sortAscending(!this.sortAscending());
|
|
|
|
|
} else {
|
|
|
|
|
this.sortAscending(true);
|
|
|
|
|
this.sortBy(field);
|
2013-08-24 10:06:45 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2014-12-31 19:20:50 +00:00
|
|
|
|
this.wantPagination = function() {
|
|
|
|
|
return !Cookies.get("nopagination");
|
|
|
|
|
};
|
|
|
|
|
this.togglePagination = function() {
|
|
|
|
|
console.log("toggle " + this.wantPagination());
|
|
|
|
|
if (this.wantPagination()) {
|
|
|
|
|
Cookies.set("nopagination", "1");
|
|
|
|
|
} else {
|
|
|
|
|
Cookies.expire("nopagination");
|
|
|
|
|
}
|
|
|
|
|
console.log("toggled " + Cookies.get("nopagination"));
|
|
|
|
|
};
|
2014-12-31 18:20:28 +00:00
|
|
|
|
this.paginatorCtrl = new melpa.paginator.controller(this.sortedPackages);
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
melpa.packagelist.view = function(ctrl) {
|
|
|
|
|
var sortToggler = function(field) {
|
|
|
|
|
return function() { return ctrl.toggleSort(field); };
|
2013-08-24 10:06:45 +00:00
|
|
|
|
};
|
2014-05-24 14:57:50 +00:00
|
|
|
|
var sortIndicator = function(field) {
|
|
|
|
|
return glyphicon((field != ctrl.sortBy()) ? "minus" : (ctrl.sortAscending() ? "chevron-down" : "chevron-up"));
|
|
|
|
|
};
|
|
|
|
|
return m("section#packages", [
|
|
|
|
|
m("h2", [
|
|
|
|
|
"Current List of ",
|
2014-08-22 08:52:49 +00:00
|
|
|
|
ctrl.packageList().totalPackages().toLocaleString(),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
" Packages ",
|
|
|
|
|
m("small", [
|
2014-08-22 08:52:49 +00:00
|
|
|
|
ctrl.packageList().totalDownloads().toLocaleString(),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
" downloads to date"
|
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m("p", [
|
2014-12-31 18:20:28 +00:00
|
|
|
|
m("input.form-control[type=search]", {
|
|
|
|
|
placeholder: "Enter filter terms", autofocus: true,
|
|
|
|
|
value: ctrl.filterTerms(), onkeyup: m.withAttr("value", ctrl.filterTerms)
|
|
|
|
|
}),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
" ",
|
2014-12-31 18:20:28 +00:00
|
|
|
|
m("span.help-block", [_.keys(ctrl.matchingPackages()).length, " matching package(s)"])
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]),
|
|
|
|
|
m("table#package-list.table.table-bordered.table-responsive.table-hover", [
|
|
|
|
|
m("thead", [
|
|
|
|
|
m("tr", [
|
2014-06-09 18:36:30 +00:00
|
|
|
|
m("th.sortable", {onclick: sortToggler("name")}, ["Package", sortIndicator("name")]),
|
|
|
|
|
m("th.sortable", {onclick: sortToggler("description")}, ["Description", sortIndicator("description")]),
|
|
|
|
|
m("th.sortable", {onclick: sortToggler("version")}, ["Version", sortIndicator("version")]),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("th", "Recipe"),
|
2014-06-09 18:36:30 +00:00
|
|
|
|
m("th.sortable", {onclick: sortToggler("fetcher")}, ["Source", sortIndicator("fetcher")]),
|
|
|
|
|
m("th.sortable", {onclick: sortToggler("downloads")}, ["DLs", sortIndicator("downloads")]),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m("tbody",
|
2014-12-31 19:20:50 +00:00
|
|
|
|
(ctrl.wantPagination() ? ctrl.paginatorCtrl.paginatedItems() : ctrl.sortedPackages()).map(function(p) {
|
2014-08-20 07:53:06 +00:00
|
|
|
|
return m("tr", { key: p.name }, [
|
2014-08-16 15:09:44 +00:00
|
|
|
|
m("td", packageLink(p)),
|
2014-08-17 07:47:35 +00:00
|
|
|
|
m("td", packageLink(p, p.description)),
|
2014-09-27 19:28:05 +00:00
|
|
|
|
m("td.version", m("a", {href: p.packageURL}, [p.version, " ", glyphicon('download')])),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("td.recipe", [
|
2014-06-18 13:29:04 +00:00
|
|
|
|
m("a", {href: p.recipeURL}, [
|
2014-05-24 14:57:50 +00:00
|
|
|
|
glyphicon('cutlery')
|
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m("td.source", [
|
2014-06-18 13:29:04 +00:00
|
|
|
|
p.sourceURL ? m("a", {href: p.sourceURL}, [p.source]) : p.source
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]),
|
2014-08-22 08:52:49 +00:00
|
|
|
|
m("td", [p.downloads.toLocaleString()])
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]);
|
|
|
|
|
}))
|
2014-12-31 18:20:28 +00:00
|
|
|
|
]),
|
2014-12-31 19:20:50 +00:00
|
|
|
|
(ctrl.wantPagination() ? melpa.paginator.view(ctrl.paginatorCtrl) : null),
|
|
|
|
|
m("small",
|
|
|
|
|
m("a", {onclick: ctrl.togglePagination.bind(ctrl)},
|
|
|
|
|
(ctrl.wantPagination() ? "Disable pagination (may slow down display)" : "Enable pagination")
|
|
|
|
|
))
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]);
|
|
|
|
|
};
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Package details
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
melpa.packagedetails = {};
|
|
|
|
|
melpa.packagedetails.controller = function() {
|
2014-08-16 15:10:24 +00:00
|
|
|
|
this.packageName = m.route.param("package");
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.package = m.prop();
|
|
|
|
|
this.readme = m.prop('No description available.');
|
|
|
|
|
this.neededBy = m.prop([]);
|
|
|
|
|
this.downloadsPercentile = m.prop(0);
|
2014-10-23 09:18:20 +00:00
|
|
|
|
this.archivename = new melpa.archivename.controller();
|
2014-05-24 14:57:50 +00:00
|
|
|
|
|
|
|
|
|
melpa.packageList.then(function(packageList) {
|
2014-08-16 15:10:24 +00:00
|
|
|
|
var p = packageList.packageWithName(this.packageName);
|
2014-05-24 14:57:50 +00:00
|
|
|
|
if (!p) return;
|
|
|
|
|
this.package(p);
|
|
|
|
|
this.downloadsPercentile(packageList.downloadsPercentileForPackage(p));
|
2014-08-18 08:11:18 +00:00
|
|
|
|
this.neededBy(_.sortBy(packageList.dependenciesOnPackageName(this.packageName), 'name'));
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.packageWithName = packageList.packageWithName;
|
|
|
|
|
m.request({method: "GET",
|
2014-06-18 13:29:04 +00:00
|
|
|
|
url: p.readmeURL,
|
2014-05-24 14:57:50 +00:00
|
|
|
|
deserialize: function(v){return v;}
|
|
|
|
|
}).then(this.readme);
|
|
|
|
|
}.bind(this));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
melpa.packagedetails.view = function(ctrl) {
|
|
|
|
|
var pkg = ctrl.package();
|
2014-08-16 15:10:24 +00:00
|
|
|
|
if (!pkg) return m("h1", ["Package not found: ", ctrl.packageName]);
|
2014-05-24 14:57:50 +00:00
|
|
|
|
this.depLink = function(dep) {
|
|
|
|
|
var depPkg = ctrl.packageWithName(dep.name);
|
|
|
|
|
var label = dep.name + " " + dep.version;
|
2014-08-18 08:07:52 +00:00
|
|
|
|
return depPkg ? packageLink(depPkg, label) : label;
|
|
|
|
|
};
|
|
|
|
|
this.reverseDepLink = function(dep) {
|
|
|
|
|
var depPkg = ctrl.packageWithName(dep.name);
|
|
|
|
|
return depPkg ? packageLink(depPkg, dep.name) : dep.name;
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
2014-10-26 20:14:26 +00:00
|
|
|
|
var badgeURL = melpa.rootURL + pkg.badgeURL;
|
|
|
|
|
var fullURL = melpa.rootURL + packagePath(pkg);
|
|
|
|
|
|
2014-05-24 14:57:50 +00:00
|
|
|
|
return m("section", [
|
|
|
|
|
m("h1", [
|
2014-06-18 13:29:04 +00:00
|
|
|
|
pkg.name,
|
2014-05-24 14:57:50 +00:00
|
|
|
|
" ",
|
2014-06-18 13:29:04 +00:00
|
|
|
|
m("small", pkg.version)
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]),
|
2014-06-18 13:29:04 +00:00
|
|
|
|
m("p.lead", pkg.description),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("p", [
|
2014-06-18 13:29:04 +00:00
|
|
|
|
m("a.btn.btn-default", {href: pkg.recipeURL}, [glyphicon('cutlery'), " Recipe"]), ' ',
|
|
|
|
|
m("a.btn.btn-default", {href: pkg.packageURL}, [glyphicon('download'), " Download"]), ' ',
|
|
|
|
|
(pkg.sourceURL ? m("a.btn.btn-default", {href: pkg.sourceURL}, [glyphicon('home'), " Homepage"]) : '')
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]),
|
|
|
|
|
m("section", [
|
|
|
|
|
m(".well", [
|
2014-07-28 08:33:23 +00:00
|
|
|
|
m("dl.dl-horizontal", [
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("dt", "Downloads"),
|
|
|
|
|
m("dd", [
|
2014-08-22 08:52:49 +00:00
|
|
|
|
pkg.downloads.toLocaleString(),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("span.muted", " (all versions)"),
|
|
|
|
|
", percentile: ",
|
|
|
|
|
ctrl.downloadsPercentile().toFixed(2)
|
|
|
|
|
]),
|
|
|
|
|
m("dt", "Source"),
|
|
|
|
|
m("dd", [
|
2014-06-18 13:29:04 +00:00
|
|
|
|
pkg.sourceURL ? m("a", {href: pkg.sourceURL}, pkg.source) : m("span", pkg.source)
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]),
|
|
|
|
|
m("dt", "Dependencies"),
|
2014-08-18 08:11:18 +00:00
|
|
|
|
m("dd", intersperse(_.sortBy(pkg.dependencies, 'name').map(this.depLink), " / ")),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("dt", "Needed by"),
|
2014-08-18 08:07:52 +00:00
|
|
|
|
m("dd", intersperse(ctrl.neededBy().map(this.reverseDepLink), " / ")),
|
2014-07-28 08:33:23 +00:00
|
|
|
|
pkg.oldNames.length > 0 ? [
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("dt", "Renamed from:"),
|
2014-10-27 13:10:21 +00:00
|
|
|
|
m("dd", intersperse(pkg.oldNames, ', '))
|
2014-05-24 14:57:50 +00:00
|
|
|
|
// m("dt", "Old name needed by:"),
|
|
|
|
|
// m("dd", "TODO")
|
2014-07-28 08:33:23 +00:00
|
|
|
|
] : []
|
|
|
|
|
])
|
2014-05-24 14:57:50 +00:00
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m("section", [
|
|
|
|
|
m("h4", "Description"),
|
|
|
|
|
m("pre", ctrl.readme())
|
2014-10-23 09:18:20 +00:00
|
|
|
|
]),
|
|
|
|
|
m("section",
|
|
|
|
|
m("h4", "Badge code"),
|
|
|
|
|
m(".well", [
|
2014-10-23 09:23:17 +00:00
|
|
|
|
packageLink(pkg, m("img", {alt: ctrl.archivename.archiveName(), src: melpa.rootURL + pkg.badgeURL})),
|
2014-10-23 09:18:20 +00:00
|
|
|
|
m("dl", [
|
|
|
|
|
m("dt", "HTML"),
|
2014-10-26 20:14:26 +00:00
|
|
|
|
m("dd", m("pre", '<a href="' + fullURL + '"><img alt="' + ctrl.archivename.archiveName() + '" src="' + badgeURL + '"/></a>')),
|
|
|
|
|
m("dt", "Markdown"),
|
|
|
|
|
m("dd", m("pre", "[![" + ctrl.archivename.archiveName() + "](" + badgeURL + ")](" + fullURL + ")")),
|
|
|
|
|
m("dt", "Org"),
|
|
|
|
|
m("dd", m("pre", '[[' + fullURL + '][file:' + badgeURL + ']]'))
|
2014-10-23 09:18:20 +00:00
|
|
|
|
])
|
2015-01-14 09:43:22 +00:00
|
|
|
|
]))
|
2014-05-24 14:57:50 +00:00
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Showing last build time
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
melpa.buildstatus = {};
|
|
|
|
|
melpa.buildstatus.controller = function() {
|
|
|
|
|
this.buildCompletionTime = m.request({method: 'GET', url: "/build-status.json"})
|
|
|
|
|
.then(function(status){
|
|
|
|
|
return new Date(status.completed * 1000);
|
2015-01-14 09:43:22 +00:00
|
|
|
|
});
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
|
|
|
|
melpa.buildstatus.view = function(ctrl) {
|
|
|
|
|
return m(".alert.alert-success", [
|
|
|
|
|
m("strong", "Last build ended: "),
|
|
|
|
|
m("span", [moment(ctrl.buildCompletionTime()).fromNow()])
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
2013-08-24 10:06:45 +00:00
|
|
|
|
|
2014-06-17 09:25:54 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Changing the appearance of the MELPA Stable page
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2014-10-23 05:04:29 +00:00
|
|
|
|
melpa.stable = m.prop(window.location.host === 'melpa-stable.milkbox.net' || window.location.host === 'stable.melpa.org');
|
2014-06-17 09:25:54 +00:00
|
|
|
|
melpa.archivename = {};
|
|
|
|
|
melpa.archivename.controller = function() {
|
|
|
|
|
this.archiveName = function() {
|
|
|
|
|
return melpa.stable() ? "MELPA Stable" : "MELPA";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
melpa.archivename.view = function(ctrl) {
|
|
|
|
|
return m("span", ctrl.archiveName());
|
|
|
|
|
};
|
|
|
|
|
|
2014-06-21 13:09:41 +00:00
|
|
|
|
jQuery(window).load(function() {
|
2014-06-17 09:25:54 +00:00
|
|
|
|
document.title = (new melpa.archivename.controller()).archiveName();
|
2015-01-01 15:36:42 +00:00
|
|
|
|
jQuery(".archive-name").each(function(i, e) {
|
2014-10-23 08:51:03 +00:00
|
|
|
|
// jshint unused: false
|
2014-06-17 09:25:54 +00:00
|
|
|
|
m.module(e, melpa.archivename);
|
|
|
|
|
});
|
|
|
|
|
if (melpa.stable()) {
|
2014-06-21 13:09:41 +00:00
|
|
|
|
jQuery("html").addClass("stable");
|
2014-06-17 09:25:54 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2014-05-24 14:57:50 +00:00
|
|
|
|
// Static pages
|
2013-08-24 10:06:45 +00:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2014-05-24 14:57:50 +00:00
|
|
|
|
|
|
|
|
|
melpa.staticpage = function(partialPath) {
|
|
|
|
|
this.controller = function() {
|
2015-01-14 09:43:22 +00:00
|
|
|
|
this.content = m.prop('');
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m.request({method: "GET", url: partialPath,
|
|
|
|
|
deserialize: function(v){return v;}
|
|
|
|
|
}).then(this.content);
|
|
|
|
|
};
|
|
|
|
|
this.view = function(ctrl) {
|
|
|
|
|
return m("div", [m.trust(ctrl.content())]);
|
2013-08-24 10:06:45 +00:00
|
|
|
|
};
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Front page
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
melpa.frontpage = {};
|
|
|
|
|
melpa.frontpage.controller = function() {
|
|
|
|
|
this.packagelist = new melpa.packagelist.controller();
|
|
|
|
|
this.buildstatus = new melpa.buildstatus.controller();
|
2014-06-17 09:25:54 +00:00
|
|
|
|
this.archivename = new melpa.archivename.controller();
|
2014-05-24 14:57:50 +00:00
|
|
|
|
};
|
|
|
|
|
melpa.frontpage.view = function(ctrl) {
|
|
|
|
|
return m("div", [
|
|
|
|
|
m("section.page-header", [
|
|
|
|
|
m("h1", [
|
2014-06-17 09:25:54 +00:00
|
|
|
|
melpa.archivename.view(ctrl.archivename),
|
2014-05-24 14:57:50 +00:00
|
|
|
|
m("small", " (Milkypostman’s Emacs Lisp Package Archive)")
|
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m(".row", [
|
|
|
|
|
m(".col-md-8", [
|
|
|
|
|
m("section.jumbotron", [
|
|
|
|
|
m("ul", [
|
2015-01-14 09:32:04 +00:00
|
|
|
|
"<strong>Up-to-date packages built on our servers from upstream source</strong>",
|
|
|
|
|
"<strong>Installable in any recent Emacs using 'package.el'</strong> - no need to install svn/cvs/hg/bzr/git/darcs/fossil etc.",
|
|
|
|
|
"<strong>Curated</strong> - no obsolete, renamed, forked or randomly hacked packages",
|
|
|
|
|
"<strong>Comprehensive</strong> - more packages than any other archive",
|
|
|
|
|
"<strong>Automatic updates</strong> - new commits result in new packages",
|
|
|
|
|
"<strong>Extensible</strong> - contribute recipes via github, and we'll build the packages"
|
|
|
|
|
].map(function(content) { return m("li", m.trust(content)); }))
|
2014-05-24 14:57:50 +00:00
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
m(".col-md-4", [
|
|
|
|
|
melpa.buildstatus.view(ctrl.buildstatus),
|
2015-01-13 15:02:15 +00:00
|
|
|
|
m.trust('<a class="twitter-timeline" data-dnt="true" data-related="milkypostman,sanityinc" href="https://twitter.com/melpa_emacs" data-widget-id="311867756586864640">Tweets by @melpa_emacs</a>'),
|
|
|
|
|
m('script', {src: "http://platform.twitter.com/widgets.js", type: "text/javascript"})
|
2014-05-24 14:57:50 +00:00
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
melpa.packagelist.view(ctrl.packagelist)
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Routing
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
melpa.gettingstarted = new melpa.staticpage("/partials/getting-started.html");
|
|
|
|
|
|
|
|
|
|
m.route.mode = "hash";
|
|
|
|
|
m.route(document.getElementById("content"), "/", {
|
|
|
|
|
"/": melpa.frontpage,
|
|
|
|
|
"/getting-started": melpa.gettingstarted,
|
2014-05-27 10:56:12 +00:00
|
|
|
|
"/:package": melpa.packagedetails
|
2013-08-24 10:06:45 +00:00
|
|
|
|
});
|
2014-12-31 19:20:50 +00:00
|
|
|
|
})(window.m, window.document, window._, window.moment, window.jQuery, window.Cookies);
|