samhuri.net/public/js/gitter.js

765 lines
21 KiB
JavaScript

/// gitter
/// http://github.com/samsonjs/gitter
/// @_sjs
///
/// Copyright 2010 - 2012 Sami Samhuri <sami@samhuri.net>
/// MIT License
(function() {
"use strict"
var global = (function() { return this || (1, eval)('this') }())
, isBrowser = 'document' in global
, ie
if (isBrowser) {
ie = (function() {
var undef
, v = 3
, div = document.createElement('div')
, all = div.getElementsByTagName('i')
while (
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]
);
return v > 4 ? v : undef
}())
}
var inherits
if ('create' in Object) {
// util.inherits from node
inherits = function(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false
}
})
}
}
else if ([].__proto__) {
inherits = function(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype.__proto__ = superCtor.prototype
ctor.prototype.constructor = ctor
}
}
else { // ie8
var __hasProp = Object.prototype.hasOwnProperty
inherits = function(child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key)) child[key] = parent[key]
}
function ctor() { this.constructor = child }
ctor.prototype = parent.prototype
child.prototype = new ctor
child.__super__ = parent.prototype
return child
}
}
var api = {
// Blob
blob: function(user, repo, sha, cb) {
return new Blob(user, repo, sha, cb)
}
// Branch
, branch: function(user, repo, name, cb) {
return new Branch(user, repo, name, cb)
}
// Commit
, commit: function(user, repo, sha, cb) {
return new Commit(user, repo, sha, cb)
}
// Download
, download: function(user, repo, id, cb) {
return new Download(user, repo, id, cb)
}
// Issue
, issue: function(user, repo, id, cb) {
return new Issue(user, repo, id, cb)
}
// Organization
, org: function(name, cb) {
return new Org(name, cb)
}
, members: function(name, cb) {
return new Org(name).fetchMembers(cb)
}
// Repo
, repo: function(user, repo, cb) {
return new Repo(user, repo, cb)
}
, branches: function(user, repo, cb) {
return new Repo(user, repo).fetchBranches(cb)
}
, collaborators: function(user, repo, cb) {
return new Repo(user, repo).fetchCollaborators(cb)
}
, contributors: function(user, repo, cb) {
return new Repo(user, repo).fetchContributors(cb)
}
, downloads: function(user, repo, cb) {
return new Repo(user, repo).fetchDownloads(cb)
}
, forks: function(user, repo, cb) {
return new Repo(user, repo).fetchForks(cb)
}
, issues: function(user, repo, cb) {
return new Repo(user, repo).fetchIssues(cb)
}
, languages: function(user, repo, cb) {
return new Repo(user, repo).fetchLanguages(cb)
}
, tags: function(user, repo, cb) {
return new Repo(user, repo).fetchTags(cb)
}
, watchers: function(user, repo, cb) {
return new Repo(user, repo).fetchWatchers(cb)
}
, ref: function(user, repo, name, cb) {
return new Ref(user, repo, name, cb)
}
// Tag
, tag: function(user, repo, name, cb) {
return new Tag(user, repo, name, cb)
}
// Tree
, tree: function(user, repo, sha, cb) {
return new Tree(user, repo, sha, cb)
}
// User
, user: function(login, cb) {
return new User(login, cb)
}
, followers: function(login, cb) {
return new User(login).fetchFollowers(cb)
}
, following: function(login, cb) {
return new User(login).fetchFollowing(cb)
}
, repos: function(login, cb) {
return new User(login).fetchRepos(cb)
}
, watched: function(login, cb) {
return new User(login).fetchWatched(cb)
}
// Why not, expose the resources directly as well.
, Blob: Blob
, Branch: Branch
, Commit: Commit
, Download: Download
, Issue: Issue
, Org: Org
, Ref: Ref
, Repo: Repo
, Tree: Tree
, User: User
}
// when running in the browser request is set later, in shim()
var request
if (isBrowser) {
shim()
global.GITR = api
}
else {
var https = require('https')
request = function(options, cb) {
var req = https.request(options, function(response) {
var bodyParts = []
response.on('data', function(b) { bodyParts.push(b) })
response.on('end', function() {
var body = bodyParts.join('')
if (response.statusCode === 200) {
cb(null, body, response)
}
else {
console.dir(options, response, body)
var err = new Error('http error')
err.statusCode = response.statusCode
err.body = body
cb(err)
}
})
})
req.end()
req.on('error', function(err) { cb(err) })
}
module.exports = api
}
// Generic Resource //
//
// Used as the prototype by createResource. Provides
// methods for fetching the resource and related
// sub-resources.
function Resource() {}
// Fetch data for this resource and pass it to the
// callback after mixing the data into the object.
// Data is also available via the `data` property.
Resource.prototype.fetch = function(cb) {
if (this.data) {
cb.call(this, null, this.data)
}
else {
var self = this
fetch(this.path, function(err, data) {
// console.log('FETCH', self.path, err, data)
if (err) {
// console.log(err)
}
else {
self.data = data
mixin(self, data)
}
if (typeof cb === 'function') {
cb.call(self, err, data)
}
})
}
return this
}
Resource.prototype.fetchSubResource = function(thing, cb) {
if (this['_' + thing]) {
cb(null, this['_' + thing])
}
else {
var self = this
fetch(this.path + '/' + thing, function(err, data) {
// console.log('FETCH SUBRESOURCE', self.path, thing, err, data)
if (err) {
// console.log(self.path, err)
}
else {
self['_' + thing] = data
}
if (typeof cb === 'function') {
cb.call(self, err, data)
}
})
}
return this
}
var __slice = Array.prototype.slice
// Create a resource w/ Resource as the prototype.
//
// spec: an object with the following properties:
//
// - constructor: a constructor function
// - has: a list of resources belonging to this resource
//
// Typically the constructor accepts one or more arguments specifying
// the name or pieces of info identifying the specific resource and
// used to build the URL to fetch it. It also accepts an optional
// callback as the last parameter.
//
// The constructor must set the `path` property which is used to
// fetch the resource.
//
// If a callback is provided then the resource is immediately
// fetched and the callback is threaded through to the `fetch`
// method. The callback function has the signature
// `function(err, data)`.
//
// The `has` list specifies sub-resources, e.g. a user has repos,
// followers, etc. An organization has members.
//
// Each related sub-resource gets a method named appropriately,
// e.g. the User resource has followers so User objects have a
// `fetchFollowers` method.
function createResource(spec) {
var subResources = spec.has ? __slice.call(spec.has) : null
, resource = function(/* ..., cb */) {
var args = __slice.call(arguments)
, lastArgIsCallback = typeof args[args.length - 1] === 'function'
, cb = lastArgIsCallback ? args.pop() : null
, result = spec.constructor.apply(this, args)
if (typeof cb === 'function') {
this.fetch(cb)
}
return result
}
inherits(resource, Resource)
if (subResources) {
subResources.forEach(function(thing) {
var fnName = 'fetch' + toTitleCase(thing)
resource.prototype[fnName] = function(cb) {
return this.fetchSubResource(thing, cb)
}
})
}
return resource
}
// Define Resources //
var Blob = createResource({
constructor: function(user, repo, sha) {
this.user = user
this.repo = repo
this.sha = sha
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/blobs/' + sha
}
})
var Branch = createResource({
constructor: function (user, repo, name) {
this.user = user
this.repo = repo
this.name = name
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/heads/' + name
}
})
var Commit = createResource({
constructor: function Commit(user, repo, sha) {
this.user = user
this.repo = repo
this.sha = sha
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/commits/' + sha
}
})
var Download = createResource({
constructor: function(user, repo, id) {
this.user = user
this.repo = repo
this.id = id
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/downloads/' + id
}
})
var Issue = createResource({
constructor: function(user, repo, id) {
this.user = user
this.repo = repo
this.id = id
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/issues/' + id
}
})
var Org = createResource({
constructor: function(name) {
this.name = name
this.path = '/orgs/' + encodeURIComponent(nam)
}
, has: 'members repos'.split(' ')
})
var Ref = createResource({
constructor: function (user, repo, name) {
this.user = user
this.repo = repo
this.name = name
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/' + name
}
})
var Repo = createResource({
constructor: function(user, repo) {
this.user = user
this.repo = repo
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/')
}
, has: ('branches collaborators contributors downloads' +
' forks languages tags teams watchers').split(' ')
})
var Tag = createResource({
constructor: function (user, repo, name) {
this.user = user
this.repo = repo
this.name = name
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/tags/' + name
}
})
var Tree = createResource({
constructor: function(user, repo, sha) {
this.user = user
this.repo = repo
this.sha = sha
this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/trees/' + sha
}
})
var User = createResource({
constructor: function(login) {
// Allow creating a user from an object returned by the API
if (login.login) {
login = login.login
}
this.login = login
this.path = '/users/' + encodeURIComponent(login)
}
, has: 'followers following repos watched'.split(' ')
})
// Fetch data from github. JSON is parsed and keys are camelized.
//
// path: the path to the resource
// cb: callback(err, data)
function fetch(path, cb) {
request({ host: 'api.github.com', path: path }, function(err, body, response) {
// JSONP requests in the browser return the object directly
var data = body
// Requests in Node return json text, try to parse it
if (response && /json/i.exec(response.headers['content-type'])) {
try {
data = JSON.parse(body)
}
catch (e) {
err = e
data = null
}
}
cb(err, camelize(data))
})
}
// created_at => createdAt
function camel(s) {
return s.replace(/_(.)/g, function(_, l) { return l.toUpperCase() })
}
// camelize all keys of an object, or all objects in an array
function camelize(obj) {
if (!obj || typeof obj === 'string') return obj
if (Array.isArray(obj)) return obj.map(camelize)
if (typeof obj === 'object') {
return Object.keys(obj).reduce(function(camelizedObj, k) {
camelizedObj[camel(k)] = camelize(obj[k])
return camelizedObj
}, {})
}
return obj
}
function toTitleCase(s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
function mixin(a, b) {
for (var k in b) {
if (b.hasOwnProperty(k)) a[k] = b[k]
}
}
// Browser Utilities //
function shim() {
shimBind()
shimES5()
shimRequest()
}
function shimBind() {
// bind from Prototype
if (!Function.prototype.bind) {
(function(){
function update(array, args) {
var arrayLength = array.length, length = args.length
while (length--) array[arrayLength + length] = args[length]
return array
}
function merge(array, args) {
array = __slice.call(array, 0)
return update(array, args)
}
Function.prototype.bind = function(context) {
if (arguments.length < 2 && typeof arguments[0] === 'undefined') return this
var __method = this, args = __slice.call(arguments, 1)
return function() {
var a = merge(args, arguments)
return __method.apply(context, a)
}
}
}())
}
}
// a few functions from Kris Kowal's es5-shim
// https://github.com/kriskowal/es5-shim
function shimES5() {
var has = Object.prototype.hasOwnProperty
// ES5 15.2.3.6
if (!Object.defineProperty || ie === 8) { // ie8
Object.defineProperty = function(object, property, descriptor) {
if (typeof descriptor == "object" && object.__defineGetter__) {
if (has.call(descriptor, "value")) {
if (!object.__lookupGetter__(property) && !object.__lookupSetter__(property)) {
// data property defined and no pre-existing accessors
object[property] = descriptor.value
}
if (has.call(descriptor, "get") || has.call(descriptor, "set")) {
// descriptor has a value property but accessor already exists
throw new TypeError("Object doesn't support this action")
}
}
// fail silently if "writable", "enumerable", or "configurable"
// are requested but not supported
else if (typeof descriptor.get == "function") {
object.__defineGetter__(property, descriptor.get)
}
if (typeof descriptor.set == "function") {
object.__defineSetter__(property, descriptor.set)
}
}
return object
}
}
// ES5 15.2.3.14
// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
if (!Object.keys) { // ie8
(function() {
var hasDontEnumBug = true,
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length
for (var key in {"toString": null}) {
hasDontEnumBug = false
}
Object.keys = function (object) {
if (
typeof object !== "object" && typeof object !== "function"
|| object === null
)
throw new TypeError("Object.keys called on a non-object")
var keys = []
for (var name in object) {
if (has.call(object, name)) {
keys.push(name)
}
}
if (hasDontEnumBug) {
for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
var dontEnum = dontEnums[i]
if (has.call(object, dontEnum)) {
keys.push(dontEnum)
}
}
}
return keys
}
}())
} // Object.keys
//
// Array
// =====
//
// ES5 15.4.3.2
if (!Array.isArray) {
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) == "[object Array]"
}
}
// ES5 15.4.4.18
if (!Array.prototype.forEach) { // ie8
Array.prototype.forEach = function(block, thisObject) {
var len = this.length >>> 0
for (var i = 0; i < len; i++) {
if (i in this) {
block.call(thisObject, this[i], i, this)
}
}
}
}
// ES5 15.4.4.19
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
if (!Array.prototype.map) { // ie8
Array.prototype.map = function(fun /*, thisp*/) {
var len = this.length >>> 0
if (typeof fun != "function") {
throw new TypeError()
}
var res = new Array(len)
var thisp = arguments[1]
for (var i = 0; i < len; i++) {
if (i in this) {
res[i] = fun.call(thisp, this[i], i, this)
}
}
return res
}
}
// ES5 15.4.4.20
if (!Array.prototype.filter) { // ie8
Array.prototype.filter = function (block /*, thisp */) {
var values = []
, thisp = arguments[1]
for (var i = 0; i < this.length; i++) {
if (block.call(thisp, this[i])) {
values.push(this[i])
}
}
return values
}
}
// ES5 15.4.4.21
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
if (!Array.prototype.reduce) { // ie8
Array.prototype.reduce = function(fun /*, initial*/) {
var len = this.length >>> 0
if (typeof fun != "function") {
throw new TypeError()
}
// no value to return if no initial value and an empty array
if (len == 0 && arguments.length == 1) {
throw new TypeError()
}
var i = 0
if (arguments.length >= 2) {
var rv = arguments[1]
} else {
do {
if (i in this) {
rv = this[i++]
break
}
// if array contains no values, no initial value to return
if (++i >= len) {
throw new TypeError()
}
} while (true)
}
for (; i < len; i++) {
if (i in this) {
rv = fun.call(null, rv, this[i], i, this)
}
}
return rv
}
} // Array.prototype.reduce
} // function shimES5()
// jsonp request, quacks like node's http.request
function shimRequest() {
var load, _jsonpCounter = 1
// request is declared earlier
request = function(options, cb) {
var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++
, url = 'https://' + options.host + options.path + '?callback=GITR.' + jsonpCallbackName
GITR[jsonpCallbackName] = function(response) {
if (response.meta.status >= 200 && response.meta.status < 300) {
cb(null, response.data)
}
else {
var err = new Error('http error')
err.statusCode = response.meta.status
err.response = response
cb(err)
}
setTimeout(function() { delete GITR[jsonpCallbackName] }, 0)
}
load(url)
}
// bootstrap loader from LABjs (load is declared earlier)
load = function(url) {
var oDOC = document
, head = oDOC.head || oDOC.getElementsByTagName("head")
// loading code borrowed directly from LABjs itself
// (now removes script elem when done and nullifies its reference --sjs)
setTimeout(function () {
if ("item" in head) { // check if ref is still a live node list
if (!head[0]) { // append_to node not yet ready
setTimeout(arguments.callee, 25)
return
}
head = head[0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
}
var scriptElem = oDOC.createElement("script")
, scriptdone = false
scriptElem.onload = scriptElem.onreadystatechange = function () {
if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) {
return false
}
scriptElem.onload = scriptElem.onreadystatechange = null
scriptElem.parentNode.removeChild(scriptElem)
scriptElem = null
scriptdone = true
}
scriptElem.src = url
head.insertBefore(scriptElem, head.firstChild)
}, 0) // setTimeout
// required: shim for FF <= 3.5 not having document.readyState
if (oDOC.readyState == null && oDOC.addEventListener) {
oDOC.readyState = "loading"
oDOC.addEventListener("DOMContentLoaded", function handler() {
oDOC.removeEventListener("DOMContentLoaded", handler, false)
oDOC.readyState = "complete"
}, false)
}
} // function load(url)
} // function shimRequest()
}())