From 8e8bbb00f5a0524873b5a76c833c8a9d553c0327 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 11:22:47 +0000
Subject: SYWEB-12: Store unknown state events so they are displayed in the
 Room Info dialog.

---
 webclient/components/matrix/event-handler-service.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
index 3b1354cdef..6f251eec56 100644
--- a/webclient/components/matrix/event-handler-service.js
+++ b/webclient/components/matrix/event-handler-service.js
@@ -564,6 +564,13 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
                         handleRedaction(event, isLiveEvent);
                         break;
                     default:
+                        // if it is a state event, then just add it in so it
+                        // displays on the Room Info screen.
+                        if (typeof(event.state_key) === "string") { // incls. 0-len strings
+                            if (event.room_id) {
+                                handleRoomDateEvent(event, isLiveEvent, false);
+                            }
+                        }
                         console.log("Unable to handle event type " + event.type);
                         console.log(JSON.stringify(event, undefined, 4));
                         break;
-- 
cgit 1.5.1


From 40342af4597c5f52815629836bc37b4798c1d232 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 11:53:28 +0000
Subject: SYWEB-12: Format room info dialog better.

---
 webclient/app.css        | 25 +++++++++++++++++++++++++
 webclient/room/room.html | 30 +++++++++++++-----------------
 2 files changed, 38 insertions(+), 17 deletions(-)

diff --git a/webclient/app.css b/webclient/app.css
index 20a13aad81..2dc6e8caed 100755
--- a/webclient/app.css
+++ b/webclient/app.css
@@ -418,6 +418,31 @@ textarea, input {
     margin-top: 15px;
 }
 
+/*** Room Info Dialog ***/
+
+.room-info {
+    border-collapse: collapse;
+    width: 100%;
+}
+
+.room-info-event {
+    border-bottom: 1pt solid black;
+}
+
+.room-info-event-meta {
+    padding-top: 1em;
+    padding-bottom: 1em;
+}
+
+.room-info-event-content {
+    padding-top: 1em;
+    padding-bottom: 1em;
+}
+
+.monospace {
+    font-family: monospace;
+}
+
 /*** Participant list ***/
 
 #usersTableWrapper {
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 3458e97039..65b959fe94 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -15,23 +15,19 @@
 
     <script type="text/ng-template" id="roomInfoTemplate.html">
         <div class="modal-body">
-            <table id="roomInfoTable">
-                <tr>
-                <th>
-                    Event Type
-                </th>
-                <th>
-                    Content
-                </th>
-                </tr>
-                <tr ng-repeat="(key, event) in events.rooms[room_id] | stateEventsFilter">
-                    <td>
-                        <pre>{{ key }}</pre>
-                    </td>
-                    <td>
-                        <pre>{{ event.content | json }}</pre>
-                    </td>
-                </tr>
+            <table class="room-info">
+            <tr ng-repeat="(key, event) in events.rooms[room_id] | stateEventsFilter" class="room-info-event">
+                <td class="room-info-event-meta">
+                    <span class="monospace">{{ key }}</span>
+                    <br/>
+                    {{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
+                    <br/>
+                    Set by: <span class="monospace">{{ event.user_id }}</span>
+                </td>
+                <td class="room-info-event-content">
+                    <pre>{{ event.content | json }}</pre>
+                </td>
+            </tr>
             </table>
         </div>
         <div class="modal-footer">
-- 
cgit 1.5.1


From 6f3f631fd11f744afa57396e8dd18e064ce90e8f Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 13:24:40 +0000
Subject: SYWEB-12: More formatting.

---
 webclient/app-directive.js        | 17 ++++++++++++++++-
 webclient/app.css                 |  6 ++++++
 webclient/room/room-controller.js |  6 +++---
 webclient/room/room.html          | 10 +++++++---
 4 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/webclient/app-directive.js b/webclient/app-directive.js
index 75283598ab..d788475f46 100644
--- a/webclient/app-directive.js
+++ b/webclient/app-directive.js
@@ -40,4 +40,19 @@ angular.module('matrixWebClient')
             }            
         }
     };
