diff --git a/proj/batteries/index.html b/proj/batteries/index.html
index 481ac9b..738f3a1 100644
--- a/proj/batteries/index.html
+++ b/proj/batteries/index.html
@@ -1,19 +1,184 @@
-
-
batteries for node
-
-
- A slightly higher-level library for node
- github project
-
+ #info { text-align: center
+ ; margin: 0 auto
+ ; padding: 1em
+ ; border: solid 1px #ccc
+ ; width: 90%
+ ; max-width: 950px
+ ; background-color: #fff
+ ; border-radius: 20px
+ ; -webkit-border-radius: 20px
+ ; -moz-border-radius: 20px
+ }
+
+ h4 { margin: 0.5em 0 0.7em }
+
+ #info > div { text-align: center
+ ; font-size: 1.3em
+ ; width: 32%
+ ; max-width: 400px
+ ; float: left
+ ; padding: 0.5em 0.2em
+ ; border-left: dashed 1px #aaa
+ }
+
+ #info > div:first-child { border-left: none }
+
+ #info div:last-child { clear: both
+ ; float: none
+ ; border: none
+ ; height: 0
+ ; width: 0
+ ; padding: 0
+ }
+
+
+
+
+
+← samhuri.net
+← projects
+
+batteries
+useful stuff for node
+
+
+ watchers |
+ — |
+ forks |
+
+
+
+
+
branches
+

+
+
+
languages
+

+
+
+
contributors
+

