Commits

Tom Morton committed 0213d3c

more work

Comments (0)

Files changed (21)

 .watchlist .time {
   font-weight: bold;
 }
+
+.navbar .badge {
+  display: block;
+  margin-top: 10px;
+}
     </div> <!-- /container -->
 <script src="js/libs/jquery-min.js"></script>
 
+<script src="js/libs/json.js"></script>
+
 <script src="js/libs/bootstrap-min.js"></script>
 <script src="js/libs/mustache-min.js"></script>
 <script src="js/libs/underscore-min.js"></script>
   __hasProp = Object.prototype.hasOwnProperty,
   __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
 
-define(["WikiApi", "Auth", "views/menu", "views/watchlist/list"], function(MW, Auth, Menu, ListView) {
+define(["WikiApi", "Auth", "views/viewport", "views/watchlist/list"], function(MW, Auth, ViewPort, ListView) {
   var Router;
   return Router = (function(_super) {
 
     }
 
     Router.prototype.initialize = function() {
-      return Auth.checkLogin(this.loggedIn, this.needLogin);
+      Auth.checkLogin(this.loggedIn, this.needLogin);
+      return ViewPort.render();
     };
 
     Router.prototype.loggedIn = function() {
-      console.log(Auth);
-      Menu.render();
-      return Menu.setUsername(Auth.user.name);
+      ViewPort.menu.setUser(Auth.user);
+      return Backbone.history.start();
     };
 
     Router.prototype.needLogin = function() {
-      return this.navigate("login", {
-        trigger: true
-      });
+      window.location.hash = "#login";
+      return Backbone.history.start();
     };
 
     Router.prototype.routes = {
     };
 
     Router.prototype.watchlist = function() {
-      Menu.setActiveLink("#watchlist");
+      ViewPort.menu.setActiveLink("#watchlist");
       return MW.get({
         action: "query",
         list: "watchlist",
 
-define(['WikiApi'], function(MW) {
+define(['WikiApi', "views/viewport", "CurrentUser"], function(MW, ViewPort, CurrentUser) {
   var Auth, auth;
   Auth = (function() {
 
     function Auth() {}
 
-    Auth.prototype.user = null;
+    Auth.prototype.user = CurrentUser;
 
     Auth.prototype.checkLogin = function(success, fail) {
       var _this = this;
       return MW.checkLogin(function(userinfo) {
-        _this.user = userinfo;
+        _this.user.setDetails(userinfo);
+        _this.user.loadGeoNotices();
         return success();
       }, fail);
     };

js/classes/currentuser.js

+
+define(["User"], function(User) {
+  return new User;
+});
+
+define([], function() {
+  var User;
+  return User = (function() {
+
+    function User() {}
+
+    User.prototype.userinfo = null;
+
+    User.prototype.name = function() {
+      return this.userinfo.name;
+    };
+
+    User.prototype.setDetails = function(userinfo) {
+      return this.userinfo = userinfo;
+    };
+
+    User.prototype.countMessages = function() {
+      var c;
+      c = 0;
+      console.log(this.userinfo.messages);
+      if (this.userinfo.messages !== void 0) c += 1;
+      return c;
+    };
+
+    User.prototype.loadGeoNotices = function() {
+      var _this = this;
+      return $.ajax({
+        url: "http://en.wikipedia.org/w/index.php",
+        data: {
+          title: "MediaWiki:Geonotice.js",
+          action: "raw",
+          ctype: "text",
+          maxage: 3600,
+          ver: 2
+        },
+        success: function(data) {
+          var jsonStuff;
+          jsonStuff = "{" + data.slice(15, data.indexOf("/*")) + "}";
+          return console.log(jsonParse(jsonStuff));
+        }
+      });
+    };
+
+    return User;
+
+  })();
+});
+// This source code is free for use in the public domain.
+// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+// http://code.google.com/p/json-sans-eval/
+
+/**
+ * Parses a string of well-formed JSON text.
+ *
+ * If the input is not well-formed, then behavior is undefined, but it is
+ * deterministic and is guaranteed not to modify any object other than its
+ * return value.
+ *
+ * This does not use `eval` so is less likely to have obscure security bugs than
+ * json2.js.
+ * It is optimized for speed, so is much faster than json_parse.js.
+ *
+ * This library should be used whenever security is a concern (when JSON may
+ * come from an untrusted source), speed is a concern, and erroring on malformed
+ * JSON is *not* a concern.
+ *
+ *                      Pros                   Cons
+ *                    +-----------------------+-----------------------+
+ * json_sans_eval.js  | Fast, secure          | Not validating        |
+ *                    +-----------------------+-----------------------+
+ * json_parse.js      | Validating, secure    | Slow                  |
+ *                    +-----------------------+-----------------------+
+ * json2.js           | Fast, some validation | Potentially insecure  |
+ *                    +-----------------------+-----------------------+
+ *
+ * json2.js is very fast, but potentially insecure since it calls `eval` to
+ * parse JSON data, so an attacker might be able to supply strange JS that
+ * looks like JSON, but that executes arbitrary javascript.
+ * If you do have to use json2.js with untrusted data, make sure you keep
+ * your version of json2.js up to date so that you get patches as they're
+ * released.
+ *
+ * @param {string} json per RFC 4627
+ * @param {function (this:Object, string, *):*} opt_reviver optional function
+ *     that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1.
+ *     If supplied, the function is called with a string key, and a value.
+ *     The value is the property of 'this'.  The reviver should return
+ *     the value to use in its place.  So if dates were serialized as
+ *     {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
+ *     {@code
+ *     function (key, value) {
+ *       if (value && typeof value === 'object' && 'Date' === value.type) {
+ *         return new Date(value.time);
+ *       } else {
+ *         return value;
+ *       }
+ *     }}.
+ *     If the reviver returns {@code undefined} then the property named by key
+ *     will be deleted from its container.
+ *     {@code this} is bound to the object containing the specified property.
+ * @return {Object|Array}
+ * @author Mike Samuel <mikesamuel@gmail.com>
+ */
+var jsonParse = (function () {
+  var number
+      = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
+  var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
+      + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
+  var string = '(?:\"' + oneChar + '*\")';
+
+  // Will match a value in a well-formed JSON file.
+  // If the input is not well-formed, may match strangely, but not in an unsafe
+  // way.
+  // Since this only matches value tokens, it does not match whitespace, colons,
+  // or commas.
+  var jsonToken = new RegExp(
+      '(?:false|true|null|[\\{\\}\\[\\]]'
+      + '|' + number
+      + '|' + string
+      + ')', 'g');
+
+  // Matches escape sequences in a string literal
+  var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');
+
+  // Decodes escape sequences in object literals
+  var escapes = {
+    '"': '"',
+    '/': '/',
+    '\\': '\\',
+    'b': '\b',
+    'f': '\f',
+    'n': '\n',
+    'r': '\r',
+    't': '\t'
+  };
+  function unescapeOne(_, ch, hex) {
+    return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
+  }
+
+  // A non-falsy value that coerces to the empty string when used as a key.
+  var EMPTY_STRING = new String('');
+  var SLASH = '\\';
+
+  // Constructor to use based on an open token.
+  var firstTokenCtors = { '{': Object, '[': Array };
+
+  var hop = Object.hasOwnProperty;
+
+  return function (json, opt_reviver) {
+    // Split into tokens
+    var toks = json.match(jsonToken);
+    // Construct the object to return
+    var result;
+    var tok = toks[0];
+    var topLevelPrimitive = false;
+    if ('{' === tok) {
+      result = {};
+    } else if ('[' === tok) {
+      result = [];
+    } else {
+      // The RFC only allows arrays or objects at the top level, but the JSON.parse
+      // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
+      // at the top level.
+      result = [];
+      topLevelPrimitive = true;
+    }
+
+    // If undefined, the key in an object key/value record to use for the next
+    // value parsed.
+    var key;
+    // Loop over remaining tokens maintaining a stack of uncompleted objects and
+    // arrays.
+    var stack = [result];
+    for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
+      tok = toks[i];
+
+      var cont;
+      switch (tok.charCodeAt(0)) {
+        default:  // sign or digit
+          cont = stack[0];
+          cont[key || cont.length] = +(tok);
+          key = void 0;
+          break;
+        case 0x22:  // '"'
+          tok = tok.substring(1, tok.length - 1);
+          if (tok.indexOf(SLASH) !== -1) {
+            tok = tok.replace(escapeSequence, unescapeOne);
+          }
+          cont = stack[0];
+          if (!key) {
+            if (cont instanceof Array) {
+              key = cont.length;
+            } else {
+              key = tok || EMPTY_STRING;  // Use as key for next value seen.
+              break;
+            }
+          }
+          cont[key] = tok;
+          key = void 0;
+          break;
+        case 0x5b:  // '['
+          cont = stack[0];
+          stack.unshift(cont[key || cont.length] = []);
+          key = void 0;
+          break;
+        case 0x5d:  // ']'
+          stack.shift();
+          break;
+        case 0x66:  // 'f'
+          cont = stack[0];
+          cont[key || cont.length] = false;
+          key = void 0;
+          break;
+        case 0x6e:  // 'n'
+          cont = stack[0];
+          cont[key || cont.length] = null;
+          key = void 0;
+          break;
+        case 0x74:  // 't'
+          cont = stack[0];
+          cont[key || cont.length] = true;
+          key = void 0;
+          break;
+        case 0x7b:  // '{'
+          cont = stack[0];
+          stack.unshift(cont[key || cont.length] = {});
+          key = void 0;
+          break;
+        case 0x7d:  // '}'
+          stack.shift();
+          break;
+      }
+    }
+    // Fail if we've got an uncompleted object.
+    if (topLevelPrimitive) {
+      if (stack.length !== 1) { throw new Error(); }
+      result = result[0];
+    } else {
+      if (stack.length) { throw new Error(); }
+    }
+
+    if (opt_reviver) {
+      // Based on walk as implemented in http://www.json.org/json2.js
+      var walk = function (holder, key) {
+        var value = holder[key];
+        if (value && typeof value === 'object') {
+          var toDelete = null;
+          for (var k in value) {
+            if (hop.call(value, k) && value !== holder) {
+              // Recurse to properties first.  This has the effect of causing
+              // the reviver to be called on the object graph depth-first.
+
+              // Since 'this' is bound to the holder of the property, the
+              // reviver can access sibling properties of k including ones
+              // that have not yet been revived.
+
+              // The value returned by the reviver is used in place of the
+              // current value of property k.
+              // If it returns undefined then the property is deleted.
+              var v = walk(value, k);
+              if (v !== void 0) {
+                value[k] = v;
+              } else {
+                // Deleting properties inside the loop has vaguely defined
+                // semantics in ES3 and ES3.1.
+                if (!toDelete) { toDelete = []; }
+                toDelete.push(k);
+              }
+            }
+          }
+          if (toDelete) {
+            for (var i = toDelete.length; --i >= 0;) {
+              delete value[toDelete[i]];
+            }
+          }
+        }
+        return opt_reviver.call(holder, key, value);
+      };
+      result = walk({ '': result }, '');
+    }
+
+    return result;
+  };
+})();
         data: {
           action: "query",
           meta: "userinfo",
-          format: "json"
+          format: "json",
+          uiprop: "hasmsg|editcount"
         },
         success: function(data) {
           if (data.query.userinfo.id === 0) {
  	paths: {
  		WikiApi: "libs/wiki",
     // Classes
-    Auth: "classes/auth"
+    Auth: "classes/auth",
+    User: "classes/user",
+    CurrentUser: "classes/currentuser"
  	}
 });
 
 require([
 
   // Load our app module and pass it to our definition function
-  'app',
-
+  'app'
   // Some plugins have to be loaded in order due to there non AMD compliance
   // Because these scripts are not "modules" they do not pass any values to the definition function below
 ], function(App){
   // The "app" dependency is passed in as "App"
   // Again, the other dependencies passed in are not "AMD" therefore don't pass a parameter to this function
   app = new App();
-  Backbone.history.start();
+
 });

js/templates/menu.html

     <span class="pull-right" >
       <ul class="nav">
         <li><a href="#user" id="username" ></a></li>
+        <li><span class="badge badge-important" ></span></li>
         <li><a href="#watchlist">Watchlist</a></li>
         <li><a href="#contribs" >Contributions</a></li>
       </ul>
 
     Menu.prototype.el = $("#menu");
 
-    Menu.prototype.setUsername = function(user) {
-      return $("#username", this.el).html(user);
+    Menu.prototype.setUser = function(user) {
+      $("#username", this.el).html(user.name());
+      return $(".badge", this.el).html(user.countMessages());
     };
 
     Menu.prototype.setActiveLink = function(link) {
+var __hasProp = Object.prototype.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
+
+define(["views/menu"], function(Menu) {
+  var Viewport, viewport;
+  Viewport = (function(_super) {
+
+    __extends(Viewport, _super);
+
+    function Viewport() {
+      Viewport.__super__.constructor.apply(this, arguments);
+    }
+
+    Viewport.prototype.el = "body";
+
+    Viewport.prototype.initialize = function() {
+      return this.menu = Menu;
+    };
+
+    Viewport.prototype.render = function() {
+      return this.menu.render();
+    };
+
+    return Viewport;
+
+  })(Backbone.View);
+  viewport = new Viewport;
+  return viewport;
+});
     "http://en.wikipedia.org/"
   ],
   "manifest_version": 2,
-  "content_security_policy": "default-src: 'unsafe-eval'; script-src 'self'; object-src 'self'"
+  "content_security_policy": "default-src: 'unsafe-eval' 'unsafe-inline'; script-src 'self'; object-src 'self'"
 }

src/coffee/app.coffee

-define(["WikiApi", "Auth", "views/menu", "views/watchlist/list"],
-  (MW, Auth, Menu, ListView) ->
+define(["WikiApi", "Auth", "views/viewport", "views/watchlist/list"],
+  (MW, Auth, ViewPort, ListView) ->
       class Router extends Backbone.Router
         initialize: () =>
           Auth.checkLogin(@loggedIn,@needLogin)
+          ViewPort.render()
 
         loggedIn: =>
-          console.log(Auth)
-          Menu.render()
-          Menu.setUsername(Auth.user.name)
-          
+          ViewPort.menu.setUser(Auth.user)
+          Backbone.history.start()
 
         needLogin: =>
-          @navigate("login",{trigger:true})
+          window.location.hash = "#login"
+          Backbone.history.start()
 
         routes:
           "watchlist": "watchlist"
           "login": "login"
 
         watchlist: () ->    
-          Menu.setActiveLink("#watchlist")  
+          ViewPort.menu.setActiveLink("#watchlist")  
           MW.get({action:"query",list:"watchlist",wllimit:150,format:"json",wlprop:"title|ids|user|comment|flags|timestamp|sizes|parsedcomment"}, (data) ->
             list = new ListView()
             _.each(data.query.watchlist, (item) -> 

src/coffee/classes/auth.coffee

-define(['WikiApi'], 
-  (MW) ->
+define(['WikiApi',"views/viewport","CurrentUser"], 
+  (MW,ViewPort,CurrentUser) ->
     class Auth
-    	user: null
+    	user: CurrentUser
     	checkLogin: (success,fail) ->
     		MW.checkLogin((userinfo) => 
-    			@user = userinfo
+    			@user.setDetails(userinfo)
+    			@user.loadGeoNotices()
     			success()
     		,fail)
     auth = new Auth

src/coffee/classes/currentuser.coffee

+define(["User"], 
+  (User) ->
+    new User
+)

src/coffee/classes/user.coffee

+define([], 
+  () ->
+    class User
+      userinfo: null
+      name: () ->
+        @userinfo.name
+      setDetails: (userinfo) ->
+        @userinfo = userinfo
+      countMessages: () ->
+        c = 0
+        if @userinfo.messages != undefined
+          c += 1
+        c
+      loadGeoNotices: () ->
+        $.ajax({
+          url   : "http://en.wikipedia.org/w/index.php"
+          data  : 
+            title   : "MediaWiki:Geonotice.js"
+            action  : "raw"
+            ctype   : "text"
+            maxage  : 3600
+            ver     : 2
+          success: (data) => 
+            jsonStuff = "{"+data.slice(15,data.indexOf("/*"))+"}"
+            console.log(jsonParse(jsonStuff))
+        })
+)

src/coffee/libs/wiki.coffee

           action  : "query"
           meta    : "userinfo"
           format  : "json"
+          uiprop  : "hasmsg|editcount"
         success: (data) => 
           if data.query.userinfo.id == 0
             fail()

src/coffee/views/menu.coffee

   (template) ->
     class Menu extends Backbone.View
       el: $("#menu")
-      setUsername: (user) ->
-        $("#username",@el).html(user)
+      setUser: (user) ->
+        $("#username",@el).html(user.name())
+        $(".badge",@el).html(user.countMessages())
       setActiveLink: (link) ->
         _.each($("li",@el),(li) =>
           console.log(li)

src/coffee/views/viewport.coffee

+define(["views/menu"],
+  (Menu) ->
+  	class Viewport extends Backbone.View
+  		el: "body"
+  		initialize: ->
+  			@menu = Menu
+  		render: () ->
+  			@menu.render()
+  	viewport = new Viewport
+  	viewport
+)
 	.comment { padding-left: 110px; font-style: italic;}
 	.userlinks { font-style: italic;}
 	.time { font-weight: bold; }
+}
+
+.navbar {
+	.badge { display: block; margin-top: 10px;}
 }