view static-files/index.js @ 47:b938cbfa69c2

added bio display and showdown.js.
author Atul Varma <avarma@mozilla.com>
date Sat, 26 Jun 2010 18:13:37 -0700
parents 6a38739058a7
children 22da6fc3596c
line wrap: on
line source

// -----------------------------------------------------------------------
// Config object
// -----------------------------------------------------------------------
// 
// This manages interactions with client-side persistent storage and the
// current login state of the app.

(
  function(window) {
    // Amount of time, in milliseconds, that pass before we check
    // to see if another instance of this page changed our persistent
    // storage.
    const REFRESH_INTERVAL = 2000;

    var Config = window.Config = {
      get value() {
        var val = localStorage.getItem("SUMMIT_CFG");
        if (val)
          return JSON.parse(val);
        return {};
      },
      get lastChanged() {
        var ts = localStorage.getItem("SUMMIT_CFG_TIMESTAMP");
        if (ts)
          return new Date(ts);
        return new Date(0);
      },
      setValue: function Config_setValue(val) {
        localStorage.setItem("SUMMIT_CFG", JSON.stringify(val));
        localStorage.setItem("SUMMIT_CFG_TIMESTAMP",
                             (new Date()).toString());
        this.observers.forEach(function(cb) { cb(); });
      },
      wipe: function Config_wipe() {
        localStorage.removeItem("SUMMIT_CFG");
        localStorage.removeItem("SUMMIT_CFG_TIMESTAMP");
        this.observers.forEach(function(cb) { cb(); });
      },
      observers: []
    };

    function ensureStateIsValid() {
      if (!('state' in Config.value))
        Config.setValue({state: "login"});
    }

    Config.observers.push(ensureStateIsValid);

    // This is useful for finding out whether our configuration has
    // been changed by another instance of our same window.
    function setupConfigWatcher() {
      var lastChanged = Config.lastChanged;

      function onConfigChanged() {
        lastChanged = Config.lastChanged;
      };

      Config.observers.push(onConfigChanged);

      window.setInterval(
        function() {
          if (Config.lastChanged > lastChanged)
            Config.observers.forEach(function(cb) { cb(); });
        },
        REFRESH_INTERVAL
      );
    }

    function initConfig() {
      ensureStateIsValid();
      setupConfigWatcher();
    }

    $(window).ready(initConfig);
  }
)(window);

// -----------------------------------------------------------------------
// Attendees object
// -----------------------------------------------------------------------
// 
// This manages the list of Summit attendees and their shared metadata.

(
  function(window) {
    var req;

    var Attendees = window.Attendees = {
      refresh: function refresh() {
        if (req)
          return;
        var self = this;
        if (Config.value.token) {
          req = jQuery.getJSON(
            "api/profile",
            {token: Config.value.token},
            function(data, textStatus) {
              // TODO: Might need to add a failure callback too?
              req = null;
              if (textStatus == "success") {
                self.value = data.contents;
                self.observers.forEach(function(cb) { cb(); });
              } else {
                // TODO: Raise an error?
              }
            });
        }
      },
      value: null,
      observers: []
    };

    function refreshAttendees() {
      if (Config.value.state == "logged-in" && !Attendees.users)
        Attendees.refresh();
    }

    Config.observers.push(refreshAttendees);
    $(window).ready(refreshAttendees);
  }
)(window);

jQuery.postJSON = function postJSON(path, obj, cb) {
  var options = {
    url: path,
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(obj),
    dataType: "json",
    success: function(data) {
      cb(true, data);
    },
    error: function(req, textStatus, errorThrown) {
      var data = null;
      if (req.getResponseHeader("Content-Type") == "application/json") {
        try {
          data = JSON.parse(req.responseText);
        } catch (e) {}
      }
      cb(false, data);
    }
  };
  return jQuery.ajax(options);
};

// -----------------------------------------------------------------------
// UserInterface object
// -----------------------------------------------------------------------
// 
// This manages the user interface logic.

