{"action":"created","comment":{"url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/comments/199985490","pull_request_review_id":134210842,"id":199985490,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5OTk4NTQ5MA==","diff_hunk":"@@ -0,0 +1,847 @@\n+/**\n+ * MobWrite - Real-time Synchronization and Collaboration Service\n+ *\n+ * Copyright 2006 Google Inc.\n+ * http://code.google.com/p/google-mobwrite/\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+/**\n+ * @fileoverview This client-side code drives the synchronisation.\n+ * @author fraser@google.com (Neil Fraser)\n+ */\n+\n+\n+/**\n+ * Singleton class containing all MobWrite code.\n+ */\n+var mobwrite = {};\n+\n+\n+/**\n+ * URL of Ajax gateway.\n+ * @type {string}\n+ */\n+mobwrite.syncGateway = '/scripts/q.py';\n+\n+\n+/**\n+ * Print diagnostic messages to the browser's console.\n+ * @type {boolean}\n+ */\n+mobwrite.debug = false;\n+\n+\n+/**\n+ * PID of task which will trigger next Ajax request.\n+ * @type {number?}\n+ * @private\n+ */\n+mobwrite.syncRunPid_ = null;\n+\n+\n+/**\n+ * PID of task which will kill stalled Ajax request.\n+ * @type {number?}\n+ * @private\n+ */\n+mobwrite.syncKillPid_ = null;\n+\n+\n+/**\n+ * Time to wait for a connection before giving up and retrying.\n+ * @type {number}\n+ */\n+mobwrite.timeoutInterval = 30000;\n+\n+\n+/**\n+ * Shortest interval (in milliseconds) between connections.\n+ * @type {number}\n+ */\n+mobwrite.minSyncInterval = 1000;\n+\n+\n+/**\n+ * Longest interval (in milliseconds) between connections.\n+ * @type {number}\n+ */\n+mobwrite.maxSyncInterval = 10000;\n+\n+\n+/**\n+ * Initial interval (in milliseconds) for connections.\n+ * This value is modified later as traffic rates are established.\n+ * @type {number}\n+ */\n+mobwrite.syncInterval = 2000;\n+\n+\n+/**\n+ * Optional prefix to automatically add to all IDs.\n+ * @type {string}\n+ */\n+mobwrite.idPrefix = '';\n+\n+\n+/**\n+ * Flag to nullify all shared elements and terminate.\n+ * @type {boolean}\n+ */\n+mobwrite.nullifyAll = false;\n+\n+\n+/**\n+ * Track whether something changed client-side in each sync.\n+ * @type {boolean}\n+ * @private\n+ */\n+mobwrite.clientChange_ = false;\n+\n+\n+/**\n+ * Track whether something changed server-side in each sync.\n+ * @type {boolean}\n+ * @private\n+ */\n+mobwrite.serverChange_ = false;\n+\n+\n+/**\n+ * Temporary object used while each sync is airborne.\n+ * @type {Object?}\n+ * @private\n+ */\n+mobwrite.syncAjaxObj_ = null;\n+\n+\n+/**\n+ * Return a random id that's 8 letters long.\n+ * 26*(26+10+4)^7 = 4,259,840,000,000\n+ * @return {string} Random id.\n+ */\n+mobwrite.uniqueId = function() {\n+  // First character must be a letter.\n+  // IE is case insensitive (in violation of the W3 spec).\n+  var soup = 'abcdefghijklmnopqrstuvwxyz';\n+  var id = soup.charAt(Math.random() * soup.length);\n+  // Subsequent characters may include these.\n+  soup += '0123456789-_:.';\n+  for (var x = 1; x < 8; x++) {\n+    id += soup.charAt(Math.random() * soup.length);\n+  }\n+  // Don't allow IDs with '--' in them since it might close a comment.\n+  if (id.indexOf('--') != -1) {\n+    id = mobwrite.uniqueId();\n+  }\n+  return id;\n+  // Getting the maximum possible density in the ID is worth the extra code,\n+  // since the ID is transmitted to the server a lot.\n+};\n+\n+\n+/**\n+ * Unique ID for this session.\n+ * @type {string}\n+ */\n+mobwrite.syncUsername = mobwrite.uniqueId();\n+\n+\n+/**\n+ * Hash of all shared objects.\n+ * @type {Object}\n+ */\n+mobwrite.shared = {};\n+\n+\n+/**\n+ * Array of registered handlers for sharing types.\n+ * Modules add their share functions to this list.\n+ * @type {Array.<Function>}\n+ */\n+mobwrite.shareHandlers = [];\n+\n+\n+/**\n+ * Prototype of shared object.\n+ * @param {string} id Unique file ID.\n+ * @constructor\n+ */\n+mobwrite.shareObj = function(id) {\n+  if (id) {\n+    this.file = id;\n+    this.dmp = new diff_match_patch();\n+    this.dmp.Diff_Timeout = 0.5;\n+    // List of unacknowledged edits sent to the server.\n+    this.editStack = [];\n+    if (mobwrite.debug) {\n+      window.console.info('Creating shareObj: \"' + id + '\"');\n+    }\n+  }\n+};\n+\n+\n+/**\n+ * Client's understanding of what the server's text looks like.\n+ * @type {string}\n+ */\n+mobwrite.shareObj.prototype.shadowText = '';\n+\n+\n+/**\n+ * The client's version for the shadow (n).\n+ * @type {number}\n+ */\n+mobwrite.shareObj.prototype.clientVersion = 0;\n+\n+\n+/**\n+ * The server's version for the shadow (m).\n+ * @type {number}\n+ */\n+mobwrite.shareObj.prototype.serverVersion = 0;\n+\n+\n+/**\n+ * Did the client understand the server's delta in the previous heartbeat?\n+ * Initialize false because the server and client are out of sync initially.\n+ * @type {boolean}\n+ */\n+mobwrite.shareObj.prototype.deltaOk = false;\n+\n+\n+/**\n+ * Synchronization mode.\n+ * True: Used for text, attempts to gently merge differences together.\n+ * False: Used for numbers, overwrites conflicts, last save wins.\n+ * @type {boolean}\n+ */\n+mobwrite.shareObj.prototype.mergeChanges = true;\n+\n+\n+/**\n+ * Fetch or compute a plaintext representation of the user's text.\n+ * @return {string} Plaintext content.\n+ */\n+mobwrite.shareObj.prototype.getClientText = function() {\n+  window.alert('Defined by subclass');\n+  return '';\n+};\n+\n+\n+/**\n+ * Set the user's text based on the provided plaintext.\n+ * @param {string} text New text.\n+ */\n+mobwrite.shareObj.prototype.setClientText = function(text) {\n+  window.alert('Defined by subclass');\n+};\n+\n+\n+/**\n+ * Modify the user's plaintext by applying a series of patches against it.\n+ * @param {Array.<patch_obj>} patches Array of Patch objects.\n+ */\n+mobwrite.shareObj.prototype.patchClientText = function(patches) {\n+  var oldClientText = this.getClientText();\n+  var result = this.dmp.patch_apply(patches, oldClientText);\n+  // Set the new text only if there is a change to be made.\n+  if (oldClientText != result[0]) {\n+    // The following will probably destroy any cursor or selection.\n+    // Widgets with cursors should override and patch more delicately.\n+    this.setClientText(result[0]);\n+  }\n+};\n+\n+\n+/**\n+ * Notification of when a diff was sent to the server.\n+ * @param {Array.<Array.<*>>} diffs Array of diff tuples.\n+ */\n+mobwrite.shareObj.prototype.onSentDiff = function(diffs) {\n+  // Potential hook for subclass.\n+};\n+\n+\n+/**\n+ * Fire a synthetic 'change' event to a target element.\n+ * Notifies an element that its contents have been changed.\n+ * @param {Object} target Element to notify.\n+ */\n+mobwrite.shareObj.prototype.fireChange = function(target) {\n+  if ('createEvent' in document) {  // W3\n+    var e = document.createEvent('HTMLEvents');\n+    e.initEvent('change', false, false);\n+    target.dispatchEvent(e);\n+  } else if ('fireEvent' in target) { // IE\n+    target.fireEvent('onchange');\n+  }\n+};\n+\n+\n+/**\n+ * Return the command to nullify this field.  Also unshares this field.\n+ * @return {string} Command to be sent to the server.\n+ */\n+mobwrite.shareObj.prototype.nullify = function() {\n+  mobwrite.unshare(this.file);\n+  return 'N:' + mobwrite.idPrefix + this.file + '\\n';\n+};\n+\n+\n+/**\n+ * Asks the shareObj to synchronize.  Computes client-made changes since\n+ * previous postback.  Return '' to skip this synchronization.\n+ * @return {string} Commands to be sent to the server.\n+ */\n+mobwrite.shareObj.prototype.syncText = function() {\n+  var clientText = this.getClientText();\n+  if (this.deltaOk) {\n+    // The last delta postback from the server to this shareObj was successful.\n+    // Send a compressed delta.\n+    var diffs = this.dmp.diff_main(this.shadowText, clientText, true);\n+    if (diffs.length > 2) {\n+      this.dmp.diff_cleanupSemantic(diffs);\n+      this.dmp.diff_cleanupEfficiency(diffs);\n+    }\n+    var changed = diffs.length != 1 || diffs[0][0] != DIFF_EQUAL;\n+    if (changed) {\n+      mobwrite.clientChange_ = true;\n+      this.shadowText = clientText;\n+    }\n+    // Don't bother appending a no-change diff onto the stack if the stack\n+    // already contains something.\n+    if (changed || !this.editStack.length) {\n+      var action = (this.mergeChanges ? 'd:' : 'D:') + this.clientVersion +\n+          ':' + this.dmp.diff_toDelta(diffs);\n+      this.editStack.push([this.clientVersion, action]);\n+      this.clientVersion++;\n+      this.onSentDiff(diffs);\n+    }\n+  } else {\n+    // The last delta postback from the server to this shareObj didn't match.\n+    // Send a full text dump to get back in sync.  This will result in any\n+    // changes since the last postback being wiped out. :(\n+    this.shadowText = clientText;\n+    this.clientVersion++;\n+    var action = 'r:' + this.clientVersion + ':' +\n+                 encodeURI(clientText).replace(/%20/g, ' ');\n+    // Append the action to the edit stack.\n+    this.editStack.push([this.clientVersion, action]);\n+    // Sending a raw dump will put us back in sync.\n+    // Set deltaOk to true in case this sync fails to connect, in which case\n+    // the following sync(s) should be a delta, not more raw dumps.\n+    this.deltaOk = true;\n+  }\n+\n+  // Create the output starting with the file statement, followed by the edits.\n+  var data = 'F:' + this.serverVersion + ':' +\n+      mobwrite.idPrefix + this.file + '\\n';\n+  for (var x = 0; x < this.editStack.length; x++) {\n+    data += this.editStack[x][1] + '\\n';\n+  }\n+  // Opera doesn't know how to encode char 0. (fixed in Opera 9.63)\n+  return data.replace(/\\x00/g, '%00');\n+};\n+\n+\n+/**\n+ * Collect all client-side changes and send them to the server.\n+ * @private\n+ */\n+mobwrite.syncRun1_ = function() {\n+  // Initialize clientChange_, to be checked at the end of syncRun2_.\n+  mobwrite.clientChange_ = false;\n+  var data = [];\n+  data[0] = 'u:' + mobwrite.syncUsername + '\\n';\n+  var empty = true;\n+  // Ask every shared object for their deltas.\n+  for (var x in mobwrite.shared) {\n+    if (mobwrite.shared.hasOwnProperty(x)) {\n+      if (mobwrite.nullifyAll) {\n+        data.push(mobwrite.shared[x].nullify());\n+      } else {\n+        data.push(mobwrite.shared[x].syncText());\n+      }\n+      empty = false;\n+    }\n+  }\n+  if (empty) {\n+    // No sync objects.\n+    if (mobwrite.debug) {\n+      window.console.info('MobWrite task stopped.');\n+    }\n+    return;\n+  }\n+  if (data.length == 1) {\n+    // No sync data.\n+    if (mobwrite.debug) {\n+      window.console.info('All objects silent; null sync.');\n+    }\n+    mobwrite.syncRun2_('\\n\\n');\n+    return;\n+  }\n+\n+  if (mobwrite.debug) {\n+    window.console.info('TO server:\\n' + data.join(''));\n+  }\n+  // Add terminating blank line.\n+  data.push('\\n');\n+  data = data.join('');\n+\n+  // Schedule a watchdog task to catch us if something horrible happens.\n+  mobwrite.syncKillPid_ =\n+      window.setTimeout(mobwrite.syncKill_, mobwrite.timeoutInterval);\n+\n+  // Issue Ajax post of client-side changes and request server-side changes.\n+  data = 'q=' + encodeURIComponent(data);\n+  mobwrite.syncAjaxObj_ = mobwrite.syncLoadAjax_(mobwrite.syncGateway, data,\n+      mobwrite.syncCheckAjax_);\n+  // Execution will resume in either syncCheckAjax_(), or syncKill_()\n+};\n+\n+\n+/**\n+ * Callback location for JSON-P requests.\n+ * @param {string} text Raw content from server.\n+ */\n+mobwrite.callback = function(text) {\n+  // Only process the response if there is a response (don't schedule a new\n+  // heartbeat due to one of the many null responses from a buffer push).\n+  if (text) {\n+    // Add required trailing blank line.\n+    mobwrite.syncRun2_(text + '\\n');\n+  } else {\n+    // This null response proves we got a round-trip of a buffer from the\n+    // server.  Reschedule the watchdog.\n+    window.clearTimeout(mobwrite.syncKillPid_);\n+    mobwrite.syncKillPid_ =\n+        window.setTimeout(mobwrite.syncKill_, mobwrite.timeoutInterval);\n+  }\n+};\n+\n+\n+/**\n+ * Parse all server-side changes and distribute them to the shared objects.\n+ * @param {string} text Raw content from server.\n+ * @private\n+ */\n+mobwrite.syncRun2_ = function(text) {\n+  // Initialize serverChange_, to be checked at the end of syncRun2_.\n+  mobwrite.serverChange_ = false;\n+  if (mobwrite.debug) {\n+    window.console.info('FROM server:\\n' + text);\n+  }\n+  // Opera doesn't know how to decode char 0. (fixed in Opera 9.63)\n+  text = text.replace(/%00/g, '\\0');\n+  // There must be a linefeed followed by a blank line.\n+  if (text.length < 2 || text.substring(text.length - 2) != '\\n\\n') {\n+    text = '';\n+    if (mobwrite.error) {\n+      window.console.info('Truncated data.  Abort.');\n+    }\n+  }\n+  var lines = text.split('\\n');\n+  var file = null;\n+  var clientVersion = null;\n+  for (var i = 0; i < lines.length; i++) {\n+    var line = lines[i];\n+    if (!line) {\n+      // Terminate on blank line.\n+      break;\n+    }\n+    // Divide each line into 'N:value' pairs.\n+    if (line.charAt(1) != ':') {\n+      if (mobwrite.debug) {\n+        window.console.error('Unparsable line: ' + line);\n+      }\n+      continue;\n+    }\n+    var name = line.charAt(0);\n+    var value = line.substring(2);\n+\n+    // Parse out a version number for file, delta or raw.\n+    var version;\n+    if ('FfDdRr'.indexOf(name) != -1) {\n+      var div = value.indexOf(':');\n+      if (div < 1) {\n+        if (mobwrite.debug) {\n+          window.console.error('No version number: ' + line);\n+        }\n+        continue;\n+      }\n+      version = parseInt(value.substring(0, div), 10);\n+      if (isNaN(version)) {\n+        if (mobwrite.debug) {\n+          window.console.error('NaN version number: ' + line);\n+        }\n+        continue;\n+      }\n+      value = value.substring(div + 1);\n+    }\n+    if (name == 'F' || name == 'f') {\n+      // File indicates which shared object following delta/raw applies to.\n+      if (value.substring(0, mobwrite.idPrefix.length) == mobwrite.idPrefix) {\n+        // Trim off the ID prefix.\n+        value = value.substring(mobwrite.idPrefix.length);\n+      } else {\n+        // This file does not have our ID prefix.\n+        file = null;\n+        if (mobwrite.debug) {\n+          window.console.error('File does not have \"' + mobwrite.idPrefix +\n+              '\" prefix: ' + value);\n+        }\n+        continue;\n+      }\n+      if (mobwrite.shared.hasOwnProperty(value)) {\n+        file = mobwrite.shared[value];\n+        file.deltaOk = true;\n+        clientVersion = version;\n+        // Remove any elements from the edit stack with low version numbers\n+        // which have been acked by the server.\n+        for (var x = 0; x < file.editStack.length; x++) {\n+          if (file.editStack[x][0] <= clientVersion) {\n+            file.editStack.splice(x, 1);\n+            x--;\n+          }\n+        }\n+\n+      } else {\n+        // This file does not map to a currently shared object.\n+        file = null;\n+        if (mobwrite.debug) {\n+          window.console.error('Unknown file: ' + value);\n+        }\n+      }\n+    } else if (name == 'R' || name == 'r') {\n+      // The server reports it was unable to integrate the previous delta.\n+      if (file) {\n+        file.shadowText = decodeURI(value);\n+        file.clientVersion = clientVersion;\n+        file.serverVersion = version;\n+        file.editStack = [];\n+        if (name == 'R') {\n+          // Accept the server's raw text dump and wipe out any user's changes.\n+          file.setClientText(file.shadowText);\n+        }\n+        // Server-side activity.\n+        mobwrite.serverChange_ = true;\n+      }\n+    } else if (name == 'D' || name == 'd') {\n+      // The server offers a compressed delta of changes to be applied.\n+      if (file) {\n+        if (clientVersion != file.clientVersion) {\n+          // Can't apply a delta on a mismatched shadow version.\n+          file.deltaOk = false;\n+          if (mobwrite.debug) {\n+            window.console.error('Client version number mismatch.\\n' +\n+                'Expected: ' + file.clientVersion + ' Got: ' + clientVersion);\n+          }\n+        } else if (version > file.serverVersion) {\n+          // Server has a version in the future?\n+          file.deltaOk = false;\n+          if (mobwrite.debug) {\n+            window.console.error('Server version in future.\\n' +\n+                'Expected: ' + file.serverVersion + ' Got: ' + version);\n+          }\n+        } else if (version < file.serverVersion) {\n+          // We've already seen this diff.\n+          if (mobwrite.debug) {\n+            window.console.warn('Server version in past.\\n' +\n+                'Expected: ' + file.serverVersion + ' Got: ' + version);\n+          }\n+        } else {\n+          // Expand the delta into a diff using the client shadow.\n+          var diffs;\n+          try {\n+            diffs = file.dmp.diff_fromDelta(file.shadowText, value);\n+            file.serverVersion++;\n+          } catch (ex) {\n+            // The delta the server supplied does not fit on our copy of\n+            // shadowText.\n+            diffs = null;\n+            // Set deltaOk to false so that on the next sync we send\n+            // a complete dump to get back in sync.\n+            file.deltaOk = false;\n+            // Do the next sync soon because the user will lose any changes.\n+            mobwrite.syncInterval = 0;\n+            if (mobwrite.debug) {\n+              window.console.error('Delta mismatch.\\n' + encodeURI(file.shadowText));\n+            }\n+          }\n+          if (diffs && (diffs.length != 1 || diffs[0][0] != DIFF_EQUAL)) {\n+            // Compute and apply the patches.\n+            if (name == 'D') {\n+              // Overwrite text.\n+              file.shadowText = file.dmp.diff_text2(diffs);\n+              file.setClientText(file.shadowText);\n+            } else {\n+              // Merge text.\n+              var patches = file.dmp.patch_make(file.shadowText, diffs);\n+              // First shadowText.  Should be guaranteed to work.\n+              var serverResult = file.dmp.patch_apply(patches, file.shadowText);\n+              file.shadowText = serverResult[0];\n+              // Second the user's text.\n+              file.patchClientText(patches);\n+            }\n+            // Server-side activity.\n+            mobwrite.serverChange_ = true;\n+          }\n+        }\n+      }\n+    }\n+  }\n+\n+  mobwrite.computeSyncInterval_();\n+\n+  // Ensure that there is only one sync task.\n+  window.clearTimeout(mobwrite.syncRunPid_);\n+  // Schedule the next sync.\n+  mobwrite.syncRunPid_ =\n+      window.setTimeout(mobwrite.syncRun1_, mobwrite.syncInterval);\n+  // Terminate the watchdog task, everything's ok.\n+  window.clearTimeout(mobwrite.syncKillPid_);\n+  mobwrite.syncKillPid_ = null;\n+};\n+\n+\n+/**\n+ * Compute how long to wait until next synchronization.\n+ * @private\n+ */\n+mobwrite.computeSyncInterval_ = function() {\n+  var range = mobwrite.maxSyncInterval - mobwrite.minSyncInterval;\n+  if (mobwrite.clientChange_) {\n+    // Client-side activity.\n+    // Cut the sync interval by 40% of the min-max range.\n+    mobwrite.syncInterval -= range * 0.4;\n+  }\n+  if (mobwrite.serverChange_) {\n+    // Server-side activity.\n+    // Cut the sync interval by 20% of the min-max range.\n+    mobwrite.syncInterval -= range * 0.2;\n+  }\n+  if (!mobwrite.clientChange_ && !mobwrite.serverChange_) {\n+    // No activity.\n+    // Let the sync interval creep up by 10% of the min-max range.\n+    mobwrite.syncInterval += range * 0.1;\n+  }\n+  // Keep the sync interval constrained between min and max.\n+  mobwrite.syncInterval =\n+      Math.max(mobwrite.minSyncInterval, mobwrite.syncInterval);\n+  mobwrite.syncInterval =\n+      Math.min(mobwrite.maxSyncInterval, mobwrite.syncInterval);\n+};\n+\n+\n+/**\n+ * If the Ajax call doesn't complete after a timeout period, start over.\n+ * @private\n+ */\n+mobwrite.syncKill_ = function() {\n+  mobwrite.syncKillPid_ = null;\n+  if (mobwrite.syncAjaxObj_) {\n+    // Cleanup old Ajax connection.\n+    mobwrite.syncAjaxObj_.abort();\n+    mobwrite.syncAjaxObj_ = null;\n+  }\n+  if (mobwrite.debug) {\n+    window.console.warn('Connection timeout.');\n+  }\n+  window.clearTimeout(mobwrite.syncRunPid_);\n+  // Initiate a new sync right now.\n+  mobwrite.syncRunPid_ = window.setTimeout(mobwrite.syncRun1_, 1);\n+};\n+\n+\n+/**\n+ * Initiate an Ajax network connection.\n+ * @param {string} url Location to send request.\n+ * @param {string} post Data to be sent.\n+ * @param {Function} callback Function to be called when response arrives.\n+ * @return {Object?} New Ajax object or null if failure.\n+ * @private\n+ */\n+mobwrite.syncLoadAjax_ = function(url, post, callback) {\n+  var req = null;\n+  // branch for native XMLHttpRequest object\n+  if (window.XMLHttpRequest) {\n+    try {\n+      req = new XMLHttpRequest();\n+    } catch(e1) {\n+      req = null;","path":"static/code/mobwrite/mobwrite_core.js","position":671,"original_position":683,"commit_id":"68df13c1fae8388157017e3fadb77f6b16c483d5","original_commit_id":"0d4a1888c4a3143b7b47457b6ef2e15d9ae62115","user":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"body":"Threw out all IE 6 support.","created_at":"2018-07-04T00:56:53Z","updated_at":"2018-07-04T00:56:53Z","html_url":"https://github.com/NeilFraser/CodeCity/pull/198#discussion_r199985490","pull_request_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/comments/199985490"},"html":{"href":"https://github.com/NeilFraser/CodeCity/pull/198#discussion_r199985490"},"pull_request":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198"}},"in_reply_to_id":199653932},"pull_request":{"url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198","id":197879345,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk3ODc5MzQ1","html_url":"https://github.com/NeilFraser/CodeCity/pull/198","diff_url":"https://github.com/NeilFraser/CodeCity/pull/198.diff","patch_url":"https://github.com/NeilFraser/CodeCity/pull/198.patch","issue_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/198","number":198,"state":"open","locked":false,"title":"Mobwrite","user":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-06-27T23:24:08Z","updated_at":"2018-07-04T00:56:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a07155f9c3df730b6c86e784f461f5b441e88eae","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198/commits","review_comments_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198/comments","review_comment_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/comments{/number}","comments_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/198/comments","statuses_url":"https://api.github.com/repos/NeilFraser/CodeCity/statuses/68df13c1fae8388157017e3fadb77f6b16c483d5","head":{"label":"NeilFraser:mobwrite","ref":"mobwrite","sha":"68df13c1fae8388157017e3fadb77f6b16c483d5","user":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"repo":{"id":75521723,"node_id":"MDEwOlJlcG9zaXRvcnk3NTUyMTcyMw==","name":"CodeCity","full_name":"NeilFraser/CodeCity","owner":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/NeilFraser/CodeCity","description":null,"fork":false,"url":"https://api.github.com/repos/NeilFraser/CodeCity","forks_url":"https://api.github.com/repos/NeilFraser/CodeCity/forks","keys_url":"https://api.github.com/repos/NeilFraser/CodeCity/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NeilFraser/CodeCity/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NeilFraser/CodeCity/teams","hooks_url":"https://api.github.com/repos/NeilFraser/CodeCity/hooks","issue_events_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/events{/number}","events_url":"https://api.github.com/repos/NeilFraser/CodeCity/events","assignees_url":"https://api.github.com/repos/NeilFraser/CodeCity/assignees{/user}","branches_url":"https://api.github.com/repos/NeilFraser/CodeCity/branches{/branch}","tags_url":"https://api.github.com/repos/NeilFraser/CodeCity/tags","blobs_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/refs{/sha}","trees_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NeilFraser/CodeCity/statuses/{sha}","languages_url":"https://api.github.com/repos/NeilFraser/CodeCity/languages","stargazers_url":"https://api.github.com/repos/NeilFraser/CodeCity/stargazers","contributors_url":"https://api.github.com/repos/NeilFraser/CodeCity/contributors","subscribers_url":"https://api.github.com/repos/NeilFraser/CodeCity/subscribers","subscription_url":"https://api.github.com/repos/NeilFraser/CodeCity/subscription","commits_url":"https://api.github.com/repos/NeilFraser/CodeCity/commits{/sha}","git_commits_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/commits{/sha}","comments_url":"https://api.github.com/repos/NeilFraser/CodeCity/comments{/number}","issue_comment_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/comments{/number}","contents_url":"https://api.github.com/repos/NeilFraser/CodeCity/contents/{+path}","compare_url":"https://api.github.com/repos/NeilFraser/CodeCity/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NeilFraser/CodeCity/merges","archive_url":"https://api.github.com/repos/NeilFraser/CodeCity/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NeilFraser/CodeCity/downloads","issues_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues{/number}","pulls_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls{/number}","milestones_url":"https://api.github.com/repos/NeilFraser/CodeCity/milestones{/number}","notifications_url":"https://api.github.com/repos/NeilFraser/CodeCity/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NeilFraser/CodeCity/labels{/name}","releases_url":"https://api.github.com/repos/NeilFraser/CodeCity/releases{/id}","deployments_url":"https://api.github.com/repos/NeilFraser/CodeCity/deployments","created_at":"2016-12-04T06:36:37Z","updated_at":"2018-06-19T18:23:46Z","pushed_at":"2018-06-30T03:49:11Z","git_url":"git://github.com/NeilFraser/CodeCity.git","ssh_url":"git@github.com:NeilFraser/CodeCity.git","clone_url":"https://github.com/NeilFraser/CodeCity.git","svn_url":"https://github.com/NeilFraser/CodeCity","homepage":null,"size":5288,"stargazers_count":3,"watchers_count":3,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":11,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":11,"watchers":3,"default_branch":"master"}},"base":{"label":"NeilFraser:master","ref":"master","sha":"a47613e4c3c6e31faab25e3989745fda8aa9a501","user":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"repo":{"id":75521723,"node_id":"MDEwOlJlcG9zaXRvcnk3NTUyMTcyMw==","name":"CodeCity","full_name":"NeilFraser/CodeCity","owner":{"login":"NeilFraser","id":250480,"node_id":"MDQ6VXNlcjI1MDQ4MA==","avatar_url":"https://avatars3.githubusercontent.com/u/250480?v=4","gravatar_id":"","url":"https://api.github.com/users/NeilFraser","html_url":"https://github.com/NeilFraser","followers_url":"https://api.github.com/users/NeilFraser/followers","following_url":"https://api.github.com/users/NeilFraser/following{/other_user}","gists_url":"https://api.github.com/users/NeilFraser/gists{/gist_id}","starred_url":"https://api.github.com/users/NeilFraser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NeilFraser/subscriptions","organizations_url":"https://api.github.com/users/NeilFraser/orgs","repos_url":"https://api.github.com/users/NeilFraser/repos","events_url":"https://api.github.com/users/NeilFraser/events{/privacy}","received_events_url":"https://api.github.com/users/NeilFraser/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/NeilFraser/CodeCity","description":null,"fork":false,"url":"https://api.github.com/repos/NeilFraser/CodeCity","forks_url":"https://api.github.com/repos/NeilFraser/CodeCity/forks","keys_url":"https://api.github.com/repos/NeilFraser/CodeCity/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NeilFraser/CodeCity/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NeilFraser/CodeCity/teams","hooks_url":"https://api.github.com/repos/NeilFraser/CodeCity/hooks","issue_events_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/events{/number}","events_url":"https://api.github.com/repos/NeilFraser/CodeCity/events","assignees_url":"https://api.github.com/repos/NeilFraser/CodeCity/assignees{/user}","branches_url":"https://api.github.com/repos/NeilFraser/CodeCity/branches{/branch}","tags_url":"https://api.github.com/repos/NeilFraser/CodeCity/tags","blobs_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/refs{/sha}","trees_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NeilFraser/CodeCity/statuses/{sha}","languages_url":"https://api.github.com/repos/NeilFraser/CodeCity/languages","stargazers_url":"https://api.github.com/repos/NeilFraser/CodeCity/stargazers","contributors_url":"https://api.github.com/repos/NeilFraser/CodeCity/contributors","subscribers_url":"https://api.github.com/repos/NeilFraser/CodeCity/subscribers","subscription_url":"https://api.github.com/repos/NeilFraser/CodeCity/subscription","commits_url":"https://api.github.com/repos/NeilFraser/CodeCity/commits{/sha}","git_commits_url":"https://api.github.com/repos/NeilFraser/CodeCity/git/commits{/sha}","comments_url":"https://api.github.com/repos/NeilFraser/CodeCity/comments{/number}","issue_comment_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues/comments{/number}","contents_url":"https://api.github.com/repos/NeilFraser/CodeCity/contents/{+path}","compare_url":"https://api.github.com/repos/NeilFraser/CodeCity/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NeilFraser/CodeCity/merges","archive_url":"https://api.github.com/repos/NeilFraser/CodeCity/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NeilFraser/CodeCity/downloads","issues_url":"https://api.github.com/repos/NeilFraser/CodeCity/issues{/number}","pulls_url":"https://api.github.com/repos/NeilFraser/CodeCity/pulls{/number}","milestones_url":"https://api.github.com/repos/NeilFraser/CodeCity/milestones{/number}","notifications_url":"https://api.github.com/repos/NeilFraser/CodeCity/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NeilFraser/CodeCity/labels{/name}","releases_url":"https://api.github.com/repos/NeilFraser/CodeCity/releases{/id}","deployments_url":"https://api.github.com/repos/NeilFraser/CodeCity/deployments","created_at":"2016-12-04T06:36:37Z","updated_at":"2018-06-19T18:23:46Z","pushed_at":"2018-06-30T03:49:11Z","git_url":"git://github.com/NeilFraser/CodeCity.git","ssh_url":"git@github.com:NeilFraser/CodeCity.git","clone_url":"https://github.com/NeilFraser/CodeCity.git","svn_url":"https://github.com/NeilFraser/CodeCity","homepage":null,"size":5288,"stargazers_count":3,"watchers_count":3,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":11,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":11,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198"},"html":{"href":"https://github.com/NeilFraser/CodeCity/pull/198"},"issue":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/issues/198"},"comments":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/issues/198/comments"},"review_comments":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198/comments"},"review_comment":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/pulls/198/commits"},"statuses":{"href":"https://api.github.com/repos/NeilFraser/CodeCity/statuses/68df13c1fae8388157017e3fadb77f6b16c483d5"}},"author_association":"OWNER"}}
{"actor":{"display_login":"NeilFraser"}}