Alice

 view release on metacpan or  search on metacpan

share/static/alice.js  view on Meta::CPAN

    isElement:     isElement,
    isArray:       isArray,
    isHash:        isHash,
    isFunction:    isFunction,
    isString:      isString,
    isNumber:      isNumber,
    isDate:        isDate,
    isUndefined:   isUndefined
  });
})();
Object.extend(Function.prototype, (function() {
  var slice = Array.prototype.slice;

  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 argumentNames() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  }

  function bind(context) {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = slice.call(arguments, 1);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(context, a);
    }
  }

  function bindAsEventListener(context) {
    var __method = this, args = slice.call(arguments, 1);
    return function(event) {
      var a = update([event || window.event], args);
      return __method.apply(context, a);
    }
  }

  function curry() {
    if (!arguments.length) return this;
    var __method = this, args = slice.call(arguments, 0);
    return function() {
      var a = merge(args, arguments);
      return __method.apply(this, a);
    }
  }

  function delay(timeout) {
    var __method = this, args = slice.call(arguments, 1);
    timeout = timeout * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  }

  function defer() {
    var args = update([0.01], arguments);
    return this.delay.apply(this, args);
  }

  function wrap(wrapper) {
    var __method = this;
    return function() {
      var a = update([__method.bind(this)], arguments);
      return wrapper.apply(this, a);
    }
  }

  function methodize() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      var a = update([this], arguments);
      return __method.apply(null, a);
    };
  }

  return {
    argumentNames:       argumentNames,
    bind:                bind,
    bindAsEventListener: bindAsEventListener,
    curry:               curry,
    delay:               delay,
    defer:               defer,
    wrap:                wrap,
    methodize:           methodize
  }
})());



(function(proto) {


  function toISOString() {
    return this.getUTCFullYear() + '-' +
      (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
      this.getUTCDate().toPaddedString(2) + 'T' +
      this.getUTCHours().toPaddedString(2) + ':' +
      this.getUTCMinutes().toPaddedString(2) + ':' +
      this.getUTCSeconds().toPaddedString(2) + 'Z';
  }


  function toJSON() {
    return this.toISOString();
  }

  if (!proto.toISOString) proto.toISOString = toISOString;
  if (!proto.toJSON) proto.toJSON = toJSON;

share/static/alice.js  view on Meta::CPAN

});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});


function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });

share/static/alice.js  view on Meta::CPAN

    },

    handleEvent: function(event) {
      var element = Event.findElement(event, this.selector);
      if (element) this.callback.call(this.element, event, element);
    }
  });

  function on(element, eventName, selector, callback) {
    element = $(element);
    if (Object.isFunction(selector) && Object.isUndefined(callback)) {
      callback = selector, selector = null;
    }

    return new Event.Handler(element, eventName, selector, callback).start();
  }

  Object.extend(Event, Event.Methods);

  Object.extend(Event, {
    fire:          fire,
    observe:       observe,
    stopObserving: stopObserving,
    on:            on
  });

  Element.addMethods({
    fire:          fire,

    observe:       observe,

    stopObserving: stopObserving,

    on:            on
  });

  Object.extend(document, {
    fire:          fire.methodize(),

    observe:       observe.methodize(),

    stopObserving: stopObserving.methodize(),

    on:            on.methodize(),

    loaded:        false
  });

  if (window.Event) Object.extend(window.Event, Event);
  else window.Event = Event;
})();

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearTimeout(timer);
    document.loaded = true;
    document.fire('dom:loaded');
  }

  function checkReadyState() {
    if (document.readyState === 'complete') {
      document.stopObserving('readystatechange', checkReadyState);
      fireContentLoadedEvent();
    }
  }

  function pollDoScroll() {
    try { document.documentElement.doScroll('left'); }
    catch(e) {
      timer = pollDoScroll.defer();
      return;
    }
    fireContentLoadedEvent();
  }

  if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
  } else {
    document.observe('readystatechange', checkReadyState);
    if (window == top)
      timer = pollDoScroll.defer();
  }

  Event.observe(window, 'load', fireContentLoadedEvent);
})();


Element.addMethods();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

share/static/alice.js  view on Meta::CPAN

    });

    if(affected.length>0)
      drop = Droppables.findDeepestChild(affected);

    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
    if (drop) {
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));

      if (drop != this.last_active) Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) {
        this.last_active.onDrop(element, this.last_active.element, event);
        return true;
      }
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
};

var Draggables = {
  drags: [],
  observers: [],

  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);

      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },

  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },

  activate: function(draggable) {
    if(draggable.options.delay) {
      this._timeout = setTimeout(function() {
        Draggables._timeout = null;
        window.focus();
        Draggables.activeDraggable = draggable;
      }.bind(this), draggable.options.delay);
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },

  deactivate: function() {
    this.activeDraggable = null;
  },

  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;

    this.activeDraggable.updateDrag(event, pointer);
  },

  endDrag: function(event) {
    if(this._timeout) {
      clearTimeout(this._timeout);
      this._timeout = null;
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },

  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },

  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },

  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },

  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },

  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
};

/*--------------------------------------------------------------------------*/

var Draggable = Class.create({
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });

share/static/alice.js  view on Meta::CPAN

  }

  function observeButtonClick(element, handler) {
    element.on('click', function(event) {
      handler(this.editor);
      event.stop();
    }.bind(this));
  }

  function buttonStateHandler(name, options) {
    if (options.query)
      return options.query;
    else if (options.get('query'))
      return options.get('query');
    else
      return function(editor) { return editor.queryCommandState(name); };
  }

  function observeStateChanges(element, name, handler) {
    var previousState;
    this.editor.on("selection:change", function(event) {
      var state = handler(this.editor);
      if (state != previousState) {
        previousState = state;
        this.updateButtonState(element, name, state);
      }
    }.bind(this));
  }

  function updateButtonState(element, name, state) {
    if (state)
      element.addClassName('selected');
    else
      element.removeClassName('selected');
  }

  return {
    initialize:           initialize,
    createToolbarElement: createToolbarElement,
    addButtonSet:         addButtonSet,
    addButton:            addButton,
    createButtonElement:  createButtonElement,
    buttonHandler:        buttonHandler,
    observeButtonClick:   observeButtonClick,
    buttonStateHandler:   buttonStateHandler,
    observeStateChanges:  observeStateChanges,
    updateButtonState:    updateButtonState
  };
})());