-}]);
\ No newline at end of file
+}])
+.directive('elastic', [ // http://stackoverflow.com/questions/17772260/textarea-auto-height
+    '$timeout',
+    function($timeout) {
+      return {
+        restrict: 'A',
+        link: function($scope, element) {
+          var resize = function() {
+            return element[0].style.height = "" + element[0].scrollHeight + "px";
+          };
+          element.on("blur keyup change", resize);
+          $timeout(resize, 10);
+        }
+      };
+    }
+]);;
diff --git a/webclient/app.css b/webclient/app.css
index 2dc6e8caed..5ab8e2b8fd 100755
--- a/webclient/app.css
+++ b/webclient/app.css
@@ -443,6 +443,12 @@ textarea, input {
     font-family: monospace;
 }
 
+.room-info-textarea-content {
+    height: auto;
+    width: 100%;
+    resize: vertical;
+}
+
 /*** Participant list ***/
 
 #usersTableWrapper {
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index 37f51c4e91..fcbcd75364 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -1039,9 +1039,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
 .controller('RoomInfoController', function($scope, $modalInstance, $filter) {
     console.log("Displaying room info.");
 
-    $scope.submitState = function(eventType, content) {
-        console.log("Submitting " + eventType + " with " + content);
-    }
+    $scope.submit = function(event) {
+        console.error("submit >>> " + JSON.stringify(event));
+    };
 
     $scope.dismiss = $modalInstance.dismiss;
 
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 65b959fe94..cc79d6b778 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -17,15 +17,19 @@
         <div class="modal-body">
             <table class="room-info">
             <tr ng-repeat="(key, event) in events.rooms[room_id] | stateEventsFilter" class="room-info-event">
-                <td class="room-info-event-meta">
+                <td class="room-info-event-meta" width="30%">
                     <span class="monospace">{{ key }}</span>
                     <br/>
                     {{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
                     <br/>
                     Set by: <span class="monospace">{{ event.user_id }}</span>
+                    <br/>
+                    <button ng-click="submit(event)" type="button" class="btn btn-success">
+                        Submit
+                    </button>
                 </td>
-                <td class="room-info-event-content">
-                    <pre>{{ event.content | json }}</pre>
+                <td class="room-info-event-content" width="70%">
+                    <textarea class="room-info-textarea-content" elastic ng-model="event.content | json"></textarea> 
                 </td>
             </tr>
             </table>
-- 
cgit 1.5.1


From 9de9661baaa232da170e45534a7d335bf96ef606 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 16:21:27 +0000
Subject: SYWEB-12: More formatting and tweaking of state event JSON.

Use a proper elastic directive to make the <textarea> resize dynamically.
Use an 'asjson' directive to turn an ngModel of a JSON object into a
formatted JSON string so it can be displayed on the textarea. Also, deep
copy the state events being displayed, else it actually alters the underlying
data structures when playing around with the JSON in the textarea!
---
 webclient/app-directive.js        | 50 +++++++++++++++++++++++++++++----------
 webclient/app.js                  |  3 ++-
 webclient/index.html              |  1 +
 webclient/room/room-controller.js | 11 ++++++++-
 webclient/room/room.html          |  4 ++--
 5 files changed, 53 insertions(+), 16 deletions(-)

diff --git a/webclient/app-directive.js b/webclient/app-directive.js
index d788475f46..c1ba0af3a9 100644
--- a/webclient/app-directive.js
+++ b/webclient/app-directive.js
@@ -41,18 +41,44 @@ angular.module('matrixWebClient')
         }
     };
 }])