(
  function(window) {
    var UserInterface = window.UserInterface = {};

    function updateUI() {
      $(".screen").hide();
      $("#" + Config.value.state).show();
      switch (Config.value.state) {
      case "login":
        break;
      case "wait-for-verify":
        break;
      case "logged-in":
        $(".login-email").text(Config.value.email);
        break;
      }
    }

    function normalizeUserInfo(userInfo) {
      if (!(userInfo.interests && jQuery.isArray(userInfo.interests)))
        userInfo.interests = [];
    }

    function fillUserInfo() {
      var userInfo = Attendees.value[Config.value.userID];
      if (!userInfo)
        userInfo = {};
      normalizeUserInfo(userInfo);

      // Fill out the form.
      $(".usr").each(
        function() {
          var prop = this.id.match(/usr_(.+)/)[1];
          $(this).val(userInfo[prop] || "");
        });
      $("#usr_interests input").each(
        function() {
          var label = $(this.parentNode).text().trim();
          this.checked = (userInfo.interests.indexOf(label) != -1);
        });

      // Build list of all attendees.
      var everyone = $("#everyone-info .attendees");
      everyone.empty();
      for (userID in Attendees.value) {
        var person = Attendees.value[userID];
        normalizeUserInfo(person);

        var elem = $("#templates .attendee").clone();
        elem.find(".name").text(person.name || "Anonymous Human");
        var profileImageURL = person.profileImageURL;
        if (!profileImageURL && person.twitterScreenName)
          profileImageURL = ("http://api.twitter.com/1/users/" +
                             "profile_image/" +
                             person.twitterScreenName +
                             ".xml?size=normal");

        if (profileImageURL)
          elem.find(".headshot img").attr("src", profileImageURL);

        if (person.websiteURL) {
          var websiteLink = document.createElement("a");
          websiteLink.href = person.websiteURL;
          websiteLink.target = "_blank";
          elem.find(".name").wrapInner(websiteLink);
        }

        if (person.twitterScreenName) {
          var twitterLink = document.createElement("a");
          twitterLink.href = "http://twitter.com/" + person.twitterScreenName;
          twitterLink.target = "_blank";
          twitterLink.textContent = "@" + person.twitterScreenName;
          elem.find(".twitter").append(twitterLink);
        } else
          elem.find(".twitter").remove();

        if (person.bugzillaEmail) {
          var bugzillaLink = document.createElement("a");
          bugzillaLink.href = ("https://hg.mozilla.org/users/" +
                               "avarma_mozilla.com/" +
                               "bugzilla-dashboard/raw-file/v2/" +
                               "index.html#username=" +
                               encodeURI(person.bugzillaEmail));
          bugzillaLink.target = "_blank";
          elem.find(".bugzilla").wrapInner(bugzillaLink);
        } else
          elem.find(".bugzilla").remove();

        if (person.interests.length > 0) {
          var interests = elem.find(".interests ul");
          person.interests.forEach(
            function(interest) {
              var item = document.createElement("li");
              item.textContent = interest;
              interests.append(item);
            });
        } else
          elem.find(".interests").remove();

        if (person.bio) {
          var converter = new Showdown.converter();
          var text = converter.makeHtml(person.bio);
          elem.find(".bio").html(text);
        } else
          elem.find(".bio").remove();

        everyone.append(elem);
      }
    }

    Attendees.observers.push(fillUserInfo);
    Config.observers.push(updateUI);

    function initUI() {
      $("#logged-in").tabs();

      $(".start-over").submit(
        function(event) {
          event.preventDefault();
          Config.wipe();
        });

      $("#login form").submit(
        function(event) {
          event.preventDefault();
          $("#login .error").hide();
          jQuery.postJSON(
            "api/challenge/request",
            {email: $(this).find("#email").val() },
            function(success, data) {
              if (success) {
                Config.setValue({state: "wait-for-verify"});
              } else {
                $("#login .error").slideDown();
              }
            });
        });

      $("#logged-in form").submit(
        function(event) {
          event.preventDefault();
          $("#logged-in .success").hide();
          $("#logged-in .error").hide();

          var contents = {};

          $(".usr").each(
            function() {
              var prop = this.id.match(/usr_(.+)/)[1];
              contents[prop] = this.value;
            });

          contents.interests = [];

          $("#usr_interests input").each(
            function() {
              if (this.checked)
                contents.interests.push($(this.parentNode).text().trim());
            });

          jQuery.postJSON(
            "api/profile",
            {token: Config.value.token,
             contents: contents},
            function(success, data) {
              if (success) {
                $("#logged-in .success").slideDown();
                // Do a full round-trip to refresh our cached information
                // about ourselves. Not efficient at all, but it'll work
                // for now.
                Attendees.refresh();
              } else {
                $("#logged-in .error").slideDown();
              }
            });
        });

      var verify = window.location.hash.match(/#verify=(.+)/);
      if (verify && Config.value.state != "logged-in") {
        verify = verify[1];
        Config.setValue({state: "wait-for-verify"});
        jQuery.postJSON(
          "api/challenge/respond",
          {token: verify},
          function(success, data) {
            window.location.hash = "";
            updateUI();
            if (success) {
              Config.setValue({state: "logged-in",
                               token: data.token,
                               userID: data.user_id,
                               email: data.email});
            } else {
              $("#wait-for-verify .error").slideDown();
            }
          });
      } else
        updateUI();
    }

    $(window).ready(initUI);
  }
)(window);

// -----------------------------------------------------------------------
// Server object
// -----------------------------------------------------------------------
// 
// This sets up an in-browser "server" that can be accessed from other
// windows via a postMessage API.

(
  function(window) {
    var Server = window.Server = {};

    var attendeeCallbacks = [];

    var myOrigin = window.location.protocol + "//" + window.location.host;
    var handlers = {
      getAllUsers: function(options, cb, origin) {
        if (origin != myOrigin) {
          cb({error: "access denied"});
          return;
        }

        // TODO: Add support for more origins.

        if (Config.value.state != "logged-in") {
          cb({error: "not logged in"});
          return;
        }

        if (Attendees.value) {
          cb({users: Attendees.value});
        } else
          attendeeCallbacks.push(cb);
      }
    };

    var server = new Summit.Server(handlers);

    Attendees.observers.push(
      function() {
        var cbs = attendeeCallbacks;
        attendeeCallbacks = [];
        cbs.forEach(function(cb) { cb({users: Attendees.value}); });
      });
  }
)(window);