WysiHat.Toolbar.ButtonSets = {};

WysiHat.Toolbar.ButtonSets.Basic = $A([
  { label: "Bold" },
  { label: "Underline" },
  { label: "Italic" }
]);
/*	SWFObject v2.2 <http://code.google.com/p/swfobject/>
	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,...

(function() {

  if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;

  var logger;
  if (window.WEB_SOCKET_LOGGER) {
    logger = WEB_SOCKET_LOGGER;
  } else if (window.console && window.console.log && window.console.error) {
    logger = window.console;
  } else {
    logger = {log: function(){ }, error: function(){ }};
  }

  if (swfobject.getFlashPlayerVersion().major < 10) {
    logger.error("Flash Player >= 10.0.0 is required.");
    return;
  }
  if (location.protocol == "file:") {
    logger.error(
      "WARNING: web-socket-js doesn't work in file:///... URL " +
      "unless you set Flash Security Settings properly. " +
      "Open the page via Web server i.e. http://...");
  }

  /**
   * This class represents a faux web socket.
   * @param {string} url
   * @param {array or string} protocols
   * @param {string} proxyHost
   * @param {int} proxyPort
   * @param {string} headers
   */
  WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
    var self = this;
    self.__id = WebSocket.__nextId++;
    WebSocket.__instances[self.__id] = self;
    self.readyState = WebSocket.CONNECTING;
    self.bufferedAmount = 0;
    self.__events = {};
    if (!protocols) {
      protocols = [];
    } else if (typeof protocols == "string") {
      protocols = [protocols];
    }
    self.__createTask = setTimeout(function() {
      WebSocket.__addTask(function() {
        self.__createTask = null;
        WebSocket.__flash.create(
            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
      });
    }, 0);
  };

  /**
   * Send data to the web socket.
   * @param {string} data  The data to send to the socket.
   * @return {boolean}  True for success, false for failure.
   */
  WebSocket.prototype.send = function(data) {
    if (this.readyState == WebSocket.CONNECTING) {
      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
    }
    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
    if (result < 0) { // success
      return true;
    } else {
      this.bufferedAmount += result;
      return false;
    }
  };

  /**
   * Close this web socket gracefully.
   */
  WebSocket.prototype.close = function() {
    if (this.__createTask) {
        clearTimeout(this.__createTask);
        this.__createTask = null;
        this.readyState = WebSocket.CLOSED;
        return;
    }
    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
      return;
    }
    this.readyState = WebSocket.CLOSING;
    WebSocket.__flash.close(this.__id);
  };

  /**
   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
   *
   * @param {string} type
   * @param {function} listener
   * @param {boolean} useCapture
   * @return void
   */
  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
    if (!(type in this.__events)) {
      this.__events[type] = [];
    }
    this.__events[type].push(listener);
  };

  /**
   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
   *
   * @param {string} type
   * @param {function} listener
   * @param {boolean} useCapture
   * @return void
   */
  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
    if (!(type in this.__events)) return;
    var events = this.__events[type];
    for (var i = events.length - 1; i >= 0; --i) {
      if (events[i] === listener) {
        events.splice(i, 1);
        break;
      }
    }
  };

  /**
   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
   *
   * @param {Event} event
   * @return void
   */
  WebSocket.prototype.dispatchEvent = function(event) {
    var events = this.__events[event.type] || [];
    for (var i = 0; i < events.length; ++i) {
      events[i](event);
    }
    var handler = this["on" + event.type];
    if (handler) handler.apply(this, [event]);
  };

share/static/alice.js  view on Meta::CPAN

   */
  WebSocket.__initialize = function() {
    if (WebSocket.__flash) return;

    if (WebSocket.__swfLocation) {
      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
    }
    if (!window.WEB_SOCKET_SWF_LOCATION) {
      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
      return;
    }
    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
      var swfHost = RegExp.$1;
      if (location.host != swfHost) {
        logger.error(
            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
            "('" + location.host + "' != '" + swfHost + "'). " +
            "See also 'How to host HTML file and SWF file in different domains' section " +
            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
      }
    }
    var container = document.createElement("div");
    container.id = "webSocketContainer";
    container.style.position = "absolute";
    if (WebSocket.__isFlashLite()) {
      container.style.left = "0px";
      container.style.top = "0px";
    } else {
      container.style.left = "-100px";
      container.style.top = "-100px";
    }
    var holder = document.createElement("div");
    holder.id = "webSocketFlash";
    container.appendChild(holder);
    document.body.appendChild(container);
    swfobject.embedSWF(
      WEB_SOCKET_SWF_LOCATION,
      "webSocketFlash",
      "1" /* width */,
      "1" /* height */,
      "10.0.0" /* SWF version */,
      null,
      null,
      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
      null,
      function(e) {
        if (!e.success) {
          logger.error("[WebSocket] swfobject.embedSWF failed");
        }
      });
  };

  /**
   * Called by Flash to notify JS that it's fully loaded and ready
   * for communication.
   */
  WebSocket.__onFlashInitialized = function() {
    setTimeout(function() {
      WebSocket.__flash = document.getElementById("webSocketFlash");
      WebSocket.__flash.setCallerUrl(location.href);
      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
        WebSocket.__tasks[i]();
      }
      WebSocket.__tasks = [];
    }, 0);
  };

  /**
   * Called by Flash to notify WebSockets events are fired.
   */
  WebSocket.__onFlashEvent = function() {
    setTimeout(function() {
      try {
        var events = WebSocket.__flash.receiveEvents();
        for (var i = 0; i < events.length; ++i) {
          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
        }
      } catch (e) {
        logger.error(e);
      }
    }, 0);
    return true;
  };

  WebSocket.__log = function(message) {
    logger.log(decodeURIComponent(message));
  };

  WebSocket.__error = function(message) {
    logger.error(decodeURIComponent(message));
  };

  WebSocket.__addTask = function(task) {
    if (WebSocket.__flash) {
      task();
    } else {
      WebSocket.__tasks.push(task);
    }
  };

  /**
   * Test if the browser is running flash lite.
   * @return {boolean} True if flash lite is running, false otherwise.
   */
  WebSocket.__isFlashLite = function() {
    if (!window.navigator || !window.navigator.mimeTypes) {
      return false;
    }
    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
      return false;
    }
    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
  };

  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
    if (window.addEventListener) {
      window.addEventListener("load", function(){
        WebSocket.__initialize();
      }, false);
    } else {
      window.attachEvent("onload", function(){
        WebSocket.__initialize();
      });
    }
  }

})();