-.directive('elastic', [ // http://stackoverflow.com/questions/17772260/textarea-auto-height
-    '$timeout',
-    function($timeout) {
-      return {
+.directive('asjson', function() {
+    return {
         restrict: 'A',
-        link: function($scope, element) {
-          var resize = function() {
-            return element[0].style.height = "" + element[0].scrollHeight + "px";
-          };
-          element.on("blur keyup change", resize);
-          $timeout(resize, 10);
+        require: 'ngModel',
+        link: function (scope, element, attrs, ngModelCtrl) {
+            function isValidJson(model) {
+                var flag = true;
+                try {
+                    angular.fromJson(model);
+                } catch (err) {
+                    flag = false;
+                }
+                return flag;
+            };
+
+            function string2JSON(text) {
+                try {
+                    var j = angular.fromJson(text);
+                    ngModelCtrl.$setValidity('json', true);
+                    return j;
+                } catch (err) {
+                    //returning undefined results in a parser error as of angular-1.3-rc.0, and will not go through $validators
+                    //return undefined
+                    ngModelCtrl.$setValidity('json', false);
+                    return text;
+                }
+            };
+
+            function JSON2String(object) {
+                return angular.toJson(object, true);
+            };
+
+            //$validators is an object, where key is the error
+            //ngModelCtrl.$validators.json = isValidJson;
+
+            //array pipelines
+            ngModelCtrl.$parsers.push(string2JSON);
+            ngModelCtrl.$formatters.push(JSON2String);
         }
-      };
     }
-]);;
+});
diff --git a/webclient/app.js b/webclient/app.js
index 099e2170a0..8d9b662ee9 100644
--- a/webclient/app.js
+++ b/webclient/app.js
@@ -31,7 +31,8 @@ var matrixWebClient = angular.module('matrixWebClient', [
     'eventStreamService',
     'eventHandlerService',
     'infinite-scroll',
-    'ui.bootstrap'
+    'ui.bootstrap',
+    'monospaced.elastic'
 ]);
 
 matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
diff --git a/webclient/index.html b/webclient/index.html
index 35c8051298..d8b9c95353 100644
--- a/webclient/index.html
+++ b/webclient/index.html
@@ -20,6 +20,7 @@
     <script type='text/javascript' src="js/ui-bootstrap-tpls-0.11.2.js"></script>
     <script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script>
     <script type='text/javascript' src='js/autofill-event.js'></script>
+    <script type='text/javascript' src='js/elastic.js'></script>
     <script src="app.js"></script>
     <script src="config.js"></script>
     <script src="app-controller.js"></script>
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index fcbcd75364..ce513990f2 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -1018,6 +1018,13 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
     };
 
     $scope.openRoomInfo = function() {
+        var stateFilter = $filter("stateEventsFilter");
+        var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]);
+        // The modal dialog will 2-way bind this field, so we MUST make a deep
+        // copy of the state events else we will be *actually adjusing our view
+        // of the world* when fiddling with the JSON!! Apparently parse/stringify
+        // is faster than jQuery's extend when doing deep copies.
+        $scope.roomInfoStateEvents = JSON.parse(JSON.stringify(stateEvents));
         var modalInstance = $modal.open({
             templateUrl: 'roomInfoTemplate.html',
             controller: 'RoomInfoController',
@@ -1040,7 +1047,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
     console.log("Displaying room info.");
 
     $scope.submit = function(event) {
-        console.error("submit >>> " + JSON.stringify(event));
+        if (event.content) {
+            console.error("submit >>> " + JSON.stringify(event));
+        }
     };
 
     $scope.dismiss = $modalInstance.dismiss;
diff --git a/webclient/room/room.html b/webclient/room/room.html
index cc79d6b778..627918223a 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -16,7 +16,7 @@
     <script type="text/ng-template" id="roomInfoTemplate.html">
         <div class="modal-body">
             <table class="room-info">
-            <tr ng-repeat="(key, event) in events.rooms[room_id] | stateEventsFilter" class="room-info-event">
+            <tr ng-repeat="(key, event) in roomInfoStateEvents" class="room-info-event">
                 <td class="room-info-event-meta" width="30%">
                     <span class="monospace">{{ key }}</span>
                     <br/>
@@ -29,7 +29,7 @@
                     </button>
                 </td>
                 <td class="room-info-event-content" width="70%">
-                    <textarea class="room-info-textarea-content" elastic ng-model="event.content | json"></textarea> 
+                    <textarea class="room-info-textarea-content" msd-elastic ng-model="event.content" asjson></textarea> 
                 </td>
             </tr>
             </table>
-- 
cgit 1.5.1


From 0985bfb77584ea77c60c4bf8b3e95c104e2fa3f5 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 16:31:47 +0000
Subject: SYWEB-12: Allow edited state events to be submitted.

---
 webclient/room/room-controller.js | 11 +++++++++--
 webclient/room/room.html          |  2 +-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index ce513990f2..df93ccf5b8 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -1043,12 +1043,19 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
         $modalInstance.close("redact");
     };
 })
