Source / src /

$ = (id) -> document.getElementById id

addEvent = (el, type, handler) ->
  if el.addEventListener
    el.addEventListener type, handler, no
  else if el.attachEvent
    el.attachEvent "on#{type}", handler

decode = (text) ->
  try Hashify.decode text
  catch error
    throw error unless error instanceof URIError
    '# ' + error

parseJSON = (data) ->
  return null unless typeof data is 'string' and data

  data = data.replace /^\s+|\s+$/g, ''

  throw new SyntaxError 'Invalid JSON' unless /^[\],:{}\s]*$/.test data
    .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
    .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/g, ']')
    .replace(/(?:^|:|,)(?:\s*\[)+/g, '')

  if window.JSON?.parse then JSON.parse data
  else do new Function "return #{data}"

corsNotSupported = ->
  publish 'textchange', '''
    # I'm sorry, Dave

    Your browser appears not to support
    [cross-origin resource sharing][1].


sendRequest = (action, params, success) ->
  request = new XMLHttpRequest()
  try 'GET',
      "{action}?login=davidchambers&" +
  catch error
    if error.code is 1012 or /^Access is denied\.\r\n$/.test error.message
      do corsNotSupported
    throw error

  request.onreadystatechange = ->
    return unless request.readyState is 4 and request.status is 200
    json = parseJSON request.responseText
    if json.status_code is 200 then success
    else publish 'request:error', json
  catch error
    throw error unless error.message is 'Security violation' # Opera
    do corsNotSupported

subscriptions = {}

subscribe = (event, action) ->
  (subscriptions[event] or= []).push action

stack = []
publish = (event, args...) ->
  return if event in stack # prevent recursion
  stack.push event
  # A "pre:" hook may modify the published arguments before they're
  # passed to the remaining event listeners (by returning an array),
  # or it may cancel the event (by returning a falsy value).
  cancelled = no
  for s in subscriptions["pre:#{event}"] or []
    unless args = s args...
      cancelled = yes
  unless cancelled
    s args... for s in subscriptions[event] or []
    s args... for s in subscriptions["post:#{event}"] or []

Hashify.utils = {$, addEvent, decode, publish, sendRequest, subscribe}