var Alice = { };

Object.extend(Alice, {

share/static/alice.js  view on Meta::CPAN

    node.cleanWhitespace();
  },

  epochToLocal: function(epoch, format) {
    var date = new Date(parseInt(epoch) * 1000);
    if (!date) return epoch;

    var hours = date.getHours();

    if (format == "12") {
      var ap;
      if (hours >= 12) {
        if (hours > 12) hours -= 12;
        ap = "p";
      } else {
        ap = "a"
      }
      return sprintf("%d:%02d%s", hours, date.getMinutes(), ap);
    }

    return sprintf("%02d:%02d", hours, date.getMinutes());
  },

  makeLinksClickable: function(elem) {
    var children = elem.childNodes;
    var length = children.length;

    for (var i=0; i < length; i++) {
      var node = children[i];
      if (node.nodeName != "#text") {
        Alice.makeLinksClickable(node);
      }
      else if (node.nodeValue.match(Alice.RE.url)) {
        var span = new Element("SPAN");
        span.innerHTML = node.nodeValue.escapeHTML().replace(
          Alice.RE.url, '<a href="$1" target="_blank" rel="noreferrer">$1</a>');
        node.parentNode.replaceChild(span, node);
      }
    }
  },

  growlNotify: function(message) {
    if (window.fluid) {
      window.fluid.showGrowlNotification({
        title: message.subject,
        description: message.body,
        priority: 1,
        sticky: false,
        identifier: message.msgid
      });
    }
    else if (window.webkitNotifications) {
      if (window.webkitNotifications.checkPermission() == 0) {
        var popup = window.webkitNotifications.createNotification(
          "http://static.usealice.org/image/alice.png",
          message.subject,
          message.body
        );

        popup.ondisplay = function() {
          setTimeout(function () {popup.cancel();}, 5000);
        };

        popup.show();
      }
    }
  },

  isSpecialKey: function(keyCode) {
    var special_keys = [
			16,27,9,32,13,8,145,20,144,19,45,36,46,35,33,34,37,38,39,
			40,17,18,91,112,113,114,115,116,117,118,119,120,121,122,123,
      224
		];
		return special_keys.indexOf(keyCode) > -1;
  },

  playAudio: function(image, audio) {
    image.src = '/static/image/pause.png';
    if (! audio) {
      var url = image.nextSibling.href;
      audio = new Audio(url);
      audio.addEventListener('ended', function () {
        image.src = '/static/image/play.png';
        image.onclick = function () { Alice.playAudio(image, audio) };
      });
    }
    audio.play();
    image.onclick = function() {
      audio.pause();
      this.src = '/static/image/play.png';
      this.onclick = function () { Alice.playAudio(this, audio) };
    };
  },

  joinChannel: function() {
    var network = $('join_network').value;
    var channel = $('join_channel').value;
    if (!network || !channel) {
      alert("Must select a channel and network!");
      return;
    }
    var win = alice.activeWindow();
    alice.connection.sendMessage({
      source: win.id,
      msg: "/join -"+network+" "+channel
    });
    alice.input.disabled = false;
    $('join').remove();
  },

  tabsets: {
    addSet: function () {
			var name = prompt("Please enter a name for this tab set.");
      if (name && !Alice.tabsets.hasTabset(name)) {
        Alice.tabsets.clearActive();
        $('sets').insert('<li class="active">'+name.escapeHTML()+'</li>');
        var list = $('empty_tabset').clone(true).addClassName('active').show();
        list.id = null;
        $('tabset_data').insert(list);
      }

share/static/alice.js  view on Meta::CPAN

    this.tabs = $('tabs');
    this.topic = $('topic');
    this.nicklist = $('nicklist');
    this.overlayVisible = false;
    this.lastnotify = 0;
    this.topic_height = "14px";
    this.beep = new Audio("/static/beep.mp3");

    this.oembeds = [];
    this.jsonp_callbacks = {};

    this.connection = window.WebSocket && !window.location.search.match(/&?stream=xhr/) ?
      new Alice.Connection.WebSocket(this)
      : new Alice.Connection.XHR(this);

    this.tabs_width = $('tabs_container').getWidth();
    this.tabs_layout = this.tabs.getLayout();

    this.base_filters = this.baseFilters();
    this.message_filters = [];

    this.supportsTouch = 'createTouch' in document;

    this.isPhone = window.navigator.userAgent.match(/(android|iphone|wosbrowser)/i) ? true : false;
    this.isMobile = this.isPhone || Prototype.Browser.MobileSafari;
    this.loadDelay = this.isMobile ? 3000 : 1000;
    if (window.navigator.standalone || window.navigator.userAgent.match(/Fluid/)) this.loadDelay = 0;

    this.keyboard = new Alice.Keyboard(this);

    this.input = new Alice.Input(this, "msg");
    this.submit = $("submit");

    this.submit.observe("click", function (e) {
        this.input.send(); e.stop()}.bind(this));

    this.tabs.observe("webkitTransitionEnd", this.shiftEnd.bind(this));
    this.tabs.observe("transitionend", this.shiftEnd.bind(this));

    this.makeSortable();
    this.setupTopic();
    this.setupNicklist();
    this.setupMenus();
  },

  getBacklog: function (win, max, limit) {
    this.connection.requestChunk(win.id, limit, max);
  },

  fetchOembeds: function(cb) {
    var req = new XMLHttpRequest();
    req.open("GET", "https://noembed.com/providers");
    req.onreadystatechange = function(){
      if (req.readyState == 4) {
        try {
          var providers = req.responseText.evalJSON();
          this.oembeds = providers.inject([], function(acc, site){
            return acc.concat(site.patterns.map(function(pat){return new RegExp(pat)}));
          });
        } catch (e) {}
        setTimeout(this.fetchOembeds.bind(this), 1000 * 60 * 5);
        if (cb) cb();
      }
    }.bind(this);
    req.send();
  },

  embed: function(a, win) {
    var params = {
      url: a.href,
      maxheight: 300,
    };
    var req = new XMLHttpRequest();
    req.open("GET", "https://www.noembed.com/embed?" + Object.toQueryString(params));
    req.onreadystatechange = function(){
      if (req.readyState == 4) {
        var data = req.responseText.evalJSON();
        this.embedContent(a, win, data);
      }
    }.bind(this);
    req.send();
  },

  embedContent: function(a, win, data) {
    if (!data || !data.html) return;

    var html = data.html;
    var elem = new Element("DIV", {"class": "oembed"});

    if (data.provider_name == "Twitter") {
      var position = win.captureScrollPosition();
      elem.setStyle({display: "block"});
      elem.update(html);
      a.replace(elem);
      win.scrollToPosition(position);
      Alice.makeLinksClickable(elem);
      return;
    }

    var position = win.captureScrollPosition();
    a.update(data.title);
    a.insert({
      after: '<sup class="external"><a target="_blank" href="'+data.url+'">'
             +data.provider_name+'</a></sup>'
    });
    a.up("div.msg").insert(elem);
    win.scrollToPosition(position);

    a.observe('click', function(e) {
      e.stop();
      var position = win.captureScrollPosition();
      if (elem.innerHTML) {
        elem.innerHTML = "";
        elem.style.display = "none";
        return;
      }
      elem.style.display = "block";
      elem.innerHTML = html;
      Alice.makeLinksClickable(elem);
      var images = elem.select("img");
      if (images.length) {

share/static/alice.js  view on Meta::CPAN

          if (!win.active) win.focus();
          return true;
        }
      }
    }
    return false;
  },

  tabShift: function() {
    return this.tabs_layout.get('left');
  },

  tabsWidth: function() {
    return this.tabs_width;
  },

  freeze: function() {
    $('windows').setStyle({
      width: document.viewport.getWidth()+"px",
      right: "auto",
    });
  },

  thaw: function() {
    $('windows').setStyle({
      width: "auto",
      right: "0px",
    });
  },

  shiftTabs: function(shift) {
    var current = this.tabShift();

    var left = current + shift;
    var time = Math.min(Math.max(0.1, Math.abs(shift) / 100), 0.5);

    this.tabs.style.webkitTransitionDuration = time+"s";
    this.tabs.setStyle({left: left+"px"});
    this.tabs_layout = this.tabs.getLayout();
  },

  shiftEnd: function(e) {
    this.tabs_layout = this.tabs.getLayout();
    this.updateOverflowMenus();
  },

  makeSortable: function() {
    Sortable.create('tabs', {
      overlap: 'horizontal',
      constraint: 'horizontal',
      format: /(.+)/,
      only: ["info_tab", "channel_tab", "privmsg_tab"],
      onUpdate: function (res) {
        var tabs = res.childElements();
        var order = tabs.collect(function(t){
          var m = t.id.match(/([^_]+)_tab/);
          if (m) return m[1]
        });
        if (order.length) this.connection.sendTabOrder(order);

        setTimeout(function(){
          this.windows().invoke("updateTabLayout");
          this.activeWindow().shiftTab();
        }.bind(this), 100);
      }.bind(this)
    });
  },

  addMissed: function() {
    if (!window.fluid) return;
    window.fluid.dockBadge ? window.fluid.dockBadge++ :
                             window.fluid.dockBadge = 1;
  },

  clearMissed: function() {
    if (!window.fluid) return;
    window.fluid.dockBadge = "";
  },

  ready: function() {
    this.freeze();
    setTimeout(this.updateOverflowMenus.bind(this), 1000);

    this.fetchOembeds(function() {
      this.connection.connect(function() {
        this.focusHash() || this.activeWindow().focus();
      }.bind(this));

    }.bind(this));
  },

  log: function () {
    var win = this.activeWindow();
    for (var i=0; i < arguments.length; i++) {
      if (window.console && window.console.log) {
        console.log(arguments[i]);
      }
      if (this.options.debug == "true") {
        if (win) {
          win.addMessage({
            html: '<li class="message monospace"><div class="left">console</div><div class="msg">'+arguments[i].toString()+'</div></li>'
          });
        }
      }
    }
  },

  msgid: function() {
    var ids = this.windows().map(function(w){return w.msgid});
    return Math.max.apply(Math, ids);
  },

  setSource: function(id) {
    $('source').value = id;
  },

  showSet: function(name) {
    var ids = this.tabsets[name];
    if (ids) {
      var elem = $('tabset_menu').select('li').find(function(li) {
        return li.innerHTML.unescapeHTML() == name;
      });
      elem.up('ul').select('li').invoke('removeClassName', 'selectedset');
      elem.addClassName('selectedset');

      this.windows().each(function(win) {
        ids.indexOf(win.id) >= 0 || win.type == "privmsg" ? win.show() : win.hide();
      });

      this.selectSet(name);

      var active = this.activeWindow();

      if (!active.visible) {
        active = this.nextWindow();
      }

      if (active) active.shiftTab();
      setTimeout(this.updateOverflowMenus.bind(this), 2000);
    }
  },

  selectSet: function(name) {
    var hash = window.location.hash;
    hash = hash.replace(/^[^\/]*/, name);
    window.location.hash = hash;
    window.location = window.location.toString();
    this.selectedSet = name;
  },

  clearSet: function(elem) {
    elem.up('ul').select('li').invoke('removeClassName', 'selectedset');
    elem.addClassName('selectedset');
    this.windows().invoke("show");
    this.selectSet('');
    this.updateOverflowMenus();
    this.activeWindow().shiftTab();
  },

  currentSetContains: function(win) {
    var set = this.selectedSet;
    if (win.type == "channel" && set && this.tabsets[set]) {
      return (this.tabsets[set].indexOf(win.id) >= 0);
    }
    return true;
  },

  displayTopic: function(new_topic) {
    this.topic.update(new_topic || "no topic set");
    Alice.makeLinksClickable(this.topic);
  },

  displayNicks: function(nicks) {
    this.nicklist.innerHTML = nicks.sort(function(a, b) {
      a = a.toLowerCase();
      b = b.toLowerCase();
      if (a > b)
        return 1
      if (a < b)
        return -1
      return 0;
    }).map(function(nick) {
      return '<li><a>'+nick.escapeHTML()+'</a></li>';
    }).join("");
  },

  toggleNicklist: function() {
    var windows = $('windows');
    var win = this.activeWindow();
    var position = win.captureScrollPosition();
    if (windows.hasClassName('nicklist'))
      windows.removeClassName('nicklist');
    else
      windows.addClassName('nicklist');
    win.scrollToPosition(position);
  },

  setupNicklist: function() {
    this.nicklist.observe("click", function(e) {

share/static/alice.js  view on Meta::CPAN

          this.addMissed();
        }
      }
    ];
  },

  toggleOverlay: function () {
    this.overlayVisible = !this.overlayVisible;
    var opacity = this.overlayVisible ? 1 : 0;

    $$("li.avatar span.nick").each(function(span){
      span.style.opacity = opacity;
    });
  }

});
Alice.Connection = {
  gotoLogin: function() {
    window.location = "/login";
  },

  connect: function(cb) {
    if (this.reconnect_count > 3) {
      this.aborting = true;
      this.changeStatus("ok");

      if (this.type == "websocket") {
        this.application.activeWindow().showAlert("WebSocket connection failed, falling back...");
        this.application.connection = new Alice.Connection.XHR(this.application);
        this.application.connection.connect();
        return;
      }

      this.application.activeWindow().showAlert("Alice server is not responding (<a href='javascript:alice.connection.reconnect()'>reconnect</a>)");
      return;
    }
    this.pings = [];
    this.closeConnection();
    this.len = 0;
    this.reconnect_count++;

    this.changeStatus("loading");
    this._connect(cb);
  },

  changeStatus: function(classname) {
    $('connection_status').className = classname;
  },

  reconnect: function () {
    this.reconnecting = true;
    this.reconnect_count = 0;
    this.connect();
  },

  handleException: function(request, exception) {
    this.application.log("encountered an error with stream.");
    this.application.log(exception);
    this.connected = false;
    if (!this.aborting)
      setTimeout(this.connect.bind(this), 2000);
    else
      this.changeStatus("ok");
  },

  handleComplete: function(transport) {
    this.application.log("connection was closed cleanly.");
    this.connected = false;
    if (!this.aborting)
      setTimeout(this.connect.bind(this), 2000);
    else
      this.changeStatus("ok");
  },

  processQueue: function(data) {
    try {
      var queue = data.queue;
      var length = queue.length;
      for (var i=0; i<length; i++) {
        if (queue[i].type == "identify") {
          this.id = queue[i].id;
        }
        else if (queue[i].type == "action")
          this.application.handleAction(queue[i]);
        else if (queue[i].type == "message") {
          if (queue[i].timestamp)
            queue[i].timestamp = Alice.epochToLocal(queue[i].timestamp, this.application.options.timeformat);
          this.application.displayMessage(queue[i]);
        }
        else if (queue[i].type == "chunk") {
          this.application.displayChunk(queue[i]);
        }
      }
    }
    catch (e) {
      this.application.log(e.toString());
    }
  },

  get: function(path, callback) {
    new Ajax.Request(path, {
      method: 'get',
      on401: this.gotoLogin,
      onSuccess: callback
    });
  },

  sendTabOrder: function (windows) {
    new Ajax.Request('/tabs', {
      method: 'post',
      on401: this.gotoLogin,
      parameters: {tabs: windows}
    });
  },

  requestChunk: function (win, limit, max) {
    if (!this.connected) return;
    this.sendMessage({
      source: win,
      msg: "/chunk " + limit + " " + max,
    });
  }
};
Alice.Connection.WebSocket = Class.create(Alice.Connection, {
  initialize: function(application) {
    this.type = "websocket";
    this.application = application;
    this.connected = false;
    this.aborting = false;
    this.request = null;
    this.reconnect_count = 0;
    this.reconnecting = false;
  },

  _connect: function(cb) {
    var now = new Date();
    this.application.log("opening new websocket stream");
    this.changeStatus("ok");
    var parameters = Object.toQueryString({
      t: now.getTime() / 1000,
      tab: this.application.activeWindow().id
    });
    var protocol = (window.location.protocol.match(/^https/) ? "wss://" : "ws://");
    var url = protocol + window.location.host + "/wsstream?" + parameters;
    this.request = new WebSocket(url);
    this.request.onopen = function(){
      this.connected = true;
      setTimeout(cb, 100);
    }.bind(this);
    this.request.onmessage = this.handleUpdate.bind(this);
    this.request.onerror = this.handleException.bind(this);
    this.request.onclose = this.handleComplete.bind(this);
  },

  handleUpdate: function(e) {
    var data = e.data.evalJSON();
    this.processQueue(data);
  },

  sendMessage: function(form) {
    if (!this.connected) return false;

    var params = form;
    if (form.nodeName && form.nodeName == "FORM") {
      params = Form.serialize(form, true);
    }

    params['stream'] = this.id;
    this.request.send(Object.toJSON(params));
    return true;
  },

  closeConnection: function() {
    this.aborting = true;
    if (this.request) this.request.close();
    this.aborting = false;
  },

  closeWindow: function(win) {
    this.sendMessage({source: win.id, msg: "/close"});
  },

  handleException: function(exception) {
    this.application.log("encountered an error with stream.");
    this.application.log(exception);
    this.connected = false;
    if (!this.aborting)
      setTimeout(this.connect.bind(this), 2000);
    else
      this.changeStatus("ok");
  },

  requestWindow: function(title, windowId, message) {
    this.sendMessage({source: windowId, msg: "/create " + title});
    if (message) {
      setTimeout(function() {
        this.application.displayMessage(message)
      }.bind(this), 1000);
    }
  }

});
Alice.Connection.XHR = Class.create(Alice.Connection, {
  initialize: function(application) {
    this.type = "xhr";
    this.pings = [];
    this.pingLimit = 10;
    this.seperator = "--xalicex\n";
    this.len = 0;

    this.application = application;
    this.connected = false;
    this.aborting = false;
    this.request = null;
    this.reconnect_count = 0;
    this.reconnecting = false;
  },

  _connect: function(cb) {
    setTimeout(function () {
    var now = new Date();
    this.application.log("opening new xhr stream");
    this.changeStatus("ok");
    this.request = new Ajax.Request('/stream', {
      method: 'get',
      parameters: {
        t: now.getTime() / 1000,
        tab: this.application.activeWindow().id
      },
      on401: this.gotoLogin,
      on500: this.gotoLogin,
      on502: this.gotoLogin,
      on503: this.gotoLogin,
      onException: this.handleException.bind(this),
      onInteractive: function(transport) {
        if (!this.connected) {
          this.connected = true;
          setTimeout(cb, 0);
        }
        this.handleUpdate(transport);
      }.bind(this),
      onComplete: this.handleComplete.bind(this)
    });
    }.bind(this), this.application.loadDelay);
  },

  handleUpdate: function(transport) {
    if (this.reconnecting) {
      this.application.activeWindow().showHappyAlert("Reconnected to the Alice server");
      this.reconnecting = false;
    }

    this.reconnect_count = 0;

    var time = new Date();
    var data = transport.responseText.slice(this.len);
    var start, end;
    start = data.indexOf(this.seperator);

    if (start > -1) {
      start += this.seperator.length;
      end = data.indexOf(this.seperator, start);
      if (end == -1) return;
    }
    else return;

    this.len += (end + this.seperator.length) - start;
    data = data.slice(start, end);
    var data = data.evalJSON();

    this.processQueue(data);

    if (data.time) {
      var lag = this.addPing(time / 1000 -  data.time);

      if (lag > 5) {
        this.application.log("lag is over 5s, reconnecting.");
        this.connect();
      }
    }
  },

  addPing: function(ping) {
    this.pings.push(ping);
    if (this.pings.length > this.pingLimit)
      this.pings.shift();

    var lag = this.lag();
    return lag;
  },

  lag: function() {
    if (!this.pings.length) return 0;
    return this.pings.inject(0, function (acc, n) {return acc + n}) / this.pings.length;
  },

  sendMessage: function(form) {
    if (!this.connected) return false;

    var params;
    if (form.nodeName && form.nodeName == "FORM") {
      params = Form.serialize(form);
    }
    else {
      params = form;
    }

    params['stream'] = this.id;

    new Ajax.Request('/say', {
      method: 'post',
      parameters: params,
      on401: this.gotoLogin,
      onException: function (request, exception) {
        alert("There was an error sending a message.");
      }
    });

    return true;
  },

  closeConnection: function() {
    this.aborting = true;
    if (this.request && this.request.transport)
      this.request.transport.abort();
    this.aborting = false;
  },

  closeWindow: function(win) {
    new Ajax.Request('/say', {
      method: 'post',
      on401: this.gotoLogin,
      parameters: {
        source: win.id,
        msg: "/close",
        stream: this.id
      }
    });
  },

  requestWindow: function(title, windowId, message) {
    new Ajax.Request('/say', {
      method: 'post',
      parameters: {
        source: windowId,
        msg: "/create " + title,
        stream: this.id
      },
      on401: this.gotoLogin,
      onSuccess: function (transport) {
        this.handleUpdate(transport);
        if (message) {
          setTimeout(function() {
            this.application.displayMessage(message)
          }.bind(this), 1000);
        }
      }.bind(this)
    });
  }

});
Alice.Window = Class.create({
  initialize: function(application, serialized, msgid) {
    this.application = application;

    this.element = $(serialized['id']);
    this.title = serialized['title'];
    this.type = serialized['type'];
    this.hashtag = serialized['hashtag'];
    this.id = this.element.identify();
    this.active = false;
    this.topic = serialized['topic'];
    this.tab = $(this.id + "_tab");
    this.tab_layout = this.tab.getLayout();
    this.tabButton = $(this.id + "_tab_button");
    this.messages = this.element.down('.messages');
    this.visibleNick = "";
    this.visibleNickTimeout = "";
    this.lasttimestamp = new Date(0);
    this.nicks = [];
    this.nicks_order = [];
    this.statuses = [];
    this.messageLimit = this.application.isMobile ? 50 : 100;
    this.chunkSize = this.messageLimit / 2;
    this.msgid = msgid || 0;
    this.visible = true;
    this.forceScroll = false;
    this.lastScrollPosition = 0;

    this.setupEvents();
  },

  hide: function() {
    this.element.hide();
    this.tab.addClassName('hidden');
    this.tab.removeClassName('visible');
    this.visible = false;
  },

  show: function() {
    this.element.show();
    this.tab.addClassName('visible');
    this.tab.removeClassName('hidden');
    this.visible = true;
    this.updateTabLayout();
  },

  setupEvents: function() {
    this.application.supportsTouch ? this.setupTouchEvents() : this.setupMouseEvents();
  },

  setupTouchEvents: function() {
    this.messages.observe("touchstart", function (e) {
      this.showNick(e);
    }.bind(this));
    this.tab.observe("touchstart", function (e) {
      e.stop();
      if (!this.active) this.focus();
    }.bind(this));
    this.tabButton.observe("touchstart", function(e) {
      if (this.active) {
        e.stop();
        confirm("Are you sure you want to close this tab?") && this.close()
      }
    }.bind(this));
  },

  setupMouseEvents: function() {
    this.tab.observe("mousedown", function(e) {
      if (!this.active) {this.focus(); this.focusing = true}
    }.bind(this));

    this.tab.observe("click", function(e) {this.focusing = false}.bind(this));

    this.tabButton.observe("click", function(e) {
      if (this.active && !this.focusing)
        if (this.type != "channel" || confirm("Are you sure you want to leave "+this.title+"?"))
          this.close()
    }.bind(this));

    this.messages.observe("mouseover", this.showNick.bind(this));
  },

  checkScrollBack: function() {
    if (this.active && this.element.scrollTop == 0) {
      clearInterval(this.scrollListener);
      var first = this.messages.down("li[id]");
      if (first) {
        first = first.id.replace("msg-", "") - 1;
        this.messageLimit += this.chunkSize;
      }
      else {
        first = this.msgid;
      }
      this.application.log("requesting chunk" + first);
      this.tab.addClassName("loading");
      this.application.getBacklog(this, first, this.chunkSize);
    }
    else {
      clearTimeout(this.scrollListener);
      this.scrollListener = setTimeout(this.checkScrollBack.bind(this), 1000);
    }
  },

  clearMessages: function() {
    clearTimeout(this.scrollListener);
    this.messages.update("");
    this.lastNick = "";
  },

  updateTabLayout: function() {
    this.tab_layout = this.tab.getLayout();
  },

  getTabPosition: function() {
    var shift = this.application.tabShift();

    var tabs_width = this.application.tabsWidth();
    var tab_width = this.tab_layout.get("width");

    var offset_left = this.tab_layout.get("left") + shift;
    var offset_right = tabs_width - (offset_left + tab_width);

    var overflow_right = Math.abs(Math.min(0, offset_right));
    var overflow_left = Math.abs(Math.min(0, offset_left));

    return {
      right: overflow_right,
      left: overflow_left
    };
  },

  shiftTab: function() {
    var left = null
      , time = 0
      , pos = this.getTabPosition();

    if (pos.left) {
      this.application.shiftTabs(pos.left);
    }
    else if (pos.right) {
      this.application.shiftTabs(-pos.right);
    }
  },

  unFocus: function() {
    this.lastScrollPosition = this.captureScrollPosition();
    this.active = false;
    this.element.removeClassName('active');
    this.tab.removeClassName('active');
    clearTimeout(this.scrollListener);
    this.addFold();
  },

  addFold: function() {
    this.messages.select("li.fold").invoke("removeClassName", "fold");
    var last = this.messages.childElements().last();
    if (last) last.addClassName("fold");
  },

  showNick: function (e) {
    var li = e.findElement("li.message");
    if (li && li.hasClassName("avatar")) {
      if (this.application.overlayVisible || li == this.visibleNick) return;
      clearTimeout(this.visibleNickTimeout);

      this.visibleNick = li;
      var nick;
      if (li.hasClassName("consecutive")) {
        var stem = li.previous("li:not(.consecutive)");
        if (!stem) return;
        nick = stem.down("span.nick");
      } else {
        nick = li.down("span.nick");
      }

      if (nick) {
        this.visibleNickTimeout = setTimeout(function(nick) {
          if (nick) nick.style.opacity = 1;

          setTimeout(function(){
            if (this.application.overlayVisible) return;
            if (nick) nick.style.opacity = 0;
          }.bind(this, nick) , 1000);
        }.bind(this, nick), 500);
      }
    }
    else {
      this.visibleNick = "";
      clearTimeout(this.visibleNickTimeout);
    }
  },

  focus: function(event) {
    if (!this.application.currentSetContains(this)) return;

    this.application.previousFocus = this.application.activeWindow();
    if (this != this.application.previousFocus)
      this.application.previousFocus.unFocus();

    this.element.addClassName('active');
    this.tab.addClassName('active');

    if (!this.active) {
      this.active = true;

      setTimeout(function(){
        this.scrollToPosition(this.lastScrollPosition);
        if (!this.scrollBackEmpty) this.checkScrollBack();
      }.bind(this), 0);
    }

    this.application.setSource(this.id);
    this.application.displayNicks(this.nicks);
    this.markRead();
    this.setWindowHash();

    this.shiftTab();

    var last = this.messages.childElements().last();
    if (last && last.hasClassName("fold"))
      last.removeClassName("fold");

    this.application.displayTopic(this.topic);
    document.title = this.title;
    return this;
  },

  setWindowHash: function () {
    var new_hash = "#" + this.application.selectedSet + this.hashtag;
    if (new_hash != window.location.hash) {
      window.location.hash = encodeURI(new_hash);
      window.location = window.location.toString();
    }
  },

  markRead: function () {
    this.tab.removeClassName("unread");
    this.tab.removeClassName("highlight");
    this.statuses = [];
    this.application.unHighlightChannelSelect(this.id);
  },

  markUnread: function(classname) {
    var classes = ["unread"];
    if (classname) classes.push(classname);

    classes.each(function(c){this.tab.addClassName(c)}.bind(this));
    this.application.highlightChannelSelect(this.id, classes);
    this.statuses = classes;
  },

  disable: function () {
    this.markRead();
    this.tab.addClassName('disabled');
  },

  enable: function () {
    this.tab.removeClassName('disabled');
  },

  close: function(event) {
    this.tab.remove();
    this.element.remove();
    this.application.removeWindow(this);
  },

  showHappyAlert: function (message) {
    this.messages.insert(
      "<li class='event happynotice'><div class='msg'>"+message+"</div></li>"
    );
    this.scrollToPosition(0);
  },

  showAlert: function (message) {
    this.messages.insert(
      "<li class='event notice'><div class='msg'>"+message+"</div></li>"
    );
    this.scrollToPosition(0);
  },

  announce: function (message) {
    message = message.escapeHTML();
    this.messages.insert(
      "<li class='message monospaced announce'><div class='msg'>"+message+"</div></li>"
    );
    this.scrollToPosition(0);
  },

  trimMessages: function() {
    this.messages.select("li").reverse().slice(this.messageLimit).invoke("remove");
  },

  addChunk: function(chunk) {
    if (chunk.nicks) this.updateNicks(chunk.nicks);
    clearTimeout(this.scrollListener);

    if (chunk.range.length == 0) {
      this.scrollBackEmpty = true;
      this.tab.removeClassName("loading");
      return;
    }

    var position = this.captureScrollPosition();

    if (chunk['range'][0] > this.msgid) {
      this.messages.insert({"bottom": chunk['html']});
      this.trimMessages();
      this.msgid = chunk['range'][1];
    }
    else {
      this.bulk_insert = true;
      this.messages.insert({"top": chunk['html']});
    }

    this.messages.select("li:not(.filtered)").each(function (li) {
      this.application.applyFilters(li, this);
    }.bind(this));

    this.bulk_insert = false;

    this.scrollToPosition(position);
    setTimeout(function(){this.removeClassName("loading")}.bind(this.tab), 1000);
    this.scrollListener = setTimeout(this.checkScrollBack.bind(this), 1000);
  },

  addMessage: function(message) {
    if (!message.html || message.msgid <= this.msgid) return;

    if (message.msgid) this.msgid = message.msgid;
    if (message.nicks) this.updateNicks(message.nicks);

    var position = this.captureScrollPosition();

    this.messages.insert(message.html);
    this.trimMessages();

    this.scrollToPosition(position);

    var li = this.messages.select("li").last();
    this.application.applyFilters(li, this);

    this.scrollToPosition(position);

    if (message.event == "topic") {
      this.topic = message.body;
      if (this.active) this.application.displayTopic(this.topic);
    }

    this.element.redraw();
    this.promoteNick(message.nick);
  },

  promoteNick: function(nick) {
    if (this.nicks_order.last() == nick) return;

    var index = this.nicks_order.indexOf(nick);
    if (index > -1) this.nicks_order.splice(index, 1);

    this.nicks_order.push(nick);
  },

  captureScrollPosition: function() {
    if (!this.active) return;
    if (this.forceScroll) return 0;

    var bottom = this.element.scrollTop + this.element.offsetHeight;
    var height = this.element.scrollHeight;

    return height - bottom;
  },

  scrollToPosition: function(position) {
    if (!this.active) return;
    this.element.scrollTop = this.element.scrollHeight - this.element.offsetHeight - position;
  },

  getNicknames: function () {
    return this.nicks.sort(function(a, b) {

      var pos_a = this.nicks_order.indexOf(a),
          pos_b = this.nicks_order.indexOf(b);

      if (pos_a == pos_b)

share/static/alice.js  view on Meta::CPAN


if (window == window.parent) {
  document.observe("dom:loaded", function () {
    var alice = new Alice.Application();
    window.alice = alice;

    $('helpclose').observe("click", function () { $('help').hide(); });
    $('nicklist_toggle').observe("click", function () { alice.toggleNicklist() });

    $$('.dropdown').each(function (menu) {
      menu.observe(alice.supportsTouch ? "touchstart" : "mousedown", function (e) {
        e.stop();
        var element = e.element('.dropdown');
        if (element.hasClassName("dropdown")) {
          if (menu.hasClassName("open")) {
            menu.removeClassName("open");
          }
          else {
            $$(".dropdown.open").invoke("removeClassName", "open");
            menu.addClassName("open");
          }
        }
      });
    });

    document.observe(alice.supportsTouch ? "touchend" : "mouseup", function (e) {
      if (e.findElement('.dropdown')) return;
      $$('.dropdown.open').invoke("removeClassName", "open");
    });


    if (alice.isMobile) {
      $('nicklist_toggle').addClassName('visible');
    }
    else {
      window.onkeydown = function (e) {
        if (!alice.input.disabled && !Alice.isSpecialKey(e.which))
          alice.input.focus();
      };


      var windows = $('windows');
      var toggle = $('nicklist_toggle');

      var resize = function () {
        var active = alice.activeWindow();
        var position = active.captureScrollPosition();

        var end = function(){
          alice.freeze();
          alice.tabs_width = $('tabs_container').getWidth();
          alice.updateOverflowMenus();
          active.scrollToPosition(position);
          active.shiftTab();
          window.onresize = resize;
        };

        var end_timer;

        window.onresize = function() {
          clearTimeout(end_timer);
          end_timer = setTimeout(end, 1000);
        };
      };

      window.onresize = resize;

      var move = function(e) {
        var width = document.viewport.getWidth();
        var left = windows.hasClassName('nicklist') ? 200 : 100;
        var visible  = toggle.hasClassName('visible');
        if (!visible && width - e.pointerX() > left)
          return;

        toggle.addClassName('visible');

        var end = function() {
          toggle.removeClassName('visible');
          window.onmousemove = move;
        };
        var end_timer;

        window.onmousemove = function() {
          clearTimeout(end_timer);
          end_timer = setTimeout(end, 1000);
        };
      };

      window.onmousemove = move;

      window.onfocus = function () {
        alice.input.focus();

        alice.freeze();
        alice.tabs_width = $('tabs_container').getWidth();
        alice.updateOverflowMenus();

        alice.isFocused = true
        alice.clearMissed();
      };

      window.status = " ";
      window.onblur = function () {
        alice.isFocused = false
      };
    }

    window.onhashchange = function (e) {alice.focusHash()};

    window.onorientationchange = function() {
      var active = alice.activeWindow();
      active.scrollToPosition(0);
      alice.freeze();
      active.shiftTab();
    };

    document.observe("copy", function(e) {
      if (!e.findElement("ul.messages")) return;

      if(!Prototype.Browser.IE && typeof window.getSelection !== 'undefined') {
        var buffer = new Element("DIV", {"class": "copybuffer"});
        document.getElementsByTagName("body")[0].appendChild(buffer);
        var sel = window.getSelection();
        var range = sel.getRangeAt(0);
        buffer.appendChild(range.cloneContents());
        Alice.cleanupCopy(buffer);
        sel.selectAllChildren(buffer);

        setTimeout(function() {
          if(typeof window.getSelection().setBaseAndExtent !== 'undefined') {
            sel.setBaseAndExtent(
              range.startContainer,
              range.startOffset,
              range.endContainer,
              range.endOffset
            );
          }
        }, 0);

      }
    });

    if (alice.isMobile) return;

    alice.addFilters([
      function(msg, win) {
        msg.select("a").filter(function(a) {
          return Alice.RE.audio.match(a.href);
        }).each(function(a) {
          var img = new Element("IMG", {"class": "audio", src: "/static/image/play.png"});
          img.onclick = function(){ Alice.playAudio(img) };
          a.insert({before: img})
        });
      },
      function (msg, win) {
        if (alice.options.images == "show") {
          var matches = msg.select("a").inject(0, function(acc, a) {
            var oembed = alice.oembeds.find(function(service) {
              return service.match(a.href);
            });
            if (oembed) {
              alice.embed(a, win);
              acc++;
            }
            return acc;
          });
          return matches > 0;
        }
      },
      function (msg, win) {
        msg.select("a").filter(function(a) {
          var img = a.readAttribute("img") || a.innerHTML;
          return img.match(Alice.RE.img);
        }).each(function(a) {
          var image = a.readAttribute("img") || a.href;
          if (alice.options.images == "show" && !image.match(/#(nsfw|hide)$/))
            win.inlineImage(a);
          else
            a.observe("click", function(e){e.stop();win.inlineImage(a)});
        });
      }
    ]);

    if (window.navigator.userAgent.match(/chrome/i)) {
      alice.addFilters([
        function(msg, win) {
          msg.setStyle({borderWidthTop: "1px"});
        }
      ]);



( run in 0.717 second using v1.01-cache-2.11-cpan-39bf76dae61 )