-.controller('RoomInfoController', function($scope, $modalInstance, $filter) {
+.controller('RoomInfoController', function($scope, $modalInstance, $filter, matrixService) {
     console.log("Displaying room info.");
 
     $scope.submit = function(event) {
         if (event.content) {
-            console.error("submit >>> " + JSON.stringify(event));
+            console.log("submit >>> " + JSON.stringify(event.content));
+            matrixService.sendStateEvent($scope.room_id, event.type, 
+                event.content, event.state_key).then(function(response) {
+                    $modalInstance.dismiss();
+                }, function(err) {
+                    $scope.feedback = err.data.error;
+                }
+            );
         }
     };
 
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 627918223a..16c35c032c 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -24,7 +24,7 @@
                     <br/>
                     Set by: <span class="monospace">{{ event.user_id }}</span>
                     <br/>
-                    <button ng-click="submit(event)" type="button" class="btn btn-success">
+                    <button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content">
                         Submit
                     </button>
                 </td>
-- 
cgit 1.5.1


From f618f99ecea1c200b10e11de7b6fc64fcd1b7e78 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 17:01:17 +0000
Subject: SYWEB-12: Add ability to add new state events.

---
 webclient/room/room-controller.js |  9 ++++++++-
 webclient/room/room.html          | 14 +++++++++++++-
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index df93ccf5b8..59274baccb 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -1018,13 +1018,20 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
     };
 
     $scope.openRoomInfo = function() {
+        $scope.roomInfo = {};
+        $scope.roomInfo.newEvent = {
+            content: {},
+            type: "",
+            state_key: ""
+        };
+
         var stateFilter = $filter("stateEventsFilter");
         var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]);
         // The modal dialog will 2-way bind this field, so we MUST make a deep
         // copy of the state events else we will be *actually adjusing our view
         // of the world* when fiddling with the JSON!! Apparently parse/stringify
         // is faster than jQuery's extend when doing deep copies.
-        $scope.roomInfoStateEvents = JSON.parse(JSON.stringify(stateEvents));
+        $scope.roomInfo.stateEvents = JSON.parse(JSON.stringify(stateEvents));
         var modalInstance = $modal.open({
             templateUrl: 'roomInfoTemplate.html',
             controller: 'RoomInfoController',
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 16c35c032c..6c0db6d7d3 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -16,7 +16,7 @@
     <script type="text/ng-template" id="roomInfoTemplate.html">
         <div class="modal-body">
             <table class="room-info">
-            <tr ng-repeat="(key, event) in roomInfoStateEvents" class="room-info-event">
+            <tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event">
                 <td class="room-info-event-meta" width="30%">
                     <span class="monospace">{{ key }}</span>
                     <br/>
@@ -32,6 +32,18 @@
                     <textarea class="room-info-textarea-content" msd-elastic ng-model="event.content" asjson></textarea> 
                 </td>
             </tr>
+            <tr>
+                <td class="room-info-event-meta" width="30%">
+                    <input ng-model="roomInfo.newEvent.type" placeholder="your.event.type" />
+                    <br/>
+                    <button ng-click="submit(roomInfo.newEvent)" type="button" class="btn btn-success" ng-disabled="!roomInfo.newEvent.content">
+                        Submit
+                    </button>
+                </td>
+                <td class="room-info-event-content" width="70%">
+                    <textarea class="room-info-textarea-content" msd-elastic ng-model="roomInfo.newEvent.content" asjson></textarea>
+                </td>
+            </tr>
             </table>
         </div>
         <div class="modal-footer">
-- 
cgit 1.5.1


From ea6bec96d3fc6c05cd85bfd395acf697a0d8288e Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 17:16:16 +0000
Subject: SYWEB-12: UX tweaks.

---
 webclient/room/room.html | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/webclient/room/room.html b/webclient/room/room.html
index 6c0db6d7d3..19ed802f0c 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -24,6 +24,7 @@
                     <br/>
                     Set by: <span class="monospace">{{ event.user_id }}</span>
                     <br/>
+                    <span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}</span>
                     <button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content">
                         Submit
                     </button>
@@ -36,7 +37,7 @@
                 <td class="room-info-event-meta" width="30%">
                     <input ng-model="roomInfo.newEvent.type" placeholder="your.event.type" />
                     <br/>
-                    <button ng-click="submit(roomInfo.newEvent)" type="button" class="btn btn-success" ng-disabled="!roomInfo.newEvent.content">
+                    <button ng-click="submit(roomInfo.newEvent)" type="button" class="btn btn-success" ng-disabled="!roomInfo.newEvent.content || !roomInfo.newEvent.type">
                         Submit
                     </button>
                 </td>
-- 
cgit 1.5.1


From 864de6a7a4d2b7e95715ac63851e00c4644505e2 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 17:23:11 +0000
Subject: SYWEB-12: Minor layout tweaks.

---
 webclient/room/room.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/webclient/room/room.html b/webclient/room/room.html
index 19ed802f0c..fac7433a4b 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -24,7 +24,7 @@
                     <br/>
                     Set by: <span class="monospace">{{ event.user_id }}</span>
                     <br/>
-                    <span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}</span>
+                    <span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}<br/></span>
                     <button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content">
                         Submit
                     </button>
-- 
cgit 1.5.1


From 86d3180666ca6a14ac3934c9ce0cecae001f4712 Mon Sep 17 00:00:00 2001
From: Kegan Dougal <kegan@matrix.org>
Date: Thu, 30 Oct 2014 17:33:14 +0000
Subject: SYWEB-12: You'll be needing this.

---
 webclient/js/elastic.js | 216 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 216 insertions(+)
 create mode 100644 webclient/js/elastic.js

diff --git a/webclient/js/elastic.js b/webclient/js/elastic.js
new file mode 100644
index 0000000000..d585d81109
--- /dev/null
+++ b/webclient/js/elastic.js
@@ -0,0 +1,216 @@
+/*
+ * angular-elastic v2.4.0
+ * (c) 2014 Monospaced http://monospaced.com
+ * License: MIT
+ */
+
+angular.module('monospaced.elastic', [])
+
+  .constant('msdElasticConfig', {
+    append: ''
+  })
+
+  .directive('msdElastic', [
+    '$timeout', '$window', 'msdElasticConfig',
+    function($timeout, $window, config) {
+      'use strict';
+
+      return {
+        require: 'ngModel',
+        restrict: 'A, C',
+        link: function(scope, element, attrs, ngModel) {
+
+          // cache a reference to the DOM element
+          var ta = element[0],
+              $ta = element;
+
+          // ensure the element is a textarea, and browser is capable
+          if (ta.nodeName !== 'TEXTAREA' || !$window.getComputedStyle) {
+            return;
+          }
+
+          // set these properties before measuring dimensions
+          $ta.css({
+            'overflow': 'hidden',
+            'overflow-y': 'hidden',
+            'word-wrap': 'break-word'
+          });
+
+          // force text reflow
+          var text = ta.value;
+          ta.value = '';
+          ta.value = text;
+
+          var append = attrs.msdElastic ? attrs.msdElastic.replace(/\\n/g, '\n') : config.append,
+              $win = angular.element($window),
+              mirrorInitStyle = 'position: absolute; top: -999px; right: auto; bottom: auto;' +
+                                'left: 0; overflow: hidden; -webkit-box-sizing: content-box;' +
+                                '-moz-box-sizing: content-box; box-sizing: content-box;' +
+                                'min-height: 0 !important; height: 0 !important; padding: 0;' +
+                                'word-wrap: break-word; border: 0;',
+              $mirror = angular.element('<textarea tabindex="-1" ' +
+                                        'style="' + mirrorInitStyle + '"/>').data('elastic', true),
+              mirror = $mirror[0],
+              taStyle = getComputedStyle(ta),
+              resize = taStyle.getPropertyValue('resize'),
+              borderBox = taStyle.getPropertyValue('box-sizing') === 'border-box' ||
+                          taStyle.getPropertyValue('-moz-box-sizing') === 'border-box' ||
+                          taStyle.getPropertyValue('-webkit-box-sizing') === 'border-box',
+              boxOuter = !borderBox ? {width: 0, height: 0} : {
+                            width:  parseInt(taStyle.getPropertyValue('border-right-width'), 10) +
+                                    parseInt(taStyle.getPropertyValue('padding-right'), 10) +
+                                    parseInt(taStyle.getPropertyValue('padding-left'), 10) +
+                                    parseInt(taStyle.getPropertyValue('border-left-width'), 10),
+                            height: parseInt(taStyle.getPropertyValue('border-top-width'), 10) +
+                                    parseInt(taStyle.getPropertyValue('padding-top'), 10) +
+                                    parseInt(taStyle.getPropertyValue('padding-bottom'), 10) +
+                                    parseInt(taStyle.getPropertyValue('border-bottom-width'), 10)
+                          },
+              minHeightValue = parseInt(taStyle.getPropertyValue('min-height'), 10),
+              heightValue = parseInt(taStyle.getPropertyValue('height'), 10),
+              minHeight = Math.max(minHeightValue, heightValue) - boxOuter.height,
+              maxHeight = parseInt(taStyle.getPropertyValue('max-height'), 10),
+              mirrored,
+              active,
+              copyStyle = ['font-family',
+                           'font-size',
+                           'font-weight',
+                           'font-style',
+                           'letter-spacing',
+                           'line-height',
+                           'text-transform',
+                           'word-spacing',
+                           'text-indent'];
+
+          // exit if elastic already applied (or is the mirror element)
+          if ($ta.data('elastic')) {
+            return;
+          }
+
+          // Opera returns max-height of -1 if not set
+          maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
+
+          // append mirror to the DOM
+          if (mirror.parentNode !== document.body) {
+            angular.element(document.body).append(mirror);
+          }
+
+          // set resize and apply elastic
+          $ta.css({
+            'resize': (resize === 'none' || resize === 'vertical') ? 'none' : 'horizontal'
+          }).data('elastic', true);
+
+          /*
+           * methods
+           */
+
+          function initMirror() {
+            var mirrorStyle = mirrorInitStyle;
+
+            mirrored = ta;
+            // copy the essential styles from the textarea to the mirror
+            taStyle = getComputedStyle(ta);
+            angular.forEach(copyStyle, function(val) {
+              mirrorStyle += val + ':' + taStyle.getPropertyValue(val) + ';';
+            });
+            mirror.setAttribute('style', mirrorStyle);
+          }
+
+          function adjust() {
+            var taHeight,
+                taComputedStyleWidth,
+                mirrorHeight,
+                width,
+                overflow;
+
+            if (mirrored !== ta) {
+              initMirror();
+            }
+
+            // active flag prevents actions in function from calling adjust again
+            if (!active) {
+              active = true;
+
+              mirror.value = ta.value + append; // optional whitespace to improve animation
+              mirror.style.overflowY = ta.style.overflowY;
+
+              taHeight = ta.style.height === '' ? 'auto' : parseInt(ta.style.height, 10);
+
+              taComputedStyleWidth = getComputedStyle(ta).getPropertyValue('width');
+
+              // ensure getComputedStyle has returned a readable 'used value' pixel width
+              if (taComputedStyleWidth.substr(taComputedStyleWidth.length - 2, 2) === 'px') {
+                // update mirror width in case the textarea width has changed
+                width = parseInt(taComputedStyleWidth, 10) - boxOuter.width;
+                mirror.style.width = width + 'px';
+              }
+
+              mirrorHeight = mirror.scrollHeight;
+
+              if (mirrorHeight > maxHeight) {
+                mirrorHeight = maxHeight;
+                overflow = 'scroll';
+              } else if (mirrorHeight < minHeight) {
+                mirrorHeight = minHeight;
+              }
+              mirrorHeight += boxOuter.height;
+
+              ta.style.overflowY = overflow || 'hidden';
+
+              if (taHeight !== mirrorHeight) {
+                ta.style.height = mirrorHeight + 'px';
+                scope.$emit('elastic:resize', $ta);
+              }
+
+              // small delay to prevent an infinite loop
+              $timeout(function() {
+                active = false;
+              }, 1);
+
+            }
+          }
+
+          function forceAdjust() {
+            active = false;
+            adjust();
+          }
+
+          /*
+           * initialise
+           */
+
+          // listen
+          if ('onpropertychange' in ta && 'oninput' in ta) {
+            // IE9
+            ta['oninput'] = ta.onkeyup = adjust;
+          } else {
+            ta['oninput'] = adjust;
+          }
+
+          $win.bind('resize', forceAdjust);
+
+          scope.$watch(function() {
+            return ngModel.$modelValue;
+          }, function(newValue) {
+            forceAdjust();
+          });
+
+          scope.$on('elastic:adjust', function() {
+            initMirror();
+            forceAdjust();
+          });
+
+          $timeout(adjust);
+
+          /*
+           * destroy
+           */
+
+          scope.$on('$destroy', function() {
+            $mirror.remove();
+            $win.unbind('resize', forceAdjust);
+          });
+        }
+      };
+    }
+  ]);
-- 
cgit 1.5.1