+
+
+
\ No newline at end of file
diff --git a/proj/forkme.png b/proj/forkme.png
new file mode 100644
index 0000000..146ef8a
Binary files /dev/null and b/proj/forkme.png differ
diff --git a/proj/gh.png b/proj/gh.png
new file mode 100644
index 0000000..f1a9466
Binary files /dev/null and b/proj/gh.png differ
diff --git a/proj/gitter.js b/proj/gitter.js
new file mode 100644
index 0000000..d3328c8
--- /dev/null
+++ b/proj/gitter.js
@@ -0,0 +1,425 @@
+/// gitter
+/// http://github.com/samsonjs/gitter
+/// @_sjs
+///
+/// Copyright 2010 Sami Samhuri
+/// MIT License
+
+// TODO:
+// - authentication and write APIs
+
+(function() {
+ var global = this
+ , isBrowser = 'document' in global
+ // when running in the browser request is set later
+ , request = isBrowser ? null : require('request')
+ , Blob, Branch, Commit, Raw, Repo, Tree, User
+ , api
+
+ api = {
+ blob: function(repo, sha, path, cb) {
+ return new Blob(repo, sha, path, cb)
+ },
+ branch: function(repo, branch, cb) {
+ return new Branch(repo, branch, cb)
+ },
+ commits: function(repo, branch, cb) {
+ return new Branch(repo, branch).getCommits(cb)
+ },
+ commit: function(repo, sha, cb) {
+ return new Commit(repo, sha, cb)
+ },
+ raw: function(repo, sha, cb) {
+ return new Raw(repo, sha, cb)
+ },
+ repo: function(repo, cb) {
+ return new Repo(repo, cb)
+ },
+ branches: function(repo, cb) {
+ return new Repo(repo).getBranches(cb)
+ },
+ collaborators: function(repo, cb) {
+ return new Repo(repo).getCollaborators(cb)
+ },
+ contributors: function(repo, cb) {
+ return new Repo(repo).getContributors(cb)
+ },
+ languages: function(repo, cb) {
+ return new Repo(repo).getLanguages(cb)
+ },
+ network: function(repo, cb) {
+ return new Repo(repo).getNetwork(cb)
+ },
+ tags: function(repo, cb) {
+ return new Repo(repo).getTags(cb)
+ },
+ watchers: function(repo, cb) {
+ return new Repo(repo).getWatchers(cb)
+ },
+ tree: function(repo, sha, cb) {
+ return new Tree(repo, sha, cb)
+ },
+ blobs: function(repo, sha, cb) {
+ return new Tree(repo, sha).getBlobs(cb)
+ },
+ user: function(user, cb) {
+ return new User(user, cb)
+ },
+ followers: function(user, cb) {
+ return new User(user).getFollowers(cb)
+ },
+ following: function(user, cb) {
+ return new User(user).getFollowing(cb)
+ },
+ repos: function(user, cb) {
+ return new User(user).getRepos(cb)
+ },
+ watched: function(user, cb) {
+ return new User(user).getWatched(cb)
+ }
+ }
+ if (isBrowser) global.GITR = api
+ else module.exports = api
+
+
+ // Define resources //
+
+ Blob = createResource('blob/show/:repo/:tree/:path', {
+ has: [ ['commits', 'commits/list/:repo/:tree/:path'] ]
+ })
+ Branch = createResource('commits/show/:repo/:branch', {
+ has: [ ['commits', 'commits/list/:repo/:branch'] ]
+ })
+ Commit = createResource('commits/show/:repo/:sha')
+ Raw = createResource('blob/show/:repo/:sha')
+ Repo = createResource('repos/show/:repo', {
+ has: [ 'branches'
+ , 'collaborators'
+ , 'contributors'
+ , 'languages'
+ , 'network'
+ , 'tags'
+ , 'watchers'
+ ]
+ })
+ Tree = createResource('tree/show/:repo/:sha', {
+ has: [ ['blobs', 'blob/all/:repo/:sha']
+ , ['fullBlobs', 'blob/full/:repo/:sha']
+ , ['fullTree', 'tree/full/:repo/:sha']
+ ]
+ })
+ Tree.prototype._processData = function(data) {
+ Resource.prototype._processData.call(this, data)
+ this.blobs = this.data()
+ }
+
+ User = createResource('user/show/:user', {
+ has: [ 'followers'
+ , 'following'
+ , ['repos', 'repos/show/:user']
+ , ['watched', 'repos/watched/:user']
+ ]
+ })
+
+ // Construct a new github resource.
+ //
+ // options:
+ // - params: params for constructor (optional, inferred from route if missing)
+ // - has: list of related resources, accessors are created for each item
+ //
+ // The members of the `has` list are arrays of the form [name, route, unpack].
+ // The first member, name, is used to create an accessor (e.g. getName), and
+ // is required.
+ //
+ // Route and unpack are optional. Route specifies the endpoint for this
+ // resource and defaults to the name appended to the main resource's endpoint.
+ //
+ // Unpack is a function that extracts the desired value from the object fetched
+ // for this resource. It defaults to a function that picks out the only property
+ // from an object, or returns the entire walue if not an object or it contains
+ // more than one property.
+ //
+ // When passing only the name you may pass it directly without wrapping it in
+ // an array.
+ function createResource(route, options) {
+ if (!route) throw new Error('route is required')
+ options = options || {}
+
+ var resource = function() { Resource.apply(this, [].slice.call(arguments)) }
+ inherits(resource, Resource)
+
+ resource.prototype._route = route
+ resource.prototype._params = options.params || paramsFromRoute(route)
+
+ resource.has = function(prop, route, unpack) {
+ unpack = unpack || onlyProp
+ var dataProp = '_' + prop
+ , fn = 'get' + titleCaseFirst(prop)
+ , processData = function(d) {
+ getter(this, dataProp, function() { return camelize(unpack(d))})
+ }
+ , result = function(resource) { return this[dataProp] }
+ resource.prototype[fn] = function(cb, force) {
+ return this._fetch({ prop: dataProp
+ , route: route || this._route + '/' + prop
+ , processData: processData.bind(this)
+ , result: result.bind(this)
+ }, cb.bind(this), force)
+ }
+ return resource
+ }
+ if (options.has) options.has.forEach(function(args) {
+ resource.has.apply(resource, Array.isArray(args) ? args : [args])
+ })
+
+ return resource
+ }
+
+ // Assigns the given resource args to the new instance. Sets the path to the
+ // endpoint for main resource data.
+ //
+ // If the optional last arg is a function main data is fetched immediately,
+ // and that function is used as the callback.
+ //
+ // If the optional last arg is an object then it is set as the main resource
+ // data.
+ function Resource(/* ...args, opt: data or callback */) {
+ var args = [].slice.call(arguments)
+ , last = args[args.length - 1]
+
+ // assign params from args
+ this._params.forEach(function(param, i) {
+ this[param] = args[i]
+ }.bind(this))
+
+ // set the resource path
+ this.urlPath = this.resolve(this._route)
+
+ if (typeof last === 'function') this.fetch(last)
+ else if (typeof last === 'object') this.data(last)
+ }
+
+ // Set or get main data for this resource, or fetch
+ // a specific property from the data.
+ //
+ // When the data param is empty cached data is returned.
+ //
+ // When the data param is a string the property by that name
+ // is looked up in the cached data.
+ //
+ // Otherwise cached data is set to the data param.
+ Resource.prototype.data = function(data) {
+ if (!data) return this._data
+ if (typeof data === 'string' && typeof this._data === 'object') return this._data[data]
+
+ getter(this, '_data', function() { return data }, {configurable: true})
+ return this
+ }
+
+ // Fetch the main data for this resource.
+ //
+ // cb: callback(err, data)
+ // force: if true load data from github, bypassing the local cache
+ Resource.prototype.fetch = function(cb, force) {
+ return this._fetch({ prop: '_data'
+ , route: this.urlPath
+ , processData: this._processData.bind(this)
+ , result: function(resource) { return resource }
+ }, cb.bind(this), force)
+ }
+
+ // 'repos/show/:user/:repo/branches' -> 'repos/show/samsonjs/gitter
+ Resource.prototype.resolve = function(route) { // based on crock's supplant
+ if (route.indexOf(':') < 0) return route
+ return route.replace(/:(\w+)\b/g, function (s, prop) {
+ var val = this[prop]
+ if (typeof val !== 'string' && typeof val !== 'number')
+ throw new Error('no suitable property named "' + prop + '" (found ' + val + ')')
+ return val
+ }.bind(this))
+ }
+
+ // Fetch arbitrary data from github.
+ //
+ // options:
+ // - prop: name of data cache property
+ // - route: route to github endpoint (can contain resource params)
+ // - processData: function that processes fetched data
+ // - result: function to obtain the result passed to the callback
+ // cb: callback(err, data)
+ // force: if true load data from github, bypassing the local cache
+ Resource.prototype._fetch = function(options, cb, force) {
+ if (!force && this[options.prop]) {
+ cb(null, options.result(this))
+ return this
+ }
+
+ // Interpolate resource params
+ var path = this.resolve(options.route)
+
+ // Make the request
+ return this._get(path, function(err, data) {
+ if (err) {
+ cb(err)
+ return
+ }
+ options.processData(data)
+ cb(null, options.result(this))
+ }.bind(this))
+ }
+
+ // Fetch data from github. JSON responses are parsed.
+ //
+ // path: github endpoint
+ // cb: callback(err, data)
+ Resource.prototype._get = function(path, cb) {
+ request({uri: 'http://github.com/api/v2/json/' + path}, function(err, response, body) {
+ if (err)
+ cb(err)
+ else if (isBrowser)
+ cb(null, body) // body is an object
+ else if (response.statusCode !== 200)
+ cb(new Error('failed to fetch ' + path + ': ' + response.statusCode))
+ else if (response.headers['content-type'].match(/json/))
+ cb(null, JSON.parse(body))
+ else
+ cb(null, body)
+ })
+ return this
+ }
+
+ // Descendents of Resource can overwrite _processData and _unpack to process
+ // the main resource data differently.
+
+ Resource.prototype._processData = function(data) {
+ return this.data(camelize(this._unpack(data)))
+ }
+ Resource.prototype._unpack = onlyProp
+
+
+ // Utilities //
+
+ function camel(s) { // created_at => createdAt
+ return s.replace(/_(.)/g, function(_, l) { return l.toUpperCase() })
+ }
+ function camelize(obj) { // camelize all keys of an object, or all objects in an array
+ if (!obj || typeof obj === 'string') return obj
+ if (Array.isArray(obj)) return obj.map(camelize)
+ return Object.keys(obj).reduce(function(newObj, k) {
+ newObj[camel(k)] = obj[k]
+ return newObj
+ }, {})
+ }
+
+ function getter(obj, prop, fn, opts) { // minor convenience
+ opts = opts || {}
+ opts.get = fn
+ Object.defineProperty(obj, prop, opts)
+ }
+
+ // util.inherits from node
+ function inherits(ctor, superCtor) {
+ ctor.super_ = superCtor
+ ctor.prototype = Object.create(superCtor.prototype, {
+ constructor: {
+ value: ctor,
+ enumerable: false
+ }
+ })
+ }
+ // get an only property, if any
+ function onlyProp(obj) {
+ if (obj && typeof obj === 'object') {
+ var keys = Object.keys(obj)
+ if (keys.length === 1) return obj[keys[0]]
+ }
+ return obj
+ }
+
+ // 'repos/show/:user/:repo/branches' -> ['user', 'repo']
+ function paramsFromRoute(route) {
+ if (route.indexOf(':') === -1) return []
+ return route.split('/')
+ .filter(function(s) { return s.charAt(0) === ':' })
+ .map(function(s) { return s.slice(1) })
+ }
+
+ function titleCaseFirst(s) { return s.charAt(0).toUpperCase() + s.slice(1) }
+
+
+ // Browser Utilities //
+
+ if (isBrowser) (function() {
+ var update, merge, load, _jsonpCounter = 1
+ request = function(options, cb) { // jsonp request, quacks like mikeal's request module
+ var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++
+ , url = options.uri + '?callback=GITR.' + jsonpCallbackName
+ GITR[jsonpCallbackName] = function(obj) {
+ cb(null, null, obj)
+ setTimeout(function() { delete GITR[jsonpCallbackName] }, 0)
+ }
+ load(url)
+ }
+
+ // bind from Prototype (for Safari 5)
+ if (!Function.prototype.bind) {
+ update = function(array, args) {
+ var arrayLength = array.length, length = args.length
+ while (length--) array[arrayLength + length] = args[length]
+ return array
+ }
+ merge = function(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)
+ }
+ }
+ }
+ // bootstrap loader from LABjs
+ load = function(url) {
+ var oDOC = document
+ , handler
+ , 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)
+
+ // 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)
+ }
+ }
+ }())
+}())
diff --git a/proj/index.html b/proj/index.html
index bc9f17f..b47da3d 100644
--- a/proj/index.html
+++ b/proj/index.html
@@ -4,6 +4,16 @@
samhuri.net
+
+
+
← samhuri.net
+
projects
+
+
+
+
+
+ projects |
+ — |
+ followers |
+
+
+ watching projects
+ | — |
+ following people |
+
+
diff --git a/proj/spinner.gif b/proj/spinner.gif
new file mode 100644
index 0000000..06dbc2b
Binary files /dev/null and b/proj/spinner.gif differ
diff --git a/style.css b/style.css
index 71b281a..a4c527d 100644
--- a/style.css
+++ b/style.css
@@ -4,13 +4,20 @@ body { background-color: #f7f7f7
}
h1 { text-align: center
- ; vertical-align: center
- ; font-size: 4em
- ; font-weight: normal
- ; height: 100%
- ; margin: 0.6em 0 0.4em
- ; padding: 0
- }
+ ; vertical-align: center
+ ; font-size: 4em
+ ; font-weight: normal
+ ; margin: 0.6em 0 0.4em
+ ; padding: 0
+ }
+
+h2 { text-align: center
+ ; vertical-align: center
+ ; font-size: 2em
+ ; font-weight: normal
+ ; margin: 0.2em 0 1em
+ ; padding: 0
+ }
ul { text-align: center
; margin: 0 auto
@@ -24,6 +31,11 @@ ul { text-align: center
; -moz-border-radius: 20px
}
+a { color: #22a
+ ; text-decoration: none
+ ; border-bottom: dashed 1px #22a
+ }
+
li { display: inline
; font-size: 2em
; margin: 0
@@ -32,13 +44,12 @@ li { display: inline
li:after { content: ' •' }
li:last-child:after { content: '' }
-li a { color: #22a
- ; text-decoration: none
- ; border-bottom: dashed 1px #22a
- ; padding: 5px
+li a { padding: 5px
; text-shadow: #999 5px 5px 5px
}
li a:visited { color: #227 }
+
+li a:hover,
li a:active { color: #000
; text-shadow: #aa7 5px 5px 5px
; border-bottom: dashed 1px #000
@@ -57,6 +68,18 @@ p#promoteJS { margin-top: 3em
; text-align: center
}
+td { font-size: 1.5em
+ ; line-height: 1.6em
+ }
+
+td:nth-child(2) { padding: 0 10px }
+
+
+.highlight { background-image: -webkit-gradient(radial, 50% 50%, 5, 50% 50%, 15, from(#ffa), to(#f7f7f7))
+ ; background-image: -moz-radial-gradient(center 45deg, circle contain, #ffa 0%, #f7f7f7 100%)
+ ; font-size: 1.2em
+ }
+
/* iPad (landscape) */