Browse Source

added initial web-version

gh-pages
Igor Zhukov 11 years ago
commit
0ffdbb8728
  1. 1162
      css/app.css
  2. BIN
      img/Logo_1x.png
  3. BIN
      img/Logo_2x.png
  4. BIN
      img/bg_full.png
  5. BIN
      img/bg_tile.png
  6. BIN
      img/icons/Arrow_1x.png
  7. BIN
      img/icons/Arrow_2x.png
  8. BIN
      img/icons/Attach_1x.png
  9. BIN
      img/icons/Attach_2x.png
  10. BIN
      img/icons/Attach_pressed_1x.png
  11. BIN
      img/icons/Attach_pressed_2x.png
  12. BIN
      img/icons/CheckIn_Recent.png
  13. BIN
      img/icons/Checks1_1x.png
  14. BIN
      img/icons/Checks1_2x.png
  15. BIN
      img/icons/Checks2_1x.png
  16. BIN
      img/icons/Checks2_2x.png
  17. BIN
      img/icons/CloseHover_1x.png
  18. BIN
      img/icons/CloseHover_2x.png
  19. BIN
      img/icons/Close_1x.png
  20. BIN
      img/icons/Close_2x.png
  21. BIN
      img/icons/DialogListGroupChatIcon@2x.png
  22. BIN
      img/icons/DialogListGroupChatIcon_Highlighted@2x.png
  23. BIN
      img/icons/DocBlue_1x.png
  24. BIN
      img/icons/DocBlue_2x.png
  25. BIN
      img/icons/DocGrey_1x.png
  26. BIN
      img/icons/DocGrey_2x.png
  27. BIN
      img/icons/Location_Active.png
  28. BIN
      img/icons/Logo_1x.png
  29. BIN
      img/icons/Logo_2x.png
  30. BIN
      img/icons/NoResults.png
  31. BIN
      img/icons/Search_1x.png
  32. BIN
      img/icons/Search_2x.png
  33. BIN
      img/icons/Smile_1x.png
  34. BIN
      img/icons/Smile_2x.png
  35. BIN
      img/icons/Smile_pressed_1x.png
  36. BIN
      img/icons/Smile_pressed_2x.png
  37. BIN
      img/icons/VideoIcon.png
  38. BIN
      img/icons/icon128.png
  39. BIN
      img/icons/icon16.png
  40. BIN
      img/placeholders/DialogListAvatarSystem@2x.png
  41. BIN
      img/placeholders/GroupAvatar1@2x.png
  42. BIN
      img/placeholders/GroupAvatar2@2x.png
  43. BIN
      img/placeholders/GroupAvatar3@2x.png
  44. BIN
      img/placeholders/GroupAvatar4@2x.png
  45. BIN
      img/placeholders/UserAvatar1@2x.png
  46. BIN
      img/placeholders/UserAvatar2@2x.png
  47. BIN
      img/placeholders/UserAvatar3@2x.png
  48. BIN
      img/placeholders/UserAvatar4@2x.png
  49. BIN
      img/placeholders/UserAvatar5@2x.png
  50. BIN
      img/placeholders/UserAvatar6@2x.png
  51. BIN
      img/placeholders/UserAvatar7@2x.png
  52. BIN
      img/placeholders/UserAvatar8@2x.png
  53. 45
      index.html
  54. 55
      js/app.js
  55. 18
      js/background.js
  56. 507
      js/controllers.js
  57. 512
      js/directives.js
  58. 126
      js/filters.js
  59. 21
      js/lib/aes_worker.js
  60. 14
      js/lib/config.js
  61. 2612
      js/lib/mtproto.js
  62. 12
      js/lib/pq_worker.js
  63. 14
      js/lib/sha1_worker.js
  64. 1439
      js/services.js
  65. 55
      js/util.js
  66. 18
      manifest.json
  67. 41
      partials/chat_modal.html
  68. 73
      partials/dialog.html
  69. 17
      partials/head.html
  70. 118
      partials/im.html
  71. 28
      partials/login.html
  72. 91
      partials/message.html
  73. 11
      partials/photo_modal.html
  74. 25
      partials/user_modal.html
  75. 11
      partials/video_modal.html
  76. 5
      partials/welcome.html
  77. 81
      vendor/README.md
  78. 1294
      vendor/angular/angular-animate.js
  79. 22
      vendor/angular/angular-animate.min.js
  80. 8
      vendor/angular/angular-animate.min.js.map
  81. 202
      vendor/angular/angular-cookies.js
  82. 8
      vendor/angular/angular-cookies.min.js
  83. 8
      vendor/angular/angular-cookies.min.js.map
  84. 24
      vendor/angular/angular-csp.css
  85. 410
      vendor/angular/angular-loader.js
  86. 9
      vendor/angular/angular-loader.min.js
  87. 8
      vendor/angular/angular-loader.min.js.map
  88. 546
      vendor/angular/angular-resource.js
  89. 12
      vendor/angular/angular-resource.min.js
  90. 8
      vendor/angular/angular-resource.min.js.map
  91. 891
      vendor/angular/angular-route.js
  92. 14
      vendor/angular/angular-route.min.js
  93. 8
      vendor/angular/angular-route.min.js.map
  94. 615
      vendor/angular/angular-sanitize.js
  95. 14
      vendor/angular/angular-sanitize.min.js
  96. 8
      vendor/angular/angular-sanitize.min.js.map
  97. 563
      vendor/angular/angular-touch.js
  98. 13
      vendor/angular/angular-touch.min.js
  99. 8
      vendor/angular/angular-touch.min.js.map
  100. 20282
      vendor/angular/angular.js
  101. Some files were not shown because too many files have changed in this diff Show More

1162
css/app.css

File diff suppressed because it is too large Load Diff

BIN
img/Logo_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/Logo_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
img/bg_full.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

BIN
img/bg_tile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
img/icons/Arrow_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

BIN
img/icons/Arrow_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

BIN
img/icons/Attach_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

BIN
img/icons/Attach_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

BIN
img/icons/Attach_pressed_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
img/icons/Attach_pressed_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

BIN
img/icons/CheckIn_Recent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

BIN
img/icons/Checks1_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

BIN
img/icons/Checks1_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

BIN
img/icons/Checks2_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

BIN
img/icons/Checks2_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

BIN
img/icons/CloseHover_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

BIN
img/icons/CloseHover_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/icons/Close_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

BIN
img/icons/Close_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/icons/DialogListGroupChatIcon@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
img/icons/DialogListGroupChatIcon_Highlighted@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

BIN
img/icons/DocBlue_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

BIN
img/icons/DocBlue_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

BIN
img/icons/DocGrey_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

BIN
img/icons/DocGrey_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

BIN
img/icons/Location_Active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
img/icons/Logo_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/icons/Logo_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
img/icons/NoResults.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
img/icons/Search_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

BIN
img/icons/Search_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

BIN
img/icons/Smile_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

BIN
img/icons/Smile_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
img/icons/Smile_pressed_1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

BIN
img/icons/Smile_pressed_2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
img/icons/VideoIcon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

BIN
img/icons/icon128.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
img/icons/icon16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
img/placeholders/DialogListAvatarSystem@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
img/placeholders/GroupAvatar1@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
img/placeholders/GroupAvatar2@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
img/placeholders/GroupAvatar3@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
img/placeholders/GroupAvatar4@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
img/placeholders/UserAvatar1@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/placeholders/UserAvatar2@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/placeholders/UserAvatar3@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
img/placeholders/UserAvatar4@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
img/placeholders/UserAvatar5@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
img/placeholders/UserAvatar6@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
img/placeholders/UserAvatar7@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
img/placeholders/UserAvatar8@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

45
index.html

@ -0,0 +1,45 @@
<!doctype html>
<html lang="en" ng-app="myApp" ng-csp="">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webogram</title>
<link rel="stylesheet" href="vendor/angular/angular-csp.css"/>
<link rel="stylesheet" href="vendor/bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" href="vendor/jquery.nanoscroller/nanoscroller.css"/>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<div ng-view></div>
<!-- In production use:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
-->
<script type="text/javascript" src="vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="vendor/jquery.nanoscroller/nanoscroller.js"></script>
<script type="text/javascript" src="vendor/jquery.emojiarea/jquery.emojiarea.js"></script>
<script type="text/javascript" src="vendor/angular/angular.js"></script>
<script type="text/javascript" src="vendor/angular/angular-route.js"></script>
<script type="text/javascript" src="vendor/angular/angular-animate.js"></script>
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script>
<script type="text/javascript" src="vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js?2"></script>
<script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script>
<script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script>
<script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script>
<script type="text/javascript" src="js/lib/config.js"></script>
<script type="text/javascript" src="js/lib/mtproto.js"></script>
<script type="text/javascript" src="js/util.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/services.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
<script type="text/javascript" src="js/filters.js"></script>
<script type="text/javascript" src="js/directives.js"></script>
</body>
</html>

55
js/app.js

@ -0,0 +1,55 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
'use strict';
// window._testMode = 1;
// Declare app level module which depends on filters, and services
angular.module('myApp', [
'ngRoute',
'ngAnimate',
'ngSanitize',
'ui.bootstrap',
'myApp.filters',
'myApp.services',
'mtproto.services',
'myApp.directives',
'myApp.controllers'
]).
config(['$locationProvider', '$routeProvider', '$compileProvider', function($locationProvider, $routeProvider, $compileProvider) {
var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem,
ranges = [[0x1f600, 0x1f637], [0x270a, 0x270c], [0x1f446, 0x1f450]];
for (j in ranges) {
for (i = ranges[j][0]; i <= ranges[j][1]; i++) {
hex = i.toString(16);
if (dataItem = Config.Emoji[hex]) {
name = dataItem[1][0];
icons[':' + name + ':'] = hex + '.png';
reverseIcons[name] = dataItem[0];
}
}
}
$.emojiarea.path = 'vendor/gemoji/images';
$.emojiarea.icons = icons;
$.emojiarea.reverseIcons = reverseIcons;
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob|filesystem|chrome-extension):|data:image\//);
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension):|data:image\//);
// $locationProvider.html5Mode(true);
$routeProvider.when('/', {templateUrl: 'partials/welcome.html', controller: 'AppWelcomeController'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'AppLoginController'});
$routeProvider.when('/im', {templateUrl: 'partials/im.html', controller: 'AppIMController', reloadOnSearch: false});
$routeProvider.otherwise({redirectTo: '/'});
}]);

18
js/background.js

@ -0,0 +1,18 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
chrome.app.runtime.onLaunched.addListener(function(launchData) {
chrome.app.window.create('../index.html', {
bounds: {
width: 1100,
height: 700
},
minWidth: 1100,
minHeight: 700,
frame: 'chrome'
});
});

507
js/controllers.js

@ -0,0 +1,507 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
'use strict';
/* Controllers */
angular.module('myApp.controllers', [])
.controller('AppWelcomeController', function($scope, $location, MtpApiManager) {
MtpApiManager.getUserID().then(function (id) {
if (id) {
$location.path('/im');
} else {
$location.path('/login');
}
});
})
.controller('AppLoginController', function ($scope, $location, MtpApiManager) {
var dcID = 1;
$scope.credentials = {};
function saveAuth (result) {
MtpApiManager.setUserAuth(dcID, {
expires: result.expires,
id: result.user.id
});
$location.path('/im');
};
$scope.sendCode = function () {
MtpApiManager.invokeApi('auth.sendCode', {
phone_number: $scope.credentials.phone_number,
sms_type: 0,
api_id: 2496,
api_hash: '8da85b0d5bfe62527e5b244c209159c3'
}, {dcID: dcID}).then(function (sentCode) {
$scope.credentials.phone_code_hash = sentCode.phone_code_hash;
$scope.credentials.phone_occupied = sentCode.phone_registered;
$scope.error = {};
}, function (error) {
dLog('sendCode', error);
if (error.code == 303) {
var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_)(\d+)/)[2];
if (newDcID != dcID) {
dcID = newDcID;
$scope.sendCode();
return;
}
}
switch (error.type) {
case 'PHONE_NUMBER_INVALID':
$scope.error = {field: 'phone'};
break;
}
});
}
$scope.logIn = function (forceSignUp) {
var method = 'auth.signIn', params = {
phone_number: $scope.credentials.phone_number,
phone_code_hash: $scope.credentials.phone_code_hash,
phone_code: $scope.credentials.phone_code
};
if (forceSignUp) {
method = 'auth.signUp';
angular.extend(params, {
first_name: $scope.credentials.first_name,
last_name: $scope.credentials.last_name
});
}
MtpApiManager.invokeApi(method, params, {dcID: dcID}).then(saveAuth, function (error) {
if (error.code == 400 && error.type == 'PHONE_NUMBER_UNOCCUPIED') {
return $scope.logIn(true);
} else if (error.code == 400 && error.type == 'PHONE_NUMBER_UNOCCUPIED') {
return $scope.logIn(false);
}
switch (error.type) {
case 'FIRSTNAME_INVALID':
$scope.error = {field: 'first_name'};
break;
case 'LASTNAME_INVALID':
$scope.error = {field: 'last_name'};
break;
case 'PHONE_CODE_INVALID':
$scope.error = {field: 'phone_code'};
break;
}
});
};
})
.controller('AppIMController', function ($scope, $location, $routeParams, MtpApiManager) {
$scope.$on('$routeUpdate', updateCurDialog);
$scope.isLoggedIn = true;
$scope.logOut = function () {
MtpApiManager.logOut().then(function () {
$location.path('/login');
});
}
updateCurDialog();
function updateCurDialog() {
$scope.curDialog = {
peer: $routeParams.p || false
};
}
})
.controller('AppImDialogsController', function ($scope, $location, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager) {
$scope.dialogs = [];
var offset = 0,
hasMore = false,
limit = 20;
MtpApiManager.invokeApi('account.updateStatus', {offline: false});
$scope.$on('dialogs_need_more', function () {
showMoreDialogs();
});
$scope.$on('dialog_unread', function (e, dialog) {
angular.forEach($scope.dialogs, function(curDialog) {
if (curDialog.peerID == dialog.peerID) {
curDialog.unreadCount = dialog.unread_count;
}
});
});
$scope.$on('dialogs_update', function (e, dialog) {
var pos = false;
angular.forEach($scope.dialogs, function(curDialog, curPos) {
if (curDialog.peerID == dialog.peerID) {
pos = curPos;
}
});
var wrappedDialog = AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count);
if (pos !== false) {
var prev = $scope.dialogs.splice(pos, 1);
wrappedDialog = angular.extend(prev[0], wrappedDialog);
offset++;
}
$scope.dialogs.unshift(wrappedDialog);
});
loadDialogs();
function loadDialogs (startLimit) {
offset = 0;
hasMore = false;
startLimit = startLimit || limit;
AppMessagesManager.getDialogs(offset, startLimit).then(function (dialogsResult) {
offset += startLimit;
hasMore = offset < dialogsResult.count;
$scope.dialogs = [];
angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
});
$scope.$broadcast('ui_dialogs_change');
}, function (error) {
if (error.code == 401) {
$location.path('/login');
}
});
}
function showMoreDialogs () {
if (!hasMore || !offset) {
return;
}
AppMessagesManager.getDialogs(offset, limit).then(function (dialogsResult) {
offset += limit;
hasMore = offset < dialogsResult.count;
angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
});
$scope.$broadcast('ui_dialogs_append');
});
}
})
.controller('AppImHistoryController', function ($scope, $location, $timeout, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager) {
$scope.$watch('curDialog.peer', applyDialogSelect);
ApiUpdatesManager.attach();
$scope.history = [];
$scope.typing = {};
var peerID, offset, hasMore, maxID, limit = 20;
function applyDialogSelect (newPeer) {
newPeer = newPeer || $scope.curDialog.peer || '';
peerID = AppPeersManager.getPeerID(newPeer);
$scope.curDialog.peerID = peerID;
$scope.curDialog.inputPeer = AppPeersManager.getInputPeer(newPeer);
if (peerID) {
loadHistory(peerID);
} else {
showEmptyHistory();
}
}
function showMoreHistory () {
if (!hasMore || !offset) {
return;
}
console.trace('load history');
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) {
offset += limit;
hasMore = offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1];
angular.forEach(historyResult.history, function (id) {
$scope.history.unshift(AppMessagesManager.wrapForHistory(id));
});
$scope.$broadcast('ui_history_prepend');
}, function () {
$scope.state = {error: true};
});
}
function loadHistory () {
hasMore = false;
offset = 0;
maxID = 0;
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) {
offset += limit;
hasMore = offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1];
$scope.history = [];
angular.forEach(historyResult.history, function (id) {
$scope.history.push(AppMessagesManager.wrapForHistory(id));
});
$scope.history.reverse();
$scope.historyPeer = {
id: peerID,
data: AppPeersManager.getPeer(peerID),
photo: AppPeersManager.getPeerPhoto(peerID, 'User', 'Group')
};
$scope.typing = {};
MtpApiManager.getUserID().then(function (id) {
$scope.ownPhoto = AppUsersManager.getUserPhoto(id, 'User');
});
$scope.state = {loaded: true};
$scope.$broadcast('ui_history_change');
AppMessagesManager.readHistory($scope.curDialog.inputPeer);
}, function () {
$scope.state = {error: true};
});
}
function showEmptyHistory () {
$scope.state = {notSelected: true};
$scope.history = [];
}
var typingTimeouts = {};
$scope.$on('history_append', function (e, addedMessage) {
if (addedMessage.peerID == $scope.curDialog.peerID) {
dLog('append', addedMessage);
// console.trace();
$scope.history.push(AppMessagesManager.wrapForHistory(addedMessage.messageID));
$scope.typing = {};
$scope.$broadcast('ui_history_append');
offset++
}
});
$scope.$on('apiUpdate', function (e, update) {
// dLog('on apiUpdate inline', update);
switch (update._) {
case 'updateUserTyping':
if (update.user_id == $scope.curDialog.peerID) {
$scope.typing = {user: AppUsersManager.getUser(update.user_id)};
$timeout.cancel(typingTimeouts[update.user_id]);
typingTimeouts[update.user_id] = $timeout(function () {
$scope.typing = {};
}, 6000);
}
break;
case 'updateChatUserTyping':
if (-update.chat_id == $scope.curDialog.peerID) {
$scope.typing = {user: AppUsersManager.getUser(update.user_id)};
$timeout.cancel(typingTimeouts[update.user_id]);
typingTimeouts[update.user_id] = $timeout(function () {
$scope.typing = {};
}, 6000);
}
break;
}
});
$scope.$on('history_need_more', function () {
showMoreHistory();
});
})
.controller('AppImPanelController', function($scope) {
$scope.$on('user_update', angular.noop);
})
.controller('AppImSendController', function ($scope, MtpApiManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
$scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop);
$scope.draftMessage = {text: ''};
var lastTyping = false;
$scope.$watch('draftMessage.text', function (newVal) {
AppMessagesManager.readHistory($scope.curDialog.inputPeer);
var now = +new Date();
if (newVal === undefined || !newVal.length || now - lastTyping < 6000) {
return;
}
lastTyping = now;
MtpApiManager.invokeApi('messages.setTyping', {
peer: $scope.curDialog.inputPeer,
typing: true
});
});
$scope.sendMessage = sendMessage;
$scope.$watch('draftMessage.files', onFilesSelected);
function sendMessage (e) {
cancelEvent(e);
var text = $scope.draftMessage.text;
if ($scope.draftMessage.sending || !text.length) {
return false;
}
text = text.replace(/:\s*(.+?)\s*:/g, function (all, name) {
var utfChar = $.emojiarea.reverseIcons[name];
if (utfChar !== undefined) {
return utfChar;
}
return all;
});
$scope.draftMessage.sending = true;
MtpApiManager.invokeApi('messages.sendMessage', {
peer: $scope.curDialog.inputPeer,
message: text,
random_id: $scope.draftMessage.randomID
}).then(function (result) {
if (ApiUpdatesManager.saveSeq(result.seq)) {
MtpApiManager.getUserID().then(function (fromID) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: {
_: 'message',
id: result.id,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer($scope.curDialog.peerID),
out: true,
unread: true,
date: result.date,
message: text,
media: {_: 'messageMediaEmpty'}
},
pts: result.pts
});
});
}
$scope.$broadcast('ui_message_send');
resetDraft();
}, function () {
delete $scope.draftMessage.sending;
});
return cancelEvent(e);
}
function resetDraft () {
$scope.draftMessage = {
randomID: [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
text: ''
};
}
function onFilesSelected (newVal) {
if (!angular.isArray(newVal) || !newVal.length) {
return;
}
for (var i = 0; i < newVal.length; i++) {
(function (file, randomID) {
MtpApiFileManager.uploadFile(file).then(function (inputFile) {
var inputMedia;
if (file.type == 'image/jpeg') {
inputMedia = {_: 'inputMediaUploadedPhoto', file: inputFile};
} else {
inputMedia = {_: 'inputMediaUploadedDocument', file: inputFile, file_name: file.name, mime_type: file.type};
}
MtpApiManager.invokeApi('messages.sendMedia', {
peer: $scope.curDialog.inputPeer,
media: inputMedia,
random_id: randomID
}).then(function (result) {
if (ApiUpdatesManager.saveSeq(result.seq)) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: result.message,
pts: result.pts
});
}
$scope.$broadcast('ui_message_send');
});
}, function (error) {
dLog('upload error', error);
})
})(newVal[i], [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]);
}
}
})
.controller('PhotoModalController', function ($scope, AppPhotosManager) {
$scope.photo = AppPhotosManager.wrapForFull($scope.photoID);
})
.controller('VideoModalController', function ($scope, AppVideoManager) {
$scope.video = AppVideoManager.wrapForFull($scope.videoID);
})
.controller('UserModalController', function ($scope, $location, AppUsersManager) {
$scope.user = AppUsersManager.wrapForFull($scope.userID);
$scope.goToHistory = function () {
$scope.$close();
$location.url('/im?p=' + $scope.user.peerString);
};
})
.controller('ChatModalController', function ($scope, AppUsersManager, AppChatsManager, fullChat) {
$scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, fullChat);
})

512
js/directives.js

@ -0,0 +1,512 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
'use strict';
/* Directives */
angular.module('myApp.directives', ['myApp.filters'])
.directive('myDialog', function() {
return {
restrict: 'AE',
scope: true,
translude: false,
templateUrl: 'partials/dialog.html'
};
})
.directive('myMessage', function() {
return {
restrict: 'AE',
scope: true,
translude: false,
templateUrl: 'partials/message.html'
};
})
.directive('myDialogsList', function($window, $timeout) {
return {
link: link
};
function link (scope, element, attrs) {
var dialogsWrap = $('.im_dialogs_wrap')[0],
scrollableWrap = $('.im_dialogs_scrollable_wrap')[0],
// dialogsSearch = $('im_dialogs_search')[0],
moreNotified = false;
onContentLoaded(function () {
$(dialogsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1});
});
var updateScroller = function () {
onContentLoaded(function () {
$(dialogsWrap).nanoScroller();
});
}
scope.$on('ui_dialogs_prepend', updateScroller);
scope.$on('ui_dialogs_append', function () {
onContentLoaded(function () {
updateScroller();
moreNotified = false;
});
});
scope.$on('ui_dialogs_change', function () {
onContentLoaded(function () {
updateScroller();
moreNotified = false;
});
});
$(scrollableWrap).on('scroll', function (e) {
if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) {
scope.$emit('dialogs_need_more');
moreNotified = true;
}
});
function updateSizes () {
$(element).css({
height: $($window).height() - 162
});
}
$($window).on('resize', updateSizes);
updateSizes();
};
})
.directive('myHistory', function ($window, $timeout) {
return {
link: link
};
function link (scope, element, attrs) {
var historyWrap = $('.im_history_wrap')[0],
scrollableWrap = $('.im_history_scrollable_wrap')[0],
scrollable = $('.im_history_scrollable')[0],
panelWrap = $('.im_history_panel_wrap', element)[0],
sendFormWrap = $('.im_send_form_wrap', element)[0],
moreNotified = false;
onContentLoaded(function () {
$(historyWrap).nanoScroller({preventPageScrolling: true, scroll: 'bottom', tabIndex: -1});
});
var updateScroller = function (delay) {
$timeout(function () {
$(historyWrap).nanoScroller();
}, delay || 0);
}
scope.$on('ui_history_append', function () {
var st = scrollableWrap.scrollTop;
$(scrollableWrap).addClass('im_history_to_bottom');
if (atBottom) {
onContentLoaded(function () {
$(scrollableWrap).removeClass('im_history_to_bottom');
updateSizes();
$(historyWrap).nanoScroller({scrollBottom: 0});
// scrollableWrap.scrollTop = st;
// $(scrollableWrap).animate({
// scrollTop: scrollableWrap.scrollHeight - scrollableWrap.clientHeight
// }, 200);
updateScroller();
});
}
});
scope.$on('ui_history_change', function () {
$(scrollableWrap).addClass('im_history_to_bottom');
$(scrollable).css({bottom: 0});
onContentLoaded(function () {
$(scrollableWrap).removeClass('im_history_to_bottom');
$(scrollable).css({bottom: ''});
updateSizes();
$(historyWrap).nanoScroller();
$(historyWrap).nanoScroller({scrollBottom: 0});
updateScroller(100);
moreNotified = false;
});
});
scope.$on('ui_history_prepend', function () {
var sh = scrollableWrap.scrollHeight,
st = scrollableWrap.scrollTop,
ch = scrollableWrap.clientHeight;
$(scrollableWrap).addClass('im_history_to_bottom');
$(scrollable).css({bottom: -(sh - st - ch)});
onContentLoaded(function () {
$(scrollableWrap).removeClass('im_history_to_bottom');
$(scrollable).css({bottom: ''});
$(historyWrap).nanoScroller();
$(historyWrap).nanoScroller({scrollTop: st + scrollableWrap.scrollHeight - sh});
// updateScroller();
updateScroller(50);
moreNotified = false;
});
});
var atBottom = true;
$(scrollableWrap).on('scroll', function (e) {
atBottom = scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight;
if (!moreNotified && scrollableWrap.scrollTop <= 300) {
moreNotified = true;
scope.$emit('history_need_more');
}
});
function updateSizes () {
$(historyWrap).css({
height: $($window).height() - panelWrap.offsetHeight - sendFormWrap.offsetHeight - 90
});
if (atBottom) {
onContentLoaded(function () {
$(historyWrap).nanoScroller({scroll: 'bottom'});
});
}
updateScroller(100);
}
$($window).on('resize', updateSizes);
onContentLoaded(updateSizes);
}
})
.directive('mySendForm', function ($timeout) {
return {
link: link,
scope: {
draftMessage: '='
}
};
function link (scope, element, attrs) {
var messageField = $('textarea', element)[0],
fileSelect = $('input', element)[0],
dropbox = $('.im_send_dropbox_wrap', element)[0],
emojiButton = $('.im_emoji_btn', element)[0],
editorElement = messageField,
dragStarted, dragTimeout,
emojiArea = $(messageField).emojiarea({button: emojiButton}),
emojiMenu = $('.emoji-menu')[0],
richTextarea = $('.emoji-wysiwyg-editor', element)[0];
if (richTextarea) {
editorElement = richTextarea;
$(richTextarea).addClass('form-control');
$(richTextarea).attr('placeholder', $(messageField).attr('placeholder'));
}
// $(emojiMenu.firstChild).addClass('nano').nanoScroller({preventPageScrolling: true, tabIndex: -1});
$(fileSelect).on('change', function () {
scope.$apply(function () {
scope.draftMessage.files = Array.prototype.slice.call(fileSelect.files);
});
});
var sendOnEnter = true;
$(editorElement).on('keydown', function (e) {
if (e.keyCode != 13) {
return;
}
var submit = false;
if (sendOnEnter && !e.shiftKey) {
submit = true;
} else if (!sendOnEnter && (e.ctrlKey || e.metaKey)) {
submit = true;
}
if (submit) {
$(element).trigger('submit');
dLog('after submit');
return cancelEvent(e);
}
});
if (richTextarea) {
scope.$watch('draftMessage.text', function (newVal) {
if (!newVal.length && !messageField.value.length) {
$timeout(function () {
$(richTextarea).html('');
}, 0);
}
});
}
$('body').on('dragenter dragleave dragover drop', onDragDropEvent);
scope.$on('ui_history_change', focusField);
scope.$on('ui_message_send', focusField);
scope.$on('$destroy', function cleanup() {
$('body').off('dragenter dragleave dragover drop', onDragDropEvent);
});
focusField();
function focusField () {
onContentLoaded(function () {
$(editorElement).focus();
});
}
function onDragDropEvent(e) {
var dragStateChanged = false;
if (!dragStarted || dragStarted == 1) {
dragStarted = checkDragEvent(e) ? 2 : 1;
dragStateChanged = true;
}
if (dragStarted == 2) {
if (dragTimeout) {
setTimeout(function () {
clearTimeout(dragTimeout);
dragTimeout = false;
}, 0);
}
if (e.type == 'dragenter' || e.type == 'dragover') {
if (dragStateChanged) {
$(dropbox)
.css({height: $(editorElement).height() + 12, width: $(editorElement).width() + 12})
.show();
}
} else {
if (e.type == 'drop') {
scope.$apply(function () {
scope.draftMessage.files = Array.prototype.slice.call(e.originalEvent.dataTransfer.files);
});
}
dragTimeout = setTimeout(function () {
$(dropbox).hide();
dragStarted = false;
dragTimeout = false;
}, 300);
}
}
return cancelEvent(e);
};
}
})
.directive('myLoadThumb', function(MtpApiFileManager) {
return {
link: link,
scope: {
thumb: '='
}
};
function link (scope, element, attrs) {
scope.$watch('thumb.location', function (newVal) {
if (!scope.thumb) dLog(222, scope);
if (!scope.thumb.location) {
element.attr('src', scope.thumb.placeholder || '');
return;
}
MtpApiFileManager.downloadSmallFile(scope.thumb.location, scope.thumb.size).then(function (url) {
element.attr('src', url);
}, function (e) {
dLog('Download image failed', e, scope.thumb.location);
element.attr('src', scope.thumb.placeholder || '');
});
})
}
})
.directive('myLoadFullPhoto', function(MtpApiFileManager) {
return {
link: link,
transclude: true,
template:
'<div class="img_fullsize_with_progress_wrap" ng-style="{width: fullPhoto.width + \'px\', height: fullPhoto.height + \'px\'}">\
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled">\
<div class="img_fullsize_progress_wrap" ng-style="{width: fullPhoto.width + \'px\', height: fullPhoto.height + \'px\'}">\
<div class="img_fullsize_progress progress tg_progress">\
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{progress.percent}}" aria-valuemin="0" aria-valuemax="100" style="width: {{progress.percent}}%">\
<span class="sr-only">{{progress.percent}}% Complete (success)</span>\
</div>\
</div>\
</div>\
</div>\
<div class="photo_full_wrap">\
<a class="photo_modal_image">\
<img class="photo_modal_image" width="{{fullPhoto.width}}" height="{{fullPhoto.height}}" />\
</a>\
</div>\
</div>',
scope: {
fullPhoto: '=',
thumbLocation: '='
}
};
function link (scope, element, attrs) {
var imgElement = $('img', element),
fullLoaded = false;
if (!scope.fullPhoto.location) {
imgElement.attr('src', scope.fullPhoto.placeholder || '');
return;
}
MtpApiFileManager.getCachedFile(scope.thumbLocation).then(function (url) {
if (!fullLoaded) {
imgElement
.attr('src', url)
.addClass('thumb_blurred')
.addClass('thumb_blur_animation');
}
});
var apiPromise;
if (scope.fullPhoto.size) {
var inputLocation = {
_: 'inputFileLocation',
volume_id: scope.fullPhoto.location.volume_id,
local_id: scope.fullPhoto.location.local_id,
secret: scope.fullPhoto.location.secret
};
apiPromise = MtpApiFileManager.downloadFile(scope.fullPhoto.location.dc_id, inputLocation, scope.fullPhoto.size);
} else {
apiPromise = MtpApiFileManager.downloadSmallFile(scope.fullPhoto.location);
}
scope.progress = {enabled: true, percent: 1};
apiPromise.then(function (url) {
fullLoaded = true;
scope.progress.enabled = false;
imgElement
.attr('src', url)
.removeClass('thumb_blurred');
}, function (e) {
dLog('Download image failed', e, scope.fullPhoto.location);
scope.progress.enabled = false;
imgElement
.attr('src', scope.fullPhoto.placeholder || '')
.removeClass('thumb_blurred');
}, function (progress) {
scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
});
}
})
.directive('myLoadVideo', function($sce, MtpApiFileManager) {
return {
link: link,
transclude: true,
template:
'<div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + \'px\', height: video.full.height + \'px\'}">\
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled">\
<div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + \'px\', height: video.full.height + \'px\'}">\
<div class="img_fullsize_progress progress tg_progress">\
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{progress.percent}}" aria-valuemin="0" aria-valuemax="100" style="width: {{progress.percent}}%">\
<span class="sr-only">{{progress.percent}}% Complete (success)</span>\
</div>\
</div>\
</div>\
</div>\
<div class="img_fullsize_wrap" ng-if="!player.src">\
<img class="img_fullsize" my-load-thumb thumb="video.thumb" width="{{video.full.width}}" height="{{video.full.height}}" />\
</div>\
<div class="video_full_player" ng-if="player.src">\
<video width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay>\
<source ng-src="{{player.src}}" type="video/mp4">\
</video>\
</div>\
</div>',
scope: {
video: '='
}
};
function link (scope, element, attrs) {
scope.progress = {enabled: true, percent: 1};
scope.player = {};
var inputLocation = {
_: 'inputVideoFileLocation',
id: scope.video.id,
access_hash: scope.video.access_hash
};
MtpApiFileManager.downloadFile(scope.video.dc_id, inputLocation, scope.video.size).then(function (url) {
scope.progress.enabled = false;
// scope.progress = {enabled: true, percent: 50};
scope.player.src = $sce.trustAsResourceUrl(url);
}, function (e) {
dLog('Download image failed', e, scope.fullPhoto.location);
scope.progress.enabled = false;
scope.player.src = '';
}, function (progress) {
scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
});
}
})
.directive('myMapPoint', function(ExternalResourcesManager) {
return {
link: link,
scope: {
point: '='
}
};
function link (scope, element, attrs) {
var apiKey = 'AIzaSyC32ij28dCa0YzEV_HqbWfIwTZQql-RNS0';
var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false&center=' + scope.point['lat'] + ',' + scope.point['long'] + '&zoom=13&size=200x100&scale=2&key=' + apiKey;
ExternalResourcesManager.downloadImage(src).then(function (url) {
element.append('<img src="' + url + '" width="200" height="100"/>');
});
element.attr('href','https://maps.google.com/?q=' + scope.point['lat'] + ',' + scope.point['long']);
element.attr('target','_blank');
}
})

126
js/filters.js

@ -0,0 +1,126 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
'use strict';
/* Filters */
angular.module('myApp.filters', [])
.filter('userName', [function() {
return function (user) {
if (!user || !user.first_name && !user.last_name) {
return 'DELETED';
}
return user.first_name + ' ' + user.last_name;
}
}])
.filter('userFirstName', [function() {
return function (user) {
if (!user || !user.first_name && !user.last_name) {
return 'DELETED';
}
return user.first_name || user.last_name;
}
}])
.filter('userStatus', ['$filter', function($filter) {
return function (user) {
if (!user || !user.status || user.status._ == 'userStatusEmpty') {
return 'offline';
}
if (user.status._ == 'userStatusOnline') {
return 'online';
}
return 'last seen ' + $filter('relativeTime')(user.status.was_online);
}
}])
.filter('chatTitle', [function() {
return function (chat) {
if (!chat || !chat.title) {
return 'DELETED';
}
return chat.title;
}
}])
.filter('dateOrTime', ['$filter', function($filter) {
return function (timestamp) {
var ticks = timestamp * 1000,
diff = Math.abs(+new Date() - ticks),
format = 'HH:mm';
if (diff > 518400000) { // 6 days
format = 'shortDate';
}
else if (diff > 43200000) { // 12 hours
format = 'EEE';
}
return $filter('date')(ticks, format);
}
}])
.filter('duration', [function() {
return function (duration) {
var secs = duration % 60,
mins = Math.floor((duration - secs) / 60.0);
if (secs < 10) {
secs = '0' + secs;
}
return mins + ':' + secs;
}
}])
.filter('phoneNumber', [function() {
return function (phoneRaw) {
if (phoneRaw.charAt(0) == '7') {
return '+' + phoneRaw.charAt(0) + ' (' + phoneRaw.substr(1, 3) + ') ' + phoneRaw.substr(4, 3) + '-' + phoneRaw.substr(7, 2) + '-' + phoneRaw.substr(9, 2);
}
return '+' + phoneRaw;
}
}])
.filter('formatSize', [function () {
return function (size) {
return Math.round(size / 1024) + 'Kb';
}
}])
.filter('nl2br', [function () {
return function (text) {
return text.replace(/\n/g, '<br/>');
}
}])
.filter('richText', ['$filter', function ($filter) {
return function (text) {
return $filter('linky')(text, '_blank').replace(/\n|&#10;/g, '<br/>');
}
}])
.filter('relativeTime', ['$filter', function($filter) {
return function (timestamp) {
var ticks = timestamp * 1000,
diff = Math.abs(+new Date() - ticks);
if (diff < 60000) {
return 'just now';
}
if (diff < 3000000) {
return Math.ceil(diff / 60000) + ' minutes ago';
}
if (diff < 10000000) {
return Math.ceil(diff / 3600000) + ' hours ago';
}
return $filter('dateOrTime')(timestamp);
}
}])

21
js/lib/aes_worker.js

@ -0,0 +1,21 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
importScripts('mtproto.js', '../../vendor/jsbn/jsbn_combined.js', '../../vendor/cryptoJS/crypto.js');
onmessage = function (e) {
// console.log('AES worker in', e.data);
var taskID = e.data.taskID,
result;
if (e.data.task == 'encrypt') {
result = aesEncrypt(e.data.bytes, e.data.keyBytes, e.data.ivBytes);
} else {
result = aesDecrypt(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes);
}
postMessage({taskID: taskID, result: result});
}

14
js/lib/config.js

File diff suppressed because one or more lines are too long

2612
js/lib/mtproto.js

File diff suppressed because it is too large Load Diff

12
js/lib/pq_worker.js

@ -0,0 +1,12 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
importScripts('mtproto.js', 'jsbn.js');
onmessage = function (e) {
postMessage(pqPrimeFactorization(e.data));
}

14
js/lib/sha1_worker.js

@ -0,0 +1,14 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
importScripts('mtproto.js', '../../vendor/cryptoJS/crypto.js');
onmessage = function (e) {
var taskID = e.data.taskID;
postMessage({taskID: taskID, result: sha1Hash(e.data.bytes)});
}

1439
js/services.js

File diff suppressed because it is too large Load Diff

55
js/util.js

@ -0,0 +1,55 @@
/*!
* Webogram v0.1 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
function checkClick (e, noprevent) {
if (e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) {
return true;
}
if (!noprevent) {
e.preventDefault();
}
return false;
}
function checkDragEvent(e) {
if (!e || e.target && (e.target.tagName == 'IMG' || e.target.tagName == 'A')) return false;
if (e.dataTransfer && e.dataTransfer.types) {
for (var i = 0; i < e.dataTransfer.types.length; i++) {
if (e.dataTransfer.types[i] == 'Files') {
return true;
}
}
} else {
return true;
}
return false;
}
function cancelEvent (event) {
event = event || window.event;
event.stopPropagation && event.stopPropagation();
event.preventDefault && event.preventDefault();
return false;
}
function onCtrlEnter (textarea, cb) {
$(textarea).on('keydown', function (e) {
if (e.keyCode == 13 && (e.ctrlKey || e.metaKey)) {
cb();
return cancelEvent(e);
}
});
}
function onContentLoaded (cb) {
setTimeout(cb, 0);
};

18
manifest.json

@ -0,0 +1,18 @@
{
"name": "Telegram UNOFFICIAL",
"version": "0.0.9",
"short_name": "Webogram",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["vendor/angular/angular.js", "js/background.js"]
}
},
"permissions": [
"webview",
{"fileSystem": ["write"]},
"storage"
],
"icons": { "16": "img/icons/icon16.png",
"128": "img/icons/icon128.png" }
}

41
partials/chat_modal.html

@ -0,0 +1,41 @@
<div class="chat_modal_wrap">
<div class="modal-header">
<!-- <a class="modal-close-link" ng-click="$close()">Close</a> -->
<h4 class="modal-title">Group Info</h4>
</div>
<div class="modal-body">
<div class="chat_modal_image_wrap pull-left">
<img class="chat_modal_image" my-load-thumb thumb="chatFull.thumb"/>
</div>
<div class="chat_modal_info_wrap clearfix">
<h4 class="chat_modal_header">{{chatFull.chat | chatTitle}}</h4>
<p class="chat_modal_members_count" ng-if="chatFull.participants._ == 'chatParticipants'">
<ng-pluralize count="chatFull.participants.participants.length"
when="{'0': 'No members', 'one': '1 member', 'other': '{} members'}">
</ng-pluralize>
</p>
<button class="btn btn-link chat_modal_invite_btn">Add member</button>
</div>
<h5 class="chat_modal_members_header">Members</h5>
<div class="chat_modal_members_list">
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants">
<a ng-click="openUser(participant.user_id)" class="chat_modal_participant_photo pull-left">
<img class="chat_modal_participant_photo" my-load-thumb thumb="participant.userPhoto"/>
</a>
<div class="chat_modal_participant_name">{{participant.user | userName}}</div>
<!--div class="chat_modal_participant_desc">invited by {{participant.inviter | userName}} {{participant.date | dateOrTime}}</div-->
<div class="chat_modal_participant_status">{{participant.user | userStatus}}</div>
</div>
</div>
</div>
</div>

73
partials/dialog.html

@ -0,0 +1,73 @@
<a class="im_dialog clearfix" href="#/im?p={{dialogMessage.peerString}}">
<div class="im_dialog_meta pull-right text-right">
<div class="im_dialog_date">
{{dialogMessage.date | dateOrTime}}
</div>
<span class="im_dialog_badge badge" ng-show="dialogMessage.unreadCount > 0">
{{dialogMessage.unreadCount}}
</span>
</div>
<div class="im_dialog_photo pull-left">
<img class="im_dialog_photo" my-load-thumb thumb="dialogMessage.peerPhoto" aaa="{{dialogMessage.peerPhoto.location}}" />
</div>
<div class="im_dialog_message_wrap">
<div class="im_dialog_peer">
<span class="im_dialog_chat" ng-if="dialogMessage.chatID">
<i class="icon icon-group"></i> <span ng-bind-html="dialogMessage.peerData.rTitle"></span>
</span>
<span class="im_dialog_user" ng-if="dialogMessage.peerID > 0" ng-bind-html="dialogMessage.peerData.rFullName"></span>
</div>
<div class="im_dialog_message">
<span class="im_dialog_chat_from_wrap">
<span class="im_dialog_chat_from" ng-if="!dialogMessage.out &amp;&amp; dialogMessage.chatID" ng-bind-html="dialogMessage.fromUser.rFirstName"></span><span class="im_dialog_chat_from" ng-if="dialogMessage.out">You</span>{{((dialogMessage.out || dialogMessage.peerID &lt; 0) &amp;&amp; (dialogMessage.message.length || dialogMessage.media &amp;&amp; dialogMessage.media._ != 'messageMediaEmpty')) ? ':' : ''}}
</span>
<span class="im_dialog_message_media" ng-if="dialogMessage.media &amp;&amp; dialogMessage.media._ != 'messageMediaEmpty'" ng-switch="dialogMessage.media._">
<span ng-switch-when="messageMediaPhoto">
<span class="glyphicon glyphicon-camera"></span> Photo
</span>
<span ng-switch-when="messageMediaVideo">
<span class="glyphicon glyphicon-facetime-video"></span> Video
</span>
<span ng-switch-when="messageMediaDocument">
<span class="glyphicon glyphicon-document"></span> Document
</span>
<span ng-switch-when="messageMediaGeo">
<span class="glyphicon glyphicon-map-marker"></span> Location
</span>
<span ng-switch-when="messageMediaContact">
<span class="glyphicon glyphicon-user"></span> Contact
</span>
</span>
<span class="im_dialog_message_service" ng-if="dialogMessage._ == 'messageService'" ng-switch="dialogMessage.action._">
<span ng-switch-when="messageActionChatCreate">
created the group
</span>
<span ng-switch-when="messageActionChatEditTitle">
changed group name
</span>
<span ng-switch-when="messageActionChatEditPhoto">
changed group photo
</span>
<span ng-switch-when="messageActionChatDeletePhoto">
removed group photo
</span>
<span ng-switch-when="messageActionChatAddUser">
invited <span ng-bind-html="dialogMessage.action.user.rFullName"></span>
</span>
<span ng-switch-when="messageActionChatDeleteUser">
kicked <span ng-bind-html="dialogMessage.action.user.rFullName"></span>
</span>
</span>
<span class="im_dialog_message_text" ng-if="dialogMessage.message" ng-bind-html="dialogMessage.richMessage"></span>
</div>
</div>
</a>

17
partials/head.html

@ -0,0 +1,17 @@
<div class="tg_page_head">
<div class="navbar navbar-static-top navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="https://zhukov.github.io/webogram"><img src="img/Logo_2x.png" class="tg_head_logo" alt="Telegram logo" width="110" height="31" /> <span class="navbar-brand-alpha font-light">alpha</span></a>
</div>
<div class="navbar-collapse collapse">
<!-- <ul class="nav navbar-nav"></ul> -->
<ul class="nav navbar-nav navbar-right">
<li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li>
<li ng-if="isLoggedIn"><a href="" ng-click="logOut()">Log out</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>

118
partials/im.html

@ -0,0 +1,118 @@
<div ng-include="'partials/head.html'"></div>
<div class="im_page_wrap">
<div class="im_page_split clearfix">
<div class="im_dialogs_col_wrap" ng-controller="AppImDialogsController">
<div class="im_dialogs_search">
<input class="form-control im_dialogs_search_field" type="search" placeholder="Search" ng-model="searchQuery"/>
</div>
<div my-dialogs-list class="im_dialogs_col">
<div class="im_dialogs_wrap nano">
<div class="im_dialogs_scrollable_wrap content">
<ul class="nav nav-pills nav-stacked">
<li my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs | filter:searchQuery" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li>
</ul>
</div>
</div>
</div>
</div>
<div class="im_history_col_wrap" ng-controller="AppImHistoryController">
<div ng-if="state.notSelected" class="im_history_not_selected">No history to display</div>
<div ng-if="state.loaded">
<div my-history class="im_history_col">
<div class="im_history_panel_wrap">
<div class="im_history_panel clearfix" ng-controller="AppImPanelController">
<div class="im_history_panel_title">
<div ng-if="historyPeer.id < 0">
<h4>
<!-- <a class="im_history_panel_info_link pull-right" ng-click="openChat(-historyPeer.id)">Info</a> -->
<span ng-bind-html="historyPeer.data.rTitle"></span>
<small class="im_chat_users">
<ng-pluralize count="historyPeer.data.participants_count"
when="{'0': 'No members', 'one': '1 member', 'other': '{} members'}">
</ng-pluralize>
</small>
</h4>
</div>
<div ng-if="historyPeer.id > 0">
<h4>
<!-- <a class="im_history_panel_info_link pull-right" ng-click="openUser(historyPeer.id)">Info</a> -->
<span ng-bind-html="historyPeer.data.rFullName"></span>
<small class="im_peer_online">{{historyPeer.data | userStatus}}</small>
</h4>
</div>
</div>
</div>
</div>
<div class="im_history_wrap nano">
<div class="im_history_scrollable_wrap content">
<div class="im_history_scrollable">
<div class="im_history">
<div class="im_history_message_wrap" my-message ng-repeat="historyMessage in history"></div>
</div>
<div class="im_history_typing_wrap">
<div class="im_history_typing" ng-animate="{enter:'animate-show', leave:'animate-hide'}" ng-if="typing.user">
<strong class="im_history_typing_author" ng-bind-html="typing.user.rFullName"></strong> is typing..
</div>
</div>
</div>
</div>
</div>
<div class="im_send_form_wrap clearfix" ng-controller="AppImSendController">
<div class="pull-right im_panel_peer_photo" ng-click="openUser(historyPeer.id)" ng-if="historyPeer.id > 0">
<img class="im_panel_peer_photo" my-load-thumb thumb="historyPeer.photo" />
<i class="icon im_panel_peer_online" ng-show="historyPeer.data.status._ == 'userStatusOnline'"></i>
</div>
<div class="pull-right im_panel_peer_photo" ng-click="openChat(-historyPeer.id)" ng-if="historyPeer.id < 0">
<img class="im_panel_peer_photo" my-load-thumb thumb="historyPeer.photo" />
</div>
<div class="pull-left im_panel_own_photo">
<img class="im_panel_own_photo" my-load-thumb thumb="ownPhoto" />
</div>
<form my-send-form draft-message="draftMessage" class="im_send_form" ng-submit="sendMessage($event)">
<div class="im_send_dropbox_wrap"> Drop photos here to send </div>
<textarea ng-model="draftMessage.text" placeholder="Write a message..." class="form-control im_message_field"></textarea>
<div class="im_emoji_btn pull-right">
<i class="icon icon-emoji"></i>
</div>
<div class="im_attach pull-right">
<input type="file" class="im_attach_input" size="28" multiple="true" />
<i class="icon icon-paperclip"></i>
</div>
<button type="submit" class="btn btn-tg im_submit">Send</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>

28
partials/login.html

@ -0,0 +1,28 @@
<div ng-include="'partials/head.html'"></div>
<div class="login_form_wrap">
<div class="error" ng-if="error.message">{{ error.message }}</div>
<form name="mySendCodeForm" ng-if="!credentials.phone_code_hash" ng-submit="sendCode()">
<h3 class="login_form_head">Sign in</h3>
<p class="login_form_lead">Welcome to an experimental web-client of Telegram messenger</p>
<div class="form-group" ng-class="{'has-error': error.field == 'phone'}">
<label class="control-label" for="phone_number" ng-if="error.field == 'phone'">Incorrect phone number</label>
<input type="text" class="form-control" name="phone_number" ng-model="credentials.phone_number" placeholder="Enter your phone" required>
</div>
<button class="btn btn-tg btn-block" type="submit">Next</button>
</form>
<form name="myLoginForm" ng-if="credentials.phone_code_hash" ng-submit="logIn()">
<h3 class="login_form_head">{{ credentials.phone_number | phoneNumber }} <small>(<a href="#/auth">edit</a>)</small></h3>
<p class="login_form_lead">We have sent you a code via SMS.<br/>Please enter it below.</p>
<div class="form-group" ng-class="{'has-error': error.field == 'phone_code'}">
<label class="control-label" for="phone_code" ng-if="error.field == 'phone_code'">Incorrect SMS code</label>
<input type="text" class="form-control" name="phone_code" ng-model="credentials.phone_code" placeholder="Enter your code" required>
</div>
<button class="btn btn-tg btn-block" type="submit">Sign in</button>
</form>
</div>

91
partials/message.html

@ -0,0 +1,91 @@
<div class="im_message_wrap clearfix" ng-class="{im_message_out: historyMessage.out}">
<div class="im_service_message_wrap" ng-if="historyMessage._ == 'messageService'">
<div class="im_service_message">
<a ng-click="openUser(historyMessage.from_id)" class="im_message_author" ng-bind-html="historyMessage.fromUser.rFirstName"></a>
<span class="im_message_service" ng-switch="historyMessage.action['_']">
<span ng-switch-when="messageActionChatCreate">
created the group &laquo;<strong>{{historyMessage.action.title}}</strong>&raquo;
</span>
<span ng-switch-when="messageActionChatEditTitle">
changed group name to &laquo;<strong>{{historyMessage.action.title}}</strong>&raquo;
</span>
<span ng-switch-when="messageActionChatEditPhoto">
changed group photo
</span>
<span ng-switch-when="messageActionChatDeletePhoto">
removed group photo
</span>
<span ng-switch-when="messageActionChatAddUser">
invited <span ng-bind-html="historyMessage.action.user.rFullName"></span>
</span>
<span ng-switch-when="messageActionChatDeleteUser">
kicked <span ng-bind-html="historyMessage.action.user.rFullName"></span>
</span>
<span ng-switch-default>
unsupported action {{historyMessage.action}}
</span>
</span>
</div>
<a ng-if="historyMessage.action._ == 'messageActionChatEditPhoto'" class="im_service_message_photo_thumb" href="" ng-click="openPhoto(historyMessage.action.photo.id)">
<img class="im_service_message_photo_thumb" my-load-thumb thumb="historyMessage.action.photo.thumb" width="{{historyMessage.action.photo.thumb.width}}" height="{{historyMessage.action.photo.thumb.height}}" />
</a>
</div>
<div class="im_content_message_wrap" ng-if="historyMessage._ != 'messageService'">
<i ng-if="historyMessage.unread" class="icon icon-message-status-unread"></i>
<a ng-click="openUser(historyMessage.from_id)" class="im_message_from_photo pull-left">
<img class="im_message_from_photo" my-load-thumb thumb="historyMessage.fromPhoto"/>
</a>
<div class="im_message_meta pull-right text-right">
<i ng-if="historyMessage.out" class="icon icon-message-status-tick" ng-class="{'message-status-delivered-tick': true, 'message-status-unread-tick': historyMessage.unread}"></i>
<span class="im_message_date">{{historyMessage.date | dateOrTime}}</span>
</div>
<div class="im_message_body">
<div class="im_message_author" ng-bind-html="historyMessage.fromUser.rFirstName"></div>
<div class="im_message_media" ng-if="historyMessage.media &amp;&amp; historyMessage.media._ != 'messageMediaEmpty'" ng-switch="historyMessage.media._">
<a ng-switch-when="messageMediaPhoto" class="im_message_photo_thumb" href="" ng-click="openPhoto(historyMessage.media.photo.id)" >
<img class="im_message_photo_thumb" my-load-thumb thumb="historyMessage.media.photo.thumb" width="{{historyMessage.media.photo.thumb.width}}" height="{{historyMessage.media.photo.thumb.height}}" />
</a>
<a ng-switch-when="messageMediaVideo" class="im_message_video_thumb" href="" ng-click="openVideo(historyMessage.media.video.id)">
<img class="im_message_video_thumb" my-load-thumb thumb="historyMessage.media.video.thumb" width="{{historyMessage.media.video.thumb.width}}" height="{{historyMessage.media.video.thumb.height}}" />
<div class="im_message_video_duration_wrap" style="width: {{historyMessage.media.video.thumb.width}}px;">
<span class="im_message_video_duration pull-right">{{historyMessage.media.video.duration | duration}}</span>
<span class="glyphicon glyphicon-facetime-video"></span>
</div>
</a>
<a ng-switch-when="messageMediaDocument" class="im_message_document" href="" ng-click="openDoc(historyMessage.media.document.id)">
<i class="icon icon-document"></i>
<div class="im_message_document_name"><strong>{{historyMessage.media.document.file_name}}</strong> {{historyMessage.media.document.size | formatSize}}</div>
</a>
<a ng-switch-when="messageMediaGeo" my-map-point point="historyMessage.media.geo" class="im_message_geopoint">
<i class="icon icon-geo-point"></i>
</a>
<div ng-switch-when="messageMediaContact">
<a ng-click="openUser(historyMessage.media.user.id)" class="im_message_contact_photo pull-left" ng-if="historyMessage.media.user">
<img class="im_message_contact_photo" my-load-thumb thumb="historyMessage.media.userPhoto"/>
</a>
<div class="im_message_contact_name"><span class="glyphicon glyphicon-user"></span> {{historyMessage.media.first_name}} {{historyMessage.media.last_name}}</div>
<div class="im_message_contact_phone">{{historyMessage.media.phone_number}}</div>
</div>
</div>
<div class="im_message_text" ng-if="historyMessage.message" ng-bind-html="historyMessage.richMessage"></div>
</div>
</div>
</div>

11
partials/photo_modal.html

@ -0,0 +1,11 @@
<div class="media_modal_wrap photo_modal_wrap">
<div class="modal-body">
<div class="photo_modal_image_wrap" my-load-full-photo full-photo="photo.full" thumb-location="photo.thumb.location" ng-click="$close()"> </div>
<p class="media_modal_info">From: <span class="media_modal_author">{{photo.fromUser | userName}}</span>, {{photo.date | dateOrTime}}</p>
</div>
</div>

25
partials/user_modal.html

@ -0,0 +1,25 @@
<div class="user_modal_wrap">
<div class="modal-header">
<!-- <a class="modal-close-link" ng-click="$close()">Close</a> -->
<h4 class="modal-title">Info</h4>
</div>
<div class="modal-body">
<div class="user_modal_image_wrap pull-left">
<img class="user_modal_image" my-load-thumb thumb="user.thumb"/>
</div>
<div class="user_modal_info_wrap clearfix">
<h4 class="user_modal_header">{{user | userName}}</h4>
<p class="user_modal_status" ng-if="user.status">{{user | userStatus}}</p>
<p class="user_modal_phone" ng-if="user.phone">{{user.phone | phoneNumber}}</p>
<button class="btn btn-link user_modal_send_btn" ng-click="goToHistory()">Send message</button>
</div>
</div>
</div>

11
partials/video_modal.html

@ -0,0 +1,11 @@
<div class="media_modal_wrap video_modal_wrap">
<div class="modal-body">
<div class="video_modal_image_wrap" my-load-video video="video"></div>
<p class="media_modal_info">From: <span class="media_modal_author">{{video.fromUser | userName}}</span>, {{video.date | dateOrTime}}</p>
</div>
</div>

5
partials/welcome.html

@ -0,0 +1,5 @@
<!-- <div class="jumbotron">
<h1>Welcome to Telegram!</h1>
<p>You are currently not authorized. Would you like to log in?</p>
<p><a class="btn btn-primary btn-lg" role="button" href="#/login">Log in</a></p>
</div> -->

81
vendor/README.md vendored

@ -0,0 +1,81 @@
## Third party libraries
### [AngularJS](http://angularjs.org/)
**Author**: Google, Inc.
**License**: MIT, https://github.com/angular/angular.js/blob/master/LICENSE
### [JSBN](http://www-cs-students.stanford.edu/~tjw/jsbn/)
**Author**: Tom Wu
**License**: BSD, http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE
Biginteger for RSA, PQ prime factorization
### [jQuery Emojiarea](https://github.com/diy/jquery-emojiarea)
**Author**: diy
**License**: Apache, Version 2.0, https://github.com/diy/jquery-emojiarea#license
Emoji keyboard and rich-textfield for composing messages with emoticons
### [UI Bootstrap](http://angular-ui.github.io/bootstrap/)
**Author**: AngularUI Team, https://github.com/organizations/angular-ui/teams/291112
**License**: MIT, https://github.com/angular-ui/bootstrap/blob/master/LICENSE
Modal windows
### [jQuery](https://github.com/jquery/jquery)
**Author**: jQuery Foundation and other contributors
**License**: MIT, https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt
Dom manupulations.
### [Bootstrap](https://github.com/twbs/bootstrap)
**Author**: Twitter, Inc
**License**: MIT, https://github.com/twbs/bootstrap/blob/master/LICENSE
Normalize, CSS-framework
### [nanoScrollerJS](https://github.com/jamesflorentino/nanoScrollerJS)
**Author**: James Florentino
**License**: MIT, https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE-MIT
Beautiful OS X Lion-like scrollbars
### [CryptoJS](https://code.google.com/p/crypto-js/)
**Author**: Jeff Mott
**License**: BSD-3-Clause, https://code.google.com/p/crypto-js/wiki/License
AES, SHA-1 implementation
### [zlib.js](https://github.com/imaya/zlib.js)
**Author**: imaya
**License**: MIT, https://github.com/imaya/zlib.js/blob/master/LICENSE
GZIP for decompressing server responses
### [emoji-data](https://github.com/iamcal/emoji-data)
**Author**: iamcal
**License**: not specified
Build emoji list in apropriate format. Generate sheet in future, when needed.
### [gemoji](https://github.com/github/gemoji)
**Author**: GitHub Inc, 37signals, id Software, whynne@deviantart, Apple Inc.
**License**: https://github.com/github/gemoji/blob/master/LICENSE
Emoji images

1294
vendor/angular/angular-animate.js vendored

File diff suppressed because it is too large Load Diff

22
vendor/angular/angular-animate.min.js vendored

@ -0,0 +1,22 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(C,k,F){'use strict';k.module("ngAnimate",["ng"]).config(["$provide","$animateProvider",function(M,G){var p=k.noop,r=k.forEach,N=G.$$selectors,T=1,h="$$ngAnimateState",H="ng-animate",l={running:!0};M.decorator("$animate",["$delegate","$injector","$sniffer","$rootElement","$timeout","$rootScope","$document",function(v,C,I,g,s,q,F){function O(a){if(a){var d=[],c={};a=a.substr(1).split(".");(I.transitions||I.animations)&&a.push("");for(var e=0;e<a.length;e++){var b=a[e],h=N[b];h&&!c[b]&&(d.push(C.get(h)),
c[b]=!0)}return d}}function m(a,d,c,e,b,l,q){function w(a){t();if(!0===a)u();else{if(a=c.data(h))a.done=u,c.data(h,a);m(x,"after",u)}}function m(e,b,h){var k=b+"End";r(e,function(l,f){var B=function(){a:{var B=b+"Complete",a=e[f];a[B]=!0;(a[k]||p)();for(a=0;a<e.length;a++)if(!e[a][B])break a;h()}};"before"!=b||"enter"!=a&&"move"!=a?l[b]?l[k]=y?l[b](c,d,B):l[b](c,B):B():B()})}function g(){q&&s(q,0,!1)}function t(){t.hasBeenRun||(t.hasBeenRun=!0,l())}function u(){if(!u.hasBeenRun){u.hasBeenRun=!0;var a=
c.data(h);a&&(y?z(c):(a.closeAnimationTimeout=s(function(){z(c)},0,!1),c.data(h,a)));g()}}var k=c.attr("class")||"",v=(" "+(k+" "+d)).replace(/\s+/g,".");e||(e=b?b.parent():c.parent());var v=O(v),y="addClass"==a||"removeClass"==a;b=c.data(h)||{};if(J(c,e)||0===v.length)t(),u();else{var x=[];b.running&&y&&b.structural||r(v,function(b){if(!b.allowCancel||b.allowCancel(c,a,d)){var e=b[a];"leave"==a?(b=e,e=null):b=b["before"+a.charAt(0).toUpperCase()+a.substr(1)];x.push({before:b,after:e})}});0===x.length?
(t(),g()):(e=" "+k+" ",b.running&&(s.cancel(b.closeAnimationTimeout),z(c),L(b.animations),b.beforeComplete?(b.done||p)(!0):y&&!b.structural&&(e="removeClass"==b.event?e.replace(b.className,""):e+b.className+" ")),k=" "+d+" ","addClass"==a&&0<=e.indexOf(k)||"removeClass"==a&&-1==e.indexOf(k)?(t(),g()):(c.addClass(H),c.data(h,{running:!0,event:a,className:d,structural:!y,animations:x,done:w}),m(x,"before",w)))}}function E(a){a=a[0];a.nodeType==T&&r(a.querySelectorAll("."+H),function(a){a=k.element(a);
var c=a.data(h);c&&(L(c.animations),z(a))})}function L(a){r(a,function(d){a.beforeComplete||(d.beforeEnd||p)(!0);a.afterComplete||(d.afterEnd||p)(!0)})}function z(a){a[0]==g[0]?l.disabled||(l.running=!1,l.structural=!1):(a.removeClass(H),a.removeData(h))}function J(a,d){if(l.disabled)return!0;if(a[0]==g[0])return l.disabled||l.running;do{if(0===d.length)break;var c=d[0]==g[0],e=c?l:d.data(h),e=e&&(!!e.disabled||!!e.running);if(c||e)return e;if(c)break}while(d=d.parent());return!0}g.data(h,l);q.$$postDigest(function(){q.$$postDigest(function(){l.running=
!1})});return{enter:function(a,d,c,e){this.enabled(!1,a);v.enter(a,d,c);q.$$postDigest(function(){m("enter","ng-enter",a,d,c,p,e)})},leave:function(a,d){E(a);this.enabled(!1,a);q.$$postDigest(function(){m("leave","ng-leave",a,null,null,function(){v.leave(a)},d)})},move:function(a,d,c,e){E(a);this.enabled(!1,a);v.move(a,d,c);q.$$postDigest(function(){m("move","ng-move",a,d,c,p,e)})},addClass:function(a,d,c){m("addClass",d,a,null,null,function(){v.addClass(a,d)},c)},removeClass:function(a,d,c){m("removeClass",
d,a,null,null,function(){v.removeClass(a,d)},c)},enabled:function(a,d){switch(arguments.length){case 2:if(a)z(d);else{var c=d.data(h)||{};c.disabled=!0;d.data(h,c)}break;case 1:l.disabled=!a;break;default:a=!l.disabled}return!!a}}}]);G.register("",["$window","$sniffer","$timeout",function(l,h,I){function g(f){R.push(f);I.cancel(S);S=I(function(){r(R,function(f){f()});R=[];S=null;D={}},10,!1)}function s(f,a){var K=a?D[a]:null;if(!K){var b=0,c=0,d=0,e=0,h,k,g,m;r(f,function(f){if(f.nodeType==T){f=l.getComputedStyle(f)||
{};g=f[A+G];b=Math.max(q(g),b);m=f[A+t];h=f[A+u];c=Math.max(q(h),c);k=f[w+u];e=Math.max(q(k),e);var a=q(f[w+G]);0<a&&(a*=parseInt(f[w+M],10)||1);d=Math.max(a,d)}});K={total:0,transitionPropertyStyle:m,transitionDurationStyle:g,transitionDelayStyle:h,transitionDelay:c,transitionDuration:b,animationDelayStyle:k,animationDelay:e,animationDuration:d};a&&(D[a]=K)}return K}function q(f){var a=0;f=k.isString(f)?f.split(/\s*,\s*/):[];r(f,function(f){a=Math.max(parseFloat(f)||0,a)});return a}function H(f){var a=
f.parent(),b=a.data(V);b||(a.data(V,++U),b=U);return b+"-"+f[0].className}function O(f,a){var b=H(f),c=b+" "+a,d={},e=D[c]?++D[c].total:0;if(0<e){var h=a+"-stagger",d=b+" "+h;(b=!D[d])&&f.addClass(h);d=s(f,d);b&&f.removeClass(h)}f.addClass(a);c=s(f,c);h=Math.max(c.transitionDuration,c.animationDuration);if(0===h)return f.removeClass(a),!1;var k="";0<c.transitionDuration?(f.addClass(x),k+=N+" ",f[0].style[A+t]="none"):f[0].style[w]="none 0s";r(a.split(" "),function(a,f){k+=(0<f?" ":"")+a+"-active"});
f.data(y,{className:a,activeClassName:k,maxDuration:h,classes:a+" "+k,timings:c,stagger:d,ii:e});return!0}function m(a){a=a[0];var b=A+t;a.style[b]&&0<a.style[b].length&&(a.style[b]="")}function E(a){var b=a[0],c=w;b.style[c]&&0<b.style[c].length&&(a[0].style[c]="")}function L(a,d,e){function k(a){a.stopPropagation();a=a.originalEvent||a;var f=a.$manualTimeStamp||a.timeStamp||Date.now();Math.max(f-w,0)>=v&&a.elapsedTime>=q&&e()}var n=a.data(y);if(a.hasClass(d)&&n){var l=a[0],g=n.timings,m=n.stagger,
q=n.maxDuration,r=n.activeClassName,v=1E3*Math.max(g.transitionDelay,g.animationDelay),w=Date.now(),t=Q+" "+P,u=n.ii,x,n="",p=[];if(0<g.transitionDuration){var s=g.transitionPropertyStyle;-1==s.indexOf("all")&&(x=!0,n+=b+"transition-property: "+s+", "+(h.msie?"-ms-zoom":"border-spacing")+"; ",n+=b+"transition-duration: "+g.transitionDurationStyle+", "+g.transitionDuration+"s; ",p.push(b+"transition-property"),p.push(b+"transition-duration"))}0<u&&(0<m.transitionDelay&&0===m.transitionDuration&&(s=
g.transitionDelayStyle,x&&(s+=", "+g.transitionDelay+"s"),n+=b+"transition-delay: "+z(s,m.transitionDelay,u)+"; ",p.push(b+"transition-delay")),0<m.animationDelay&&0===m.animationDuration&&(n+=b+"animation-delay: "+z(g.animationDelayStyle,m.animationDelay,u)+"; ",p.push(b+"animation-delay")));0<p.length&&(g=l.getAttribute("style")||"",l.setAttribute("style",g+" "+n));a.on(t,k);a.addClass(r);return function(b){a.off(t,k);a.removeClass(r);c(a,d);for(var e in p)l.style.removeProperty(p[e])}}e()}function z(a,
b,c){var d="";r(a.split(","),function(a,f){d+=(0<f?",":"")+(c*b+parseInt(a,10))+"s"});return d}function J(a,b){if(O(a,b))return function(d){d&&c(a,b)}}function a(a,b,d){if(a.data(y))return L(a,b,d);c(a,b);d()}function d(f,b,c){var d=J(f,b);if(d){var e=d;g(function(){m(f);E(f);e=a(f,b,c)});return function(a){(e||p)(a)}}c()}function c(a,b){a.removeClass(b);a.removeClass(x);a.removeData(y)}function e(a,b){var c="";a=k.isArray(a)?a:a.split(/\s+/);r(a,function(a,f){a&&0<a.length&&(c+=(0<f?" ":"")+a+b)});
return c}var b="",A,P,w,Q;C.ontransitionend===F&&C.onwebkittransitionend!==F?(b="-webkit-",A="WebkitTransition",P="webkitTransitionEnd transitionend"):(A="transition",P="transitionend");C.onanimationend===F&&C.onwebkitanimationend!==F?(b="-webkit-",w="WebkitAnimation",Q="webkitAnimationEnd animationend"):(w="animation",Q="animationend");var G="Duration",t="Property",u="Delay",M="IterationCount",V="$$ngAnimateKey",y="$$ngAnimateCSS3Data",x="ng-animate-start",N="ng-animate-active",D={},U=0,R=[],S;return{allowCancel:function(a,
b,c){var d=(a.data(y)||{}).classes;if(!d||0<=["enter","leave","move"].indexOf(b))return!0;var h=a.parent(),g=k.element(a[0].cloneNode());g.attr("style","position:absolute; top:-9999px; left:-9999px");g.removeAttr("id");g.html("");r(d.split(" "),function(a){g.removeClass(a)});g.addClass(e(c,"addClass"==b?"-add":"-remove"));h.append(g);a=s(g);g.remove();return 0<Math.max(a.transitionDuration,a.animationDuration)},enter:function(a,b){return d(a,"ng-enter",b)},leave:function(a,b){return d(a,"ng-leave",
b)},move:function(a,b){return d(a,"ng-move",b)},beforeAddClass:function(a,b,c){if(b=J(a,e(b,"-add")))return g(function(){m(a);E(a);c()}),b;c()},addClass:function(b,c,d){return a(b,e(c,"-add"),d)},beforeRemoveClass:function(a,b,c){if(b=J(a,e(b,"-remove")))return g(function(){m(a);E(a);c()}),b;c()},removeClass:function(b,c,d){return a(b,e(c,"-remove"),d)}}}])}])})(window,window.angular);
//# sourceMappingURL=angular-animate.min.js.map

8
vendor/angular/angular-animate.min.js.map vendored

File diff suppressed because one or more lines are too long

202
vendor/angular/angular-cookies.js vendored

@ -0,0 +1,202 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/**
* @ngdoc overview
* @name ngCookies
* @description
*
* # ngCookies
*
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
*
* {@installModule cookies}
*
* <div doc-module-components="ngCookies"></div>
*
* See {@link ngCookies.$cookies `$cookies`} and
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
*/
angular.module('ngCookies', ['ng']).
/**
* @ngdoc object
* @name ngCookies.$cookies
* @requires $browser
*
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from
* this object, new cookies are created/deleted at the end of current $eval.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* @example
<doc:example>
<doc:source>
<script>
function ExampleController($cookies) {
// Retrieving a cookie
var favoriteCookie = $cookies.myFavorite;
// Setting a cookie
$cookies.myFavorite = 'oatmeal';
}
</script>
</doc:source>
</doc:example>
*/
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
var cookies = {},
lastCookies = {},
lastBrowserCookies,
runEval = false,
copy = angular.copy,
isUndefined = angular.isUndefined;
//creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() {
var currentCookies = $browser.cookies();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies);
copy(currentCookies, cookies);
if (runEval) $rootScope.$apply();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were
* stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
}
}
//update all cookies updated in $cookies
for(name in cookies) {
value = cookies[name];
if (!angular.isString(value)) {
if (angular.isDefined(lastCookies[name])) {
cookies[name] = lastCookies[name];
} else {
delete cookies[name];
}
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//verify what was actually stored
if (updated){
updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
}
}
}]).
/**
* @ngdoc object
* @name ngCookies.$cookieStore
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* @example
*/
factory('$cookieStore', ['$cookies', function($cookies) {
return {
/**
* @ngdoc method
* @name ngCookies.$cookieStore#get
* @methodOf ngCookies.$cookieStore
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
get: function(key) {
var value = $cookies[key];
return value ? angular.fromJson(value) : value;
},
/**
* @ngdoc method
* @name ngCookies.$cookieStore#put
* @methodOf ngCookies.$cookieStore
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies[key] = angular.toJson(value);
},
/**
* @ngdoc method
* @name ngCookies.$cookieStore#remove
* @methodOf ngCookies.$cookieStore
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $cookies[key];
}
};
}]);
})(window, window.angular);

8
vendor/angular/angular-cookies.min.js vendored

@ -0,0 +1,8 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(p,f,n){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,k=!1,l=f.copy,m=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,l(a,g),l(a,c),k&&d.$apply())})();k=!0;d.$watch(function(){var a,e,d;for(a in g)m(c[a])&&b.cookies(a,n);for(a in c)(e=c[a],f.isString(e))?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(m(e[a])?delete c[a]:c[a]=e[a])});
return c}]).factory("$cookieStore",["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular);
//# sourceMappingURL=angular-cookies.min.js.map

8
vendor/angular/angular-cookies.min.js.map vendored

@ -0,0 +1,8 @@
{
"version":3,
"file":"angular-cookies.min.js",
"lineCount":7,
"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAoBtCD,CAAAE,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,QAAA,CA4BW,UA5BX,CA4BuB,CAAC,YAAD,CAAe,UAAf,CAA2B,QAAS,CAACC,CAAD,CAAaC,CAAb,CAAuB,CAAA,IACxEC,EAAU,EAD8D,CAExEC,EAAc,EAF0D,CAGxEC,CAHwE,CAIxEC,EAAU,CAAA,CAJ8D,CAKxEC,EAAOV,CAAAU,KALiE,CAMxEC,EAAcX,CAAAW,YAGlBN,EAAAO,UAAA,CAAmB,QAAQ,EAAG,CAC5B,IAAIC,EAAiBR,CAAAC,QAAA,EACjBE,EAAJ,EAA0BK,CAA1B,GACEL,CAGA,CAHqBK,CAGrB,CAFAH,CAAA,CAAKG,CAAL,CAAqBN,CAArB,CAEA,CADAG,CAAA,CAAKG,CAAL,CAAqBP,CAArB,CACA,CAAIG,CAAJ,EAAaL,CAAAU,OAAA,EAJf,CAF4B,CAA9B,CAAA,EAUAL,EAAA,CAAU,CAAA,CAKVL,EAAAW,OAAA,CASAC,QAAa,EAAG,CAAA,IACVC,CADU,CAEVC,CAFU,CAIVC,CAGJ,KAAKF,CAAL,GAAaV,EAAb,CACMI,CAAA,CAAYL,CAAA,CAAQW,CAAR,CAAZ,CAAJ,EACEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBhB,CAAvB,CAKJ,KAAIgB,CAAJ,GAAYX,EAAZ,CAEE,CADAY,CACK,CADGZ,CAAA,CAAQW,CAAR,CACH,CAAAjB,CAAAoB,SAAA,CAAiBF,CAAjB,CAAL,EAMWA,CANX,GAMqBX,CAAA,CAAYU,CAAZ,CANrB,GAOEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBC,CAAvB,CACA,CAAAC,CAAA,CAAU,CAAA,CARZ,EACMnB,CAAAqB,UAAA,CAAkBd,CAAA,CAAYU,CAAZ,CAAlB,CAAJ,CACEX,CAAA,CAAQW,CAAR,CADF,CACkBV,CAAA,CAAYU,CAAZ,CADlB,CAGE,OAAOX,CAAA,CAAQW,CAAR,CASb,IAAIE,CAAJ,CAIE,IAAKF,CAAL,GAFAK,EAEahB,CAFID,CAAAC,QAAA,EAEJA,CAAAA,CAAb,CACMA,CAAA,CAAQW,CAAR,CAAJ,GAAsBK,CAAA,CAAeL,CAAf,CAAtB,GAEMN,CAAA,CAAYW,CAAA,CAAeL,CAAf,CAAZ,CAAJ,CACE,OAAOX,CAAA,CAAQW,CAAR,CADT,CAGEX,CAAA,CAAQW,CAAR,CAHF,CAGkBK,CAAA,CAAeL,CAAf,CALpB,CAlCU,CAThB,CAEA;MAAOX,EA1BqE,CAA3D,CA5BvB,CAAAH,QAAA,CA4HW,cA5HX,CA4H2B,CAAC,UAAD,CAAa,QAAQ,CAACoB,CAAD,CAAW,CAErD,MAAO,KAYAC,QAAQ,CAACC,CAAD,CAAM,CAEjB,MAAO,CADHP,CACG,CADKK,CAAA,CAASE,CAAT,CACL,EAAQzB,CAAA0B,SAAA,CAAiBR,CAAjB,CAAR,CAAkCA,CAFxB,CAZd,KA4BAS,QAAQ,CAACF,CAAD,CAAMP,CAAN,CAAa,CACxBK,CAAA,CAASE,CAAT,CAAA,CAAgBzB,CAAA4B,OAAA,CAAeV,CAAf,CADQ,CA5BrB,QA0CGW,QAAQ,CAACJ,CAAD,CAAM,CACpB,OAAOF,CAAA,CAASE,CAAT,CADa,CA1CjB,CAF8C,CAAhC,CA5H3B,CApBsC,CAArC,CAAA,CAoME1B,MApMF,CAoMUA,MAAAC,QApMV;",
"sources":["angular-cookies.js"],
"names":["window","angular","undefined","module","factory","$rootScope","$browser","cookies","lastCookies","lastBrowserCookies","runEval","copy","isUndefined","addPollFn","currentCookies","$apply","$watch","push","name","value","updated","isString","isDefined","browserCookies","$cookies","get","key","fromJson","put","toJson","remove"]
}

24
vendor/angular/angular-csp.css vendored

@ -0,0 +1,24 @@
/* Include this file in your html if you are using the CSP mode. */
@charset "UTF-8";
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
.ng-cloak, .x-ng-cloak,
.ng-hide {
display: none !important;
}
ng\:form {
display: block;
}
/* The styles below ensure that the CSS transition will ALWAYS
* animate and close. A nasty bug occurs with CSS transitions where
* when the active class isn't set, or if the active class doesn't
* contain any styles to transition to, then, if ngAnimate is used,
* it will appear as if the webpage is broken due to the forever hanging
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are
* used below since they trigger a transition without making the browser
* animate anything and they're both highly underused CSS properties */
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }

410
vendor/angular/angular-loader.js vendored

@ -0,0 +1,410 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function() {'use strict';
/**
* @description
*
* This object provides a utility for producing rich Error messages within
* Angular. It can be called as follows:
*
* var exampleMinErr = minErr('example');
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
*
* The above creates an instance of minErr in the example namespace. The
* resulting error will have a namespaced error code of example.one. The
* resulting error will replace {0} with the value of foo, and {1} with the
* value of bar. The object is not restricted in the number of arguments it can
* take.
*
* If fewer arguments are specified than necessary for interpolation, the extra
* interpolation markers will be preserved in the final string.
*
* Since data will be parsed statically during a build step, some restrictions
* are applied with respect to how minErr instances are created and called.
* Instances should have names of the form namespaceMinErr for a minErr created
* using minErr('namespace') . Error codes, namespaces and template strings
* should all be static strings, not variables or general expressions.
*
* @param {string} module The namespace to use for the new minErr instance.
* @returns {function(string, string, ...): Error} instance
*/
function minErr(module) {
return function () {
var code = arguments[0],
prefix = '[' + (module ? module + ':' : '') + code + '] ',
template = arguments[1],
templateArgs = arguments,
stringify = function (obj) {
if (typeof obj === 'function') {
return obj.toString().replace(/ \{[\s\S]*$/, '');
} else if (typeof obj === 'undefined') {
return 'undefined';
} else if (typeof obj !== 'string') {
return JSON.stringify(obj);
}
return obj;
},
message, i;
message = prefix + template.replace(/\{\d+\}/g, function (match) {
var index = +match.slice(1, -1), arg;
if (index + 2 < templateArgs.length) {
arg = templateArgs[index + 2];
if (typeof arg === 'function') {
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
} else if (typeof arg === 'undefined') {
return 'undefined';
} else if (typeof arg !== 'string') {
return toJson(arg);
}
return arg;
}
return match;
});
message = message + '\nhttp://errors.angularjs.org/1.2.3/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
encodeURIComponent(stringify(arguments[i]));
}
return new Error(message);
};
}
/**
* @ngdoc interface
* @name angular.Module
* @description
*
* Interface for configuring angular {@link angular.module modules}.
*/
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
/**
* @ngdoc function
* @name angular.module
* @description
*
* The `angular.module` is a global place for creating, registering and retrieving Angular
* modules.
* All modules (angular core or 3rd party) that should be available to an application must be
* registered using this mechanism.
*
* When passed two or more arguments, a new module is created. If passed only one argument, an
* existing module (the name passed as the first argument to `module`) is retrieved.
*
*
* # Module
*
* A module is a collection of services, directives, filters, and configuration information.
* `angular.module` is used to configure the {@link AUTO.$injector $injector}.
*
* <pre>
* // Create a new module
* var myModule = angular.module('myModule', []);
*
* // register a new service
* myModule.value('appName', 'MyCoolApp');
*
* // configure existing services inside initialization blocks.
* myModule.config(function($locationProvider) {
* // Configure existing providers
* $locationProvider.hashPrefix('!');
* });
* </pre>
*
* Then you can create an injector and load your modules like this:
*
* <pre>
* var injector = angular.injector(['ng', 'MyModule'])
* </pre>
*
* However it's more likely that you'll just use
* {@link ng.directive:ngApp ngApp} or
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
* @param {Array.<string>=} requires If specified then new module is being created. If
* unspecified then the the module is being retrieved for further configuration.
* @param {Function} configFn Optional configuration function for the module. Same as
* {@link angular.Module#methods_config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** @type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke');
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_runBlocks: runBlocks,
/**
* @ngdoc property
* @name angular.Module#requires
* @propertyOf angular.Module
* @returns {Array.<string>} List of module names which must be loaded before this module.
* @description
* Holds the list of modules which the injector will load before the current module is
* loaded.
*/
requires: requires,
/**
* @ngdoc property
* @name angular.Module#name
* @propertyOf angular.Module
* @returns {string} Name of the module.
* @description
*/
name: name,
/**
* @ngdoc method
* @name angular.Module#provider
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerType Construction function for creating new instance of the
* service.
* @description
* See {@link AUTO.$provide#provider $provide.provider()}.
*/
provider: invokeLater('$provide', 'provider'),
/**
* @ngdoc method
* @name angular.Module#factory
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerFunction Function for creating new instance of the service.
* @description
* See {@link AUTO.$provide#factory $provide.factory()}.
*/
factory: invokeLater('$provide', 'factory'),
/**
* @ngdoc method
* @name angular.Module#service
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} constructor A constructor function that will be instantiated.
* @description
* See {@link AUTO.$provide#service $provide.service()}.
*/
service: invokeLater('$provide', 'service'),
/**
* @ngdoc method
* @name angular.Module#value
* @methodOf angular.Module
* @param {string} name service name
* @param {*} object Service instance object.
* @description
* See {@link AUTO.$provide#value $provide.value()}.
*/
value: invokeLater('$provide', 'value'),
/**
* @ngdoc method
* @name angular.Module#constant
* @methodOf angular.Module
* @param {string} name constant name
* @param {*} object Constant value.
* @description
* Because the constant are fixed, they get applied before other provide methods.
* See {@link AUTO.$provide#constant $provide.constant()}.
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
* @ngdoc method
* @name angular.Module#animation
* @methodOf angular.Module
* @param {string} name animation name
* @param {Function} animationFactory Factory function for creating new instance of an
* animation.
* @description
*
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
*
*
* Defines an animation hook that can be later used with
* {@link ngAnimate.$animate $animate} service and directives that use this service.
*
* <pre>
* module.animation('.animation-name', function($inject1, $inject2) {
* return {
* eventName : function(element, done) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction(element) {
* //code to cancel the animation
* }
* }
* }
* })
* </pre>
*
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
* {@link ngAnimate ngAnimate module} for more information.
*/
animation: invokeLater('$animateProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#filter
* @methodOf angular.Module
* @param {string} name Filter name.
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
*/
filter: invokeLater('$filterProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#controller
* @methodOf angular.Module
* @param {string|Object} name Controller name, or an object map of controllers where the
* keys are the names and the values are the constructors.
* @param {Function} constructor Controller constructor function.
* @description
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
*/
controller: invokeLater('$controllerProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#directive
* @methodOf angular.Module
* @param {string|Object} name Directive name, or an object map of directives where the
* keys are the names and the values are the factories.
* @param {Function} directiveFactory Factory function for creating new instance of
* directives.
* @description
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
/**
* @ngdoc method
* @name angular.Module#config
* @methodOf angular.Module
* @param {Function} configFn Execute this function on module load. Useful for service
* configuration.
* @description
* Use this method to register work which needs to be performed on module loading.
*/
config: config,
/**
* @ngdoc method
* @name angular.Module#run
* @methodOf angular.Module
* @param {Function} initializationFn Execute this function after injector creation.
* Useful for application initialization.
* @description
* Use this method to register work which should be performed when the injector is done
* loading all modules.
*/
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
/**
* @param {string} provider
* @param {string} method
* @param {String=} insertMethod
* @returns {angular.Module}
*/
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
}
setupModuleLoader(window);
})(window);
/**
* Closure compiler type information
*
* @typedef { {
* requires: !Array.<string>,
* invokeQueue: !Array.<Array.<*>>,
*
* service: function(string, Function):angular.Module,
* factory: function(string, Function):angular.Module,
* value: function(string, *):angular.Module,
*
* filter: function(string, Function):angular.Module,
*
* init: function(Function):angular.Module
* } }
*/
angular.Module;

9
vendor/angular/angular-loader.min.js vendored

@ -0,0 +1,9 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(){'use strict';function d(a){return function(){var c=arguments[0],b,c="["+(a?a+":":"")+c+"] http://errors.angularjs.org/1.2.3/"+(a?a+"/":"")+c;for(b=1;b<arguments.length;b++)c=c+(1==b?"?":"&")+"p"+(b-1)+"="+encodeURIComponent("function"==typeof arguments[b]?arguments[b].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[b]?"undefined":"string"!=typeof arguments[b]?JSON.stringify(arguments[b]):arguments[b]);return Error(c)}}(function(a){var c=d("$injector"),b=d("ng");a=a.angular||
(a.angular={});a.$$minErr=a.$$minErr||d;return a.module||(a.module=function(){var a={};return function(e,d,f){if("hasOwnProperty"===e)throw b("badname","module");d&&a.hasOwnProperty(e)&&(a[e]=null);return a[e]||(a[e]=function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return g}}if(!d)throw c("nomod",e);var b=[],h=[],k=a("$injector","invoke"),g={_invokeQueue:b,_runBlocks:h,requires:d,name:e,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide",
"service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){h.push(a);return this}};f&&k(f);return g}())}}())})(window)})(window);
//# sourceMappingURL=angular-loader.min.js.map

8
vendor/angular/angular-loader.min.js.map vendored

@ -0,0 +1,8 @@
{
"version":3,
"file":"angular-loader.min.js",
"lineCount":8,
"mappings":"A;;;;;aAMC,SAAQ,EAAG,CCNZA,QAAS,EAAM,CAAC,CAAD,CAAS,CAWtB,MAAO,SAAS,EAAG,CAAA,IACb,EAAO,SAAA,CAAU,CAAV,CADM,CAIf,CAJe,CAKjB,EAHW,GAGX,EAHkB,CAAA,CAAS,CAAT,CAAkB,GAAlB,CAAwB,EAG1C,EAHgD,CAGhD,CAAmB,sCAAnB,EAA2D,CAAA,CAAS,CAAT,CAAkB,GAAlB,CAAwB,EAAnF,EAAyF,CACzF,KAAK,CAAL,CAAS,CAAT,CAAY,CAAZ,CAAgB,SAAA,OAAhB,CAAkC,CAAA,EAAlC,CACE,CAAA,CAAU,CAAV,EAA0B,CAAL,EAAA,CAAA,CAAS,GAAT,CAAe,GAApC,EAA2C,GAA3C,EAAkD,CAAlD,CAAoD,CAApD,EAAyD,GAAzD,CACE,kBAAA,CAjBc,UAAlB,EAAI,MAiB6B,UAAA,CAAU,CAAV,CAjBjC,CAiBiC,SAAA,CAAU,CAAV,CAhBxB,SAAA,EAAA,QAAA,CAAuB,aAAvB,CAAsC,EAAtC,CADT,CAEyB,WAAlB,EAAI,MAesB,UAAA,CAAU,CAAV,CAf1B,CACE,WADF,CAEoB,QAApB,EAAM,MAaoB,UAAA,CAAU,CAAV,CAb1B,CACE,IAAA,UAAA,CAYwB,SAAA,CAAU,CAAV,CAZxB,CADF,CAa0B,SAAA,CAAU,CAAV,CAA7B,CAEJ,OAAW,MAAJ,CAAU,CAAV,CAVU,CAXG,CD0FxBC,SAA0B,CAACC,CAAD,CAAS,CAEjC,IAAIC,EAAkBH,CAAA,CAAO,WAAP,CAAtB,CACII,EAAWJ,CAAA,CAAO,IAAP,CAMXK,EAAAA,CAAiBH,CAHZ,QAGLG;CAAiBH,CAHE,QAGnBG,CAH+B,EAG/BA,CAGJA,EAAAC,SAAA,CAAmBD,CAAAC,SAAnB,EAAuCN,CAEvC,OAAcK,EARL,OAQT,GAAcA,CARS,OAQvB,CAAiCE,QAAQ,EAAG,CAE1C,IAAIC,EAAU,EAoDd,OAAOC,SAAe,CAACC,CAAD,CAAOC,CAAP,CAAiBC,CAAjB,CAA2B,CAE7C,GAAa,gBAAb,GAKsBF,CALtB,CACE,KAAMN,EAAA,CAAS,SAAT,CAIoBS,QAJpB,CAAN,CAKAF,CAAJ,EAAgBH,CAAAM,eAAA,CAAuBJ,CAAvB,CAAhB,GACEF,CAAA,CAAQE,CAAR,CADF,CACkB,IADlB,CAGA,OAAcF,EAzET,CAyEkBE,CAzElB,CAyEL,GAAcF,CAzEK,CAyEIE,CAzEJ,CAyEnB,CAA6BH,QAAQ,EAAG,CAgNtCQ,QAASA,EAAW,CAACC,CAAD,CAAWC,CAAX,CAAmBC,CAAnB,CAAiC,CACnD,MAAO,SAAQ,EAAG,CAChBC,CAAA,CAAYD,CAAZ,EAA4B,MAA5B,CAAA,CAAoC,CAACF,CAAD,CAAWC,CAAX,CAAmBG,SAAnB,CAApC,CACA,OAAOC,EAFS,CADiC,CA/MrD,GAAI,CAACV,CAAL,CACE,KAAMR,EAAA,CAAgB,OAAhB,CAEiDO,CAFjD,CAAN,CAMF,IAAIS,EAAc,EAAlB,CAGIG,EAAY,EAHhB,CAKIC,EAASR,CAAA,CAAY,WAAZ,CAAyB,QAAzB,CALb,CAQIM,EAAiB,cAELF,CAFK,YAGPG,CAHO,UAcTX,CAdS,MAuBbD,CAvBa,UAoCTK,CAAA,CAAY,UAAZ,CAAwB,UAAxB,CApCS,SA+CVA,CAAA,CAAY,UAAZ,CAAwB,SAAxB,CA/CU,SA0DVA,CAAA,CAAY,UAAZ;AAAwB,SAAxB,CA1DU,OAqEZA,CAAA,CAAY,UAAZ,CAAwB,OAAxB,CArEY,UAiFTA,CAAA,CAAY,UAAZ,CAAwB,UAAxB,CAAoC,SAApC,CAjFS,WAmHRA,CAAA,CAAY,kBAAZ,CAAgC,UAAhC,CAnHQ,QA8HXA,CAAA,CAAY,iBAAZ,CAA+B,UAA/B,CA9HW,YA0IPA,CAAA,CAAY,qBAAZ,CAAmC,UAAnC,CA1IO,WAuJRA,CAAA,CAAY,kBAAZ,CAAgC,WAAhC,CAvJQ,QAkKXQ,CAlKW,KA8KdC,QAAQ,CAACC,CAAD,CAAQ,CACnBH,CAAAI,KAAA,CAAeD,CAAf,CACA,OAAO,KAFY,CA9KF,CAoLjBb,EAAJ,EACEW,CAAA,CAAOX,CAAP,CAGF,OAAQS,EAxM8B,CAzET,EAyE/B,CAX+C,CAtDP,CART,EAQnC,CAdiC,CAAnCpB,CA2SA,CAAkBC,MAAlB,CA/XY,CAAX,CAAA,CAgYEA,MAhYF;",
"sources":["angular-loader.js","MINERR_ASSET"],
"names":["minErr","setupModuleLoader","window","$injectorMinErr","ngMinErr","angular","$$minErr","factory","modules","module","name","requires","configFn","context","hasOwnProperty","invokeLater","provider","method","insertMethod","invokeQueue","arguments","moduleInstance","runBlocks","config","run","block","push"]
}

546
vendor/angular/angular-resource.js vendored

@ -0,0 +1,546 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
var $resourceMinErr = angular.$$minErr('$resource');
// Helper functions and regex to lookup a dotted path on an object
// stopping at undefined/null. The path must be composed of ASCII
// identifiers (just like $parse)
var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
function isValidDottedPath(path) {
return (path != null && path !== '' && path !== 'hasOwnProperty' &&
MEMBER_NAME_REGEX.test('.' + path));
}
function lookupDottedPath(obj, path) {
if (!isValidDottedPath(path)) {
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
}
var keys = path.split('.');
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
var key = keys[i];
obj = (obj !== null) ? obj[key] : undefined;
}
return obj;
}
/**
* @ngdoc overview
* @name ngResource
* @description
*
* # ngResource
*
* The `ngResource` module provides interaction support with RESTful services
* via the $resource service.
*
* {@installModule resource}
*
* <div doc-module-components="ngResource"></div>
*
* See {@link ngResource.$resource `$resource`} for usage.
*/
/**
* @ngdoc object
* @name ngResource.$resource
* @requires $http
*
* @description
* A factory which creates a resource object that lets you interact with
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
*
* The returned resource object has action methods which provide high-level behaviors without
* the need to interact with the low level {@link ng.$http $http} service.
*
* Requires the {@link ngResource `ngResource`} module to be installed.
*
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
* `/user/:username`. If you are using a URL with a port number (e.g.
* `http://example.com:8080/api`), it will be respected.
*
* If you are using a url with a suffix, just add the suffix, like this:
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
* or even `$resource('http://example.com/resource/:resource_id.:format')`
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
* can escape it with `/\.`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods. If any of the parameter value is a function, it will be executed every time
* when a param value needs to be obtained for a request (unless the param was overridden).
*
* Each key value in the parameter object is first bound to url template if present and then any
* excess keys are appended to the url search query after the `?`.
*
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
* URL `/path/greet?salutation=Hello`.
*
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
* the data object (useful for non-GET operations).
*
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
* default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#usage_parameters $http.config}:
*
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
* action2: {method:?, params:?, isArray:?, headers:?, ...},
* ...}
*
* Where:
*
* - **`action`** {string} The name of action. This name becomes the name of the method on
* your resource object.
* - **`method`** {string} HTTP request method. Valid methods are: `GET`, `POST`, `PUT`,
* `DELETE`, and `JSONP`.
* - **`params`** {Object=} Optional set of pre-bound parameters for this action. If any of
* the parameter value is a function, it will be executed every time when a param value needs to
* be obtained for a request (unless the param was overridden).
* - **`url`** {string} action specific `url` override. The url templating is supported just
* like for the resource-level urls.
* - **`isArray`** {boolean=} If true then the returned object for this action is an array,
* see `returns` section.
* - **`transformRequest`**
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
* - **`transformResponse`**
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* - **`cache`** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
* caching.
* - **`timeout`** `{number|Promise}` timeout in milliseconds, or {@link ng.$q promise} that
* should abort the request when resolved.
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
* requests with credentials} for more information.
* - **`responseType`** - `{string}` - see {@link
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
* with `http response` object. See {@link ng.$http $http interceptors}.
*
* @returns {Object} A resource "class" object with methods for the default set of resource actions
* optionally extended with custom `actions`. The default set contains these actions:
*
* { 'get': {method:'GET'},
* 'save': {method:'POST'},
* 'query': {method:'GET', isArray:true},
* 'remove': {method:'DELETE'},
* 'delete': {method:'DELETE'} };
*
* Calling these methods invoke an {@link ng.$http} with the specified http method,
* destination and parameters. When the data is returned from the server then the object is an
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
* read, update, delete) on server-side data like this:
* <pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
</pre>
*
* It is important to realize that invoking a $resource object method immediately returns an
* empty reference (object or array depending on `isArray`). Once the data is returned from the
* server the existing reference is populated with the actual data. This is a useful trick since
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
* object results in no rendering, once the data arrives from the server then the object is
* populated with the data and the view automatically re-renders itself showing the new data. This
* means that in most cases one never has to write a callback function for the action methods.
*
* The action methods on the class object or instance object can be invoked with the following
* parameters:
*
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
* with (httpResponse) argument.
*
* Class actions return empty instance (with additional properties below).
* Instance actions return promise of the action.
*
* The Resource instances and collection have these additional properties:
*
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
* instance or collection.
*
* On success, the promise is resolved with the same resource instance or collection object,
* updated with data from server. This makes it easy to use in
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
* rendering until the resource(s) are loaded.
*
* On failure, the promise is resolved with the {@link ng.$http http response} object, without
* the `resource` property.
*
* - `$resolved`: `true` after first server interaction is completed (either with success or
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
* data-binding.
*
* @example
*
* # Credit card resource
*
* <pre>
// Define CreditCard class
var CreditCard = $resource('/user/:userId/card/:cardId',
{userId:123, cardId:'@id'}, {
charge: {method:'POST', params:{charge:true}}
});
// We can retrieve a collection from the server
var cards = CreditCard.query(function() {
// GET: /user/123/card
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
var card = cards[0];
// each item is an instance of CreditCard
expect(card instanceof CreditCard).toEqual(true);
card.name = "J. Smith";
// non GET methods are mapped onto the instances
card.$save();
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
// server returns: {id:456, number:'1234', name: 'J. Smith'};
// our custom method is mapped as well.
card.$charge({amount:9.99});
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
});
// we can create an instance as well
var newCard = new CreditCard({number:'0123'});
newCard.name = "Mike Smith";
newCard.$save();
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
expect(newCard.id).toEqual(789);
* </pre>
*
* The object returned from this function execution is a resource "class" which has "static" method
* for each action in the definition.
*
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
* `headers`.
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
</pre>
*
* It's worth noting that the success callback for `get`, `query` and other methods gets passed
* in the response that came from the server as well as $http header getter function, so one
* could rewrite the above example and get access to http headers as:
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u, getResponseHeaders){
u.abc = true;
u.$save(function(u, putResponseHeaders) {
//u => saved user object
//putResponseHeaders => $http header getter
});
});
</pre>
*/
angular.module('ngResource', ['ng']).
factory('$resource', ['$http', '$q', function($http, $q) {
var DEFAULT_ACTIONS = {
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
var noop = angular.noop,
forEach = angular.forEach,
extend = angular.extend,
copy = angular.copy,
isFunction = angular.isFunction;
/**
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments:
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a
* custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
* have to be encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function Route(template, defaults) {
this.template = template;
this.defaults = defaults || {};
this.urlParams = {};
}
Route.prototype = {
setUrlParams: function(config, params, actionUrl) {
var self = this,
url = actionUrl || self.template,
val,
encodedVal;
var urlParams = self.urlParams = {};
forEach(url.split(/\W/), function(param){
if (param === 'hasOwnProperty') {
throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
}
if (!(new RegExp("^\\d+$").test(param)) && param &&
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
urlParams[param] = true;
}
});
url = url.replace(/\\:/g, ':');
params = params || {};
forEach(self.urlParams, function(_, urlParam){
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
if (angular.isDefined(val) && val !== null) {
encodedVal = encodeUriSegment(val);
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
} else {
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
leadingSlashes, tail) {
if (tail.charAt(0) == '/') {
return tail;
} else {
return leadingSlashes + tail;
}
});
}
});
// strip trailing slashes and set the url
url = url.replace(/\/+$/, '');
// then replace collapse `/.` if found in the last URL path segment before the query
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.`
config.url = url.replace(/\/\\\./, '/.');
// set params - delegate param encoding to $http
forEach(params, function(value, key){
if (!self.urlParams[key]) {
config.params = config.params || {};
config.params[key] = value;
}
});
}
};
function resourceFactory(url, paramDefaults, actions) {
var route = new Route(url);
actions = extend({}, DEFAULT_ACTIONS, actions);
function extractParams(data, actionParams){
var ids = {};
actionParams = extend({}, paramDefaults, actionParams);
forEach(actionParams, function(value, key){
if (isFunction(value)) { value = value(); }
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
lookupDottedPath(data, value.substr(1)) : value;
});
return ids;
}
function defaultResponseInterceptor(response) {
return response.resource;
}
function Resource(value){
copy(value || {}, this);
}
forEach(actions, function(action, name) {
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
Resource[name] = function(a1, a2, a3, a4) {
var params = {}, data, success, error;
/* jshint -W086 */ /* (purposefully fall through case statements) */
switch(arguments.length) {
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
} else {
params = a1;
data = a2;
success = a3;
break;
}
case 1:
if (isFunction(a1)) success = a1;
else if (hasBody) data = a1;
else params = a1;
break;
case 0: break;
default:
throw $resourceMinErr('badargs',
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
arguments.length);
}
/* jshint +W086 */ /* (purposefully fall through case statements) */
var isInstanceCall = this instanceof Resource;
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
var httpConfig = {};
var responseInterceptor = action.interceptor && action.interceptor.response ||
defaultResponseInterceptor;
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
undefined;
forEach(action, function(value, key) {
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
httpConfig[key] = copy(value);
}
});
if (hasBody) httpConfig.data = data;
route.setUrlParams(httpConfig,
extend({}, extractParams(data, action.params || {}), params),
action.url);
var promise = $http(httpConfig).then(function(response) {
var data = response.data,
promise = value.$promise;
if (data) {
// Need to convert action.isArray to boolean in case it is undefined
// jshint -W018
if ( angular.isArray(data) !== (!!action.isArray) ) {
throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
'response to contain an {0} but got an {1}',
action.isArray?'array':'object', angular.isArray(data)?'array':'object');
}
// jshint +W018
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
value.$promise = promise;
}
}
value.$resolved = true;
response.resource = value;
return response;
}, function(response) {
value.$resolved = true;
(error||noop)(response);
return $q.reject(response);
});
promise = promise.then(
function(response) {
var value = responseInterceptor(response);
(success||noop)(value, response.headers);
return value;
},
responseErrorInterceptor);
if (!isInstanceCall) {
// we are creating instance / collection
// - set the initial promise
// - return the instance / collection
value.$promise = promise;
value.$resolved = false;
return value;
}
// instance call
return promise;
};
Resource.prototype['$' + name] = function(params, success, error) {
if (isFunction(params)) {
error = success; success = params; params = {};
}
var result = Resource[name].call(this, params, this, success, error);
return result.$promise || result;
};
});
Resource.bind = function(additionalParamDefaults){
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
return Resource;
}
return resourceFactory;
}]);
})(window, window.angular);

12
vendor/angular/angular-resource.min.js vendored

@ -0,0 +1,12 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(H,f,z){'use strict';var u=f.$$minErr("$resource"),A=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;f.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(D,E){function n(f,h){this.template=f;this.defaults=h||{};this.urlParams={}}function v(m,h,k){function r(d,c){var e={};c=w({},h,c);s(c,function(a,c){t(a)&&(a=a());var g;if(a&&a.charAt&&"@"==a.charAt(0)){g=d;var b=a.substr(1);if(null==b||""===b||"hasOwnProperty"===b||!A.test("."+b))throw u("badmember",b);for(var b=b.split("."),f=0,h=
b.length;f<h&&g!==z;f++){var q=b[f];g=null!==g?g[q]:z}}else g=a;e[c]=g});return e}function e(b){return b.resource}function b(b){B(b||{},this)}var F=new n(m);k=w({},G,k);s(k,function(d,c){var h=/^(POST|PUT|PATCH)$/i.test(d.method);b[c]=function(a,c,g,m){var p={},k,q,x;switch(arguments.length){case 4:x=m,q=g;case 3:case 2:if(t(c)){if(t(a)){q=a;x=c;break}q=c;x=g}else{p=a;k=c;q=g;break}case 1:t(a)?q=a:h?k=a:p=a;break;case 0:break;default:throw u("badargs",arguments.length);}var n=this instanceof b,l=
n?k:d.isArray?[]:new b(k),y={},v=d.interceptor&&d.interceptor.response||e,A=d.interceptor&&d.interceptor.responseError||z;s(d,function(b,a){"params"!=a&&("isArray"!=a&&"interceptor"!=a)&&(y[a]=B(b))});h&&(y.data=k);F.setUrlParams(y,w({},r(k,d.params||{}),p),d.url);p=D(y).then(function(a){var c=a.data,g=l.$promise;if(c){if(f.isArray(c)!==!!d.isArray)throw u("badcfg",d.isArray?"array":"object",f.isArray(c)?"array":"object");d.isArray?(l.length=0,s(c,function(a){l.push(new b(a))})):(B(c,l),l.$promise=
g)}l.$resolved=!0;a.resource=l;return a},function(a){l.$resolved=!0;(x||C)(a);return E.reject(a)});p=p.then(function(a){var c=v(a);(q||C)(c,a.headers);return c},A);return n?p:(l.$promise=p,l.$resolved=!1,l)};b.prototype["$"+c]=function(a,d,g){t(a)&&(g=d,d=a,a={});a=b[c].call(this,a,this,d,g);return a.$promise||a}});b.bind=function(b){return v(m,w({},h,b),k)};return b}var G={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},
C=f.noop,s=f.forEach,w=f.extend,B=f.copy,t=f.isFunction;n.prototype={setUrlParams:function(m,h,k){var r=this,e=k||r.template,b,n,d=r.urlParams={};s(e.split(/\W/),function(c){if("hasOwnProperty"===c)throw u("badname");!/^\d+$/.test(c)&&(c&&RegExp("(^|[^\\\\]):"+c+"(\\W|$)").test(e))&&(d[c]=!0)});e=e.replace(/\\:/g,":");h=h||{};s(r.urlParams,function(c,d){b=h.hasOwnProperty(d)?h[d]:r.defaults[d];f.isDefined(b)&&null!==b?(n=encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,
"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),e=e.replace(RegExp(":"+d+"(\\W|$)","g"),n+"$1")):e=e.replace(RegExp("(/?):"+d+"(\\W|$)","g"),function(a,c,b){return"/"==b.charAt(0)?b:c+b})});e=e.replace(/\/+$/,"");e=e.replace(/\/\.(?=\w+($|\?))/,".");m.url=e.replace(/\/\\\./,"/.");s(h,function(b,d){r.urlParams[d]||(m.params=m.params||{},m.params[d]=b)})}};return v}])})(window,window.angular);
//# sourceMappingURL=angular-resource.min.js.map

8
vendor/angular/angular-resource.min.js.map vendored

File diff suppressed because one or more lines are too long

891
vendor/angular/angular-route.js vendored

@ -0,0 +1,891 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/**
* @ngdoc overview
* @name ngRoute
* @description
*
* # ngRoute
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* {@installModule route}
*
* <div doc-module-components="ngRoute"></div>
*/
/* global -ngRouteModule */
var ngRouteModule = angular.module('ngRoute', ['ng']).
provider('$route', $RouteProvider);
/**
* @ngdoc object
* @name ngRoute.$routeProvider
* @function
*
* @description
*
* Used for configuring routes.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider(){
function inherit(parent, extra) {
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra);
}
var routes = {};
/**
* @ngdoc method
* @name ngRoute.$routeProvider#when
* @methodOf ngRoute.$routeProvider
*
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
* contains redundant trailing slash or is missing one, the route will still match and the
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
* route definition.
*
* * `path` can contain named groups starting with a colon (`:name`). All characters up
* to the next slash are matched and stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain named groups starting with a colon and ending with a star (`:name*`).
* All characters are eagerly stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain optional named groups with a question mark (`:name?`).
*
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
* `/color/brown/largecode/code/with/slashs/edit` and extract:
*
* * `color: brown`
* * `largecode: code/with/slashs`.
*
*
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
*
* Object properties:
*
* - `controller` `{(string|function()=}` Controller fn that should be associated with
* newly created scope or the name of a {@link angular.Module#controller registered
* controller} if passed as a string.
* - `controllerAs` `{string=}` A controller alias name. If present the controller will be
* published to scope under the `controllerAs` name.
* - `template` `{string=|function()=}` html template as a string or a function that
* returns an html template as a string which should be used by {@link
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
* This property takes precedence over `templateUrl`.
*
* If `template` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `templateUrl` `{string=|function()=}` path or function that returns a path to an html
* template that should be used by {@link ngRoute.directive:ngView ngView}.
*
* If `templateUrl` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, the router
* will wait for them all to be resolved or one to be rejected before the controller is
* instantiated.
* If all the promises are resolved successfully, the values of the resolved promises are
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
* fired. If any of the promises are rejected the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
* is:
*
* - `key` `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
* and the return value is treated as the dependency. If the result is a promise, it is
* resolved before its value is injected into the controller. Be aware that
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
* functions. Use `$route.current.params` to access the new route parameters, instead.
*
* - `redirectTo` {(string|function())=} value to update
* {@link ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
*
* - `{Object.<string>}` - route parameters extracted from the current
* `$location.path()` by applying the current route templateUrl.
* - `{string}` - current `$location.path()`
* - `{Object}` - current `$location.search()`
*
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
* or `$location.hash()` changes.
*
* If the option is set to `false` and url in the browser changes, then
* `$routeUpdate` event is broadcasted on the root scope.
*
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
*
* If the option is set to `true`, then the particular route can be matched without being
* case sensitive
*
* @returns {Object} self
*
* @description
* Adds a new route definition to the `$route` service.
*/
this.when = function(path, route) {
routes[path] = angular.extend(
{reloadOnSearch: true},
route,
path && pathRegExp(path, route)
);
// create redirection for trailing slashes
if (path) {
var redirectPath = (path[path.length-1] == '/')
? path.substr(0, path.length-1)
: path +'/';
routes[redirectPath] = angular.extend(
{redirectTo: path},
pathRegExp(redirectPath, route)
);
}
return this;
};
/**
* @param path {string} path
* @param opts {Object} options
* @return {?Object}
*
* @description
* Normalizes the given path, returning a regular expression
* and the original path.
*
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
*/
function pathRegExp(path, opts) {
var insensitive = opts.caseInsensitiveMatch,
ret = {
originalPath: path,
regexp: path
},
keys = ret.keys = [];
path = path
.replace(/([().])/g, '\\$1')
.replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){
var optional = option === '?' ? option : null;
var star = option === '*' ? option : null;
keys.push({ name: key, optional: !!optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (star && '(.+?)' || '([^/]+)')
+ (optional || '')
+ ')'
+ (optional || '');
})
.replace(/([\/$\*])/g, '\\$1');
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
return ret;
}
/**
* @ngdoc method
* @name ngRoute.$routeProvider#otherwise
* @methodOf ngRoute.$routeProvider
*
* @description
* Sets route definition that will be used on route change when no other route definition
* is matched.
*
* @param {Object} params Mapping information to be assigned to `$route.current`.
* @returns {Object} self
*/
this.otherwise = function(params) {
this.when(null, params);
return this;
};
this.$get = ['$rootScope',
'$location',
'$routeParams',
'$q',
'$injector',
'$http',
'$templateCache',
'$sce',
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
/**
* @ngdoc object
* @name ngRoute.$route
* @requires $location
* @requires $routeParams
*
* @property {Object} current Reference to the current route definition.
* The route definition contains:
*
* - `controller`: The controller constructor as define in route definition.
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
* controller instantiation. The `locals` contain
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
*
* - `$scope` - The current route scope.
* - `$template` - The current route template HTML.
*
* @property {Array.<Object>} routes Array of all configured routes.
*
* @description
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
* It watches `$location.url()` and tries to map the path to an existing route definition.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
*
* The `$route` service is typically used in conjunction with the
* {@link ngRoute.directive:ngView `ngView`} directive and the
* {@link ngRoute.$routeParams `$routeParams`} service.
*
* @example
This example shows how changing the URL hash causes the `$route` to match a route against the
URL, and the `ngView` pulls in the partial.
Note that this example is using {@link ng.directive:script inlined templates}
to get it working on jsfiddle as well.
<example module="ngViewExample" deps="angular-route.js">
<file name="index.html">
<div ng-controller="MainCntl">
Choose:
<a href="Book/Moby">Moby</a> |
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
<a href="Book/Gatsby">Gatsby</a> |
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
<a href="Book/Scarlet">Scarlet Letter</a><br/>
<div ng-view></div>
<hr />
<pre>$location.path() = {{$location.path()}}</pre>
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
<pre>$route.current.params = {{$route.current.params}}</pre>
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
<pre>$routeParams = {{$routeParams}}</pre>
</div>
</file>
<file name="book.html">
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
</file>
<file name="chapter.html">
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
Chapter Id: {{params.chapterId}}
</file>
<file name="script.js">
angular.module('ngViewExample', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
resolve: {
// I will cause a 1 second delay
delay: function($q, $timeout) {
var delay = $q.defer();
$timeout(delay.resolve, 1000);
return delay.promise;
}
}
});
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
templateUrl: 'chapter.html',
controller: ChapterCntl
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
function MainCntl($scope, $route, $routeParams, $location) {
$scope.$route = $route;
$scope.$location = $location;
$scope.$routeParams = $routeParams;
}
function BookCntl($scope, $routeParams) {
$scope.name = "BookCntl";
$scope.params = $routeParams;
}
function ChapterCntl($scope, $routeParams) {
$scope.name = "ChapterCntl";
$scope.params = $routeParams;
}
</file>
<file name="scenario.js">
it('should load and compile correct template', function() {
element('a:contains("Moby: Ch1")').click();
var content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element('a:contains("Scarlet")').click();
sleep(2); // promises are not part of scenario waiting
content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
</file>
</example>
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeStart
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted before a route change. At this point the route services starts
* resolving all of the dependencies needed for the route change to occurs.
* Typically this involves fetching the view template as well as any dependencies
* defined in `resolve` route property. Once all of the dependencies are resolved
* `$routeChangeSuccess` is fired.
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} next Future route information.
* @param {Route} current Current route information.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeSuccess
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted after a route dependencies are resolved.
* {@link ngRoute.directive:ngView ngView} listens for the directive
* to instantiate the controller and render the view.
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} current Current route information.
* @param {Route|Undefined} previous Previous route information, or undefined if current is
* first route entered.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeChangeError
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
* Broadcasted if any of the resolve promises are rejected.
*
* @param {Object} angularEvent Synthetic event object
* @param {Route} current Current route information.
* @param {Route} previous Previous route information.
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
*/
/**
* @ngdoc event
* @name ngRoute.$route#$routeUpdate
* @eventOf ngRoute.$route
* @eventType broadcast on root scope
* @description
*
* The `reloadOnSearch` property has been set to false, and we are reusing the same
* instance of the Controller.
*/
var forceReload = false,
$route = {
routes: routes,
/**
* @ngdoc method
* @name ngRoute.$route#reload
* @methodOf ngRoute.$route
*
* @description
* Causes `$route` service to reload the current route even if
* {@link ng.$location $location} hasn't changed.
*
* As a result of that, {@link ngRoute.directive:ngView ngView}
* creates new scope, reinstantiates the controller.
*/
reload: function() {
forceReload = true;
$rootScope.$evalAsync(updateRoute);
}
};
$rootScope.$on('$locationChangeSuccess', updateRoute);
return $route;
/////////////////////////////////////////////////////
/**
* @param on {string} current url
* @param route {Object} route regexp to match the url against
* @return {?Object}
*
* @description
* Check if the route matches the current url.
*
* Inspired by match in
* visionmedia/express/lib/router/router.js.
*/
function switchRouteMatcher(on, route) {
var keys = route.keys,
params = {};
if (!route.regexp) return null;
var m = route.regexp.exec(on);
if (!m) return null;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
if (key && val) {
params[key.name] = val;
}
}
return params;
}
function updateRoute() {
var next = parseRoute(),
last = $route.current;
if (next && last && next.$$route === last.$$route
&& angular.equals(next.pathParams, last.pathParams)
&& !next.reloadOnSearch && !forceReload) {
last.params = next.params;
angular.copy(last.params, $routeParams);
$rootScope.$broadcast('$routeUpdate', last);
} else if (next || last) {
forceReload = false;
$rootScope.$broadcast('$routeChangeStart', next, last);
$route.current = next;
if (next) {
if (next.redirectTo) {
if (angular.isString(next.redirectTo)) {
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
.replace();
} else {
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
.replace();
}
}
}
$q.when(next).
then(function() {
if (next) {
var locals = angular.extend({}, next.resolve),
template, templateUrl;
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) : $injector.invoke(value);
});
if (angular.isDefined(template = next.template)) {
if (angular.isFunction(template)) {
template = template(next.params);
}
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
if (angular.isFunction(templateUrl)) {
templateUrl = templateUrl(next.params);
}
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
if (angular.isDefined(templateUrl)) {
next.loadedTemplateUrl = templateUrl;
template = $http.get(templateUrl, {cache: $templateCache}).
then(function(response) { return response.data; });
}
}
if (angular.isDefined(template)) {
locals['$template'] = template;
}
return $q.all(locals);
}
}).
// after route change
then(function(locals) {
if (next == $route.current) {
if (next) {
next.locals = locals;
angular.copy(next.params, $routeParams);
}
$rootScope.$broadcast('$routeChangeSuccess', next, last);
}
}, function(error) {
if (next == $route.current) {
$rootScope.$broadcast('$routeChangeError', next, last, error);
}
});
}
}
/**
* @returns the current active route, by matching it against the URL
*/
function parseRoute() {
// Match a route
var params, match;
angular.forEach(routes, function(route, path) {
if (!match && (params = switchRouteMatcher($location.path(), route))) {
match = inherit(route, {
params: angular.extend({}, $location.search(), params),
pathParams: params});
match.$$route = route;
}
});
// No route matched; fallback to "otherwise" route
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
}
/**
* @returns interpolation of the redirect path with the parameters
*/
function interpolate(string, params) {
var result = [];
angular.forEach((string||'').split(':'), function(segment, i) {
if (i === 0) {
result.push(segment);
} else {
var segmentMatch = segment.match(/(\w+)(.*)/);
var key = segmentMatch[1];
result.push(params[key]);
result.push(segmentMatch[2] || '');
delete params[key];
}
});
return result.join('');
}
}];
}
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
/**
* @ngdoc object
* @name ngRoute.$routeParams
* @requires $route
*
* @description
* The `$routeParams` service allows you to retrieve the current set of route parameters.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* The route parameters are a combination of {@link ng.$location `$location`}'s
* {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}.
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
*
* In case of parameter name collision, `path` params take precedence over `search` params.
*
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
* (but its properties will likely change) even when a route change occurs.
*
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
* Instead you can use `$route.current.params` to access the new route's parameters.
*
* @example
* <pre>
* // Given:
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
* // Route: /Chapter/:chapterId/Section/:sectionId
* //
* // Then
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
* </pre>
*/
function $RouteParamsProvider() {
this.$get = function() { return {}; };
}
ngRouteModule.directive('ngView', ngViewFactory);
/**
* @ngdoc directive
* @name ngRoute.directive:ngView
* @restrict ECA
*
* @description
* # Overview
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
* including the rendered template of the current route into the main layout (`index.html`) file.
* Every time the current route changes, the included view changes with it according to the
* configuration of the `$route` service.
*
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* @animations
* enter - animation is used to bring new content into the browser.
* leave - animation is used to animate existing content away.
*
* The enter and leave animation occur concurrently.
*
* @scope
* @priority 400
* @example
<example module="ngViewExample" deps="angular-route.js" animations="true">
<file name="index.html">
<div ng-controller="MainCntl as main">
Choose:
<a href="Book/Moby">Moby</a> |
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
<a href="Book/Gatsby">Gatsby</a> |
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
<a href="Book/Scarlet">Scarlet Letter</a><br/>
<div class="view-animate-container">
<div ng-view class="view-animate"></div>
</div>
<hr />
<pre>$location.path() = {{main.$location.path()}}</pre>
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
<pre>$route.current.params = {{main.$route.current.params}}</pre>
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
<pre>$routeParams = {{main.$routeParams}}</pre>
</div>
</file>
<file name="book.html">
<div>
controller: {{book.name}}<br />
Book Id: {{book.params.bookId}}<br />
</div>
</file>
<file name="chapter.html">
<div>
controller: {{chapter.name}}<br />
Book Id: {{chapter.params.bookId}}<br />
Chapter Id: {{chapter.params.chapterId}}
</div>
</file>
<file name="animations.css">
.view-animate-container {
position:relative;
height:100px!important;
position:relative;
background:white;
border:1px solid black;
height:40px;
overflow:hidden;
}
.view-animate {
padding:10px;
}
.view-animate.ng-enter, .view-animate.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
display:block;
width:100%;
border-left:1px solid black;
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
padding:10px;
}
.view-animate.ng-enter {
left:100%;
}
.view-animate.ng-enter.ng-enter-active {
left:0;
}
.view-animate.ng-leave.ng-leave-active {
left:-100%;
}
</file>
<file name="script.js">
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
controllerAs: 'book'
});
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
templateUrl: 'chapter.html',
controller: ChapterCntl,
controllerAs: 'chapter'
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
function MainCntl($route, $routeParams, $location) {
this.$route = $route;
this.$location = $location;
this.$routeParams = $routeParams;
}
function BookCntl($routeParams) {
this.name = "BookCntl";
this.params = $routeParams;
}
function ChapterCntl($routeParams) {
this.name = "ChapterCntl";
this.params = $routeParams;
}
</file>
<file name="scenario.js">
it('should load and compile correct template', function() {
element('a:contains("Moby: Ch1")').click();
var content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element('a:contains("Scarlet")').click();
content = element('.doc-example-live [ng-view]').text();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
</file>
</example>
*/
/**
* @ngdoc event
* @name ngRoute.directive:ngView#$viewContentLoaded
* @eventOf ngRoute.directive:ngView
* @eventType emit on the current ngView scope
* @description
* Emitted every time the ngView content is reloaded.
*/
ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate'];
function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) {
return {
restrict: 'ECA',
terminal: true,
priority: 400,
transclude: 'element',
link: function(scope, $element, attr, ctrl, $transclude) {
var currentScope,
currentElement,
autoScrollExp = attr.autoscroll,
onloadExp = attr.onload || '';
scope.$on('$routeChangeSuccess', update);
update();
function cleanupLastView() {
if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
if(currentElement) {
$animate.leave(currentElement);
currentElement = null;
}
}
function update() {
var locals = $route.current && $route.current.locals,
template = locals && locals.$template;
if (template) {
var newScope = scope.$new();
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-view on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, angular.noop);
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(currentScope);
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
cleanupLastView();
}
}
}
};
}
})(window, window.angular);

14
vendor/angular/angular-route.min.js vendored

@ -0,0 +1,14 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(t,c,A){'use strict';function x(r,m,d,b,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(l,z,k,B,w){function v(){g&&(g.$destroy(),g=null);q&&(h.leave(q),q=null)}function u(){var a=r.current&&r.current.locals,e=a&&a.$template;if(e){var y=l.$new(),s=w(y,c.noop);s.html(e);h.enter(s,null,q||z,function(){!c.isDefined(n)||n&&!l.$eval(n)||m()});v();var e=d(s.contents()),f=r.current;g=f.scope=y;q=s;f.controller&&(a.$scope=g,a=b(f.controller,a),f.controllerAs&&
(g[f.controllerAs]=a),s.data("$ngControllerController",a),s.children().data("$ngControllerController",a));e(g);g.$emit("$viewContentLoaded");g.$eval(p)}else v()}var g,q,n=k.autoscroll,p=k.onload||"";l.$on("$routeChangeSuccess",u);u()}}}t=c.module("ngRoute",["ng"]).provider("$route",function(){function r(b,h){return c.extend(new (c.extend(function(){},{prototype:b})),h)}function m(b,c){var l=c.caseInsensitiveMatch,d={originalPath:b,regexp:b},k=d.keys=[];b=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,
function(b,c,h,d){b="?"===d?d:null;d="*"===d?d:null;k.push({name:h,optional:!!b});c=c||"";return""+(b?"":c)+"(?:"+(b?c:"")+(d&&"(.+?)"||"([^/]+)")+(b||"")+")"+(b||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=RegExp("^"+b+"$",l?"i":"");return d}var d={};this.when=function(b,h){d[b]=c.extend({reloadOnSearch:!0},h,b&&m(b,h));if(b){var l="/"==b[b.length-1]?b.substr(0,b.length-1):b+"/";d[l]=c.extend({redirectTo:b},m(l,h))}return this};this.otherwise=function(b){this.when(null,b);return this};this.$get=
["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(b,h,l,m,k,t,w,v){function u(){var a=g(),e=p.current;if(a&&e&&a.$$route===e.$$route&&c.equals(a.pathParams,e.pathParams)&&!a.reloadOnSearch&&!n)e.params=a.params,c.copy(e.params,l),b.$broadcast("$routeUpdate",e);else if(a||e)n=!1,b.$broadcast("$routeChangeStart",a,e),(p.current=a)&&a.redirectTo&&(c.isString(a.redirectTo)?h.path(q(a.redirectTo,a.params)).search(a.params).replace():h.url(a.redirectTo(a.pathParams,
h.path(),h.search())).replace()),m.when(a).then(function(){if(a){var b=c.extend({},a.resolve),e,f;c.forEach(b,function(a,e){b[e]=c.isString(a)?k.get(a):k.invoke(a)});c.isDefined(e=a.template)?c.isFunction(e)&&(e=e(a.params)):c.isDefined(f=a.templateUrl)&&(c.isFunction(f)&&(f=f(a.params)),f=v.getTrustedResourceUrl(f),c.isDefined(f)&&(a.loadedTemplateUrl=f,e=t.get(f,{cache:w}).then(function(a){return a.data})));c.isDefined(e)&&(b.$template=e);return m.all(b)}}).then(function(d){a==p.current&&(a&&(a.locals=
d,c.copy(a.params,l)),b.$broadcast("$routeChangeSuccess",a,e))},function(c){a==p.current&&b.$broadcast("$routeChangeError",a,e,c)})}function g(){var a,b;c.forEach(d,function(d,l){var f;if(f=!b){var g=h.path();f=d.keys;var m={};if(d.regexp)if(g=d.regexp.exec(g)){for(var k=1,q=g.length;k<q;++k){var n=f[k-1],p="string"==typeof g[k]?decodeURIComponent(g[k]):g[k];n&&p&&(m[n.name]=p)}f=m}else f=null;else f=null;f=a=f}f&&(b=r(d,{params:c.extend({},h.search(),a),pathParams:a}),b.$$route=d)});return b||d[null]&&
r(d[null],{params:{},pathParams:{}})}function q(a,b){var d=[];c.forEach((a||"").split(":"),function(a,c){if(0===c)d.push(a);else{var g=a.match(/(\w+)(.*)/),h=g[1];d.push(b[h]);d.push(g[2]||"");delete b[h]}});return d.join("")}var n=!1,p={routes:d,reload:function(){n=!0;b.$evalAsync(u)}};b.$on("$locationChangeSuccess",u);return p}]});t.provider("$routeParams",function(){this.$get=function(){return{}}});t.directive("ngView",x);x.$inject=["$route","$anchorScroll","$compile","$controller","$animate"]})(window,
window.angular);
//# sourceMappingURL=angular-route.min.js.map

8
vendor/angular/angular-route.min.js.map vendored

File diff suppressed because one or more lines are too long

615
vendor/angular/angular-sanitize.js vendored

@ -0,0 +1,615 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
* @ngdoc overview
* @name ngSanitize
* @description
*
* # ngSanitize
*
* The `ngSanitize` module provides functionality to sanitize HTML.
*
* {@installModule sanitize}
*
* <div doc-module-components="ngSanitize"></div>
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
*/
/**
* @ngdoc service
* @name ngSanitize.$sanitize
* @function
*
* @description
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<script>
function Ctrl($scope, $sce) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
}
</script>
<div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Directive</td>
<td>How</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="bind-html-with-sanitize">
<td>ng-bind-html</td>
<td>Automatically uses $sanitize</td>
<td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html="snippet"></div></td>
</tr>
<tr id="bind-html-with-trust">
<td>ng-bind-html</td>
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
<td>
<pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
&lt;/div&gt;</pre>
</td>
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
</tr>
<tr id="bind-default">
<td>ng-bind</td>
<td>Automatically escapes</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</div>
</doc:source>
<doc:scenario>
it('should sanitize the html snippet by default', function() {
expect(using('#bind-html-with-sanitize').element('div').html()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should inline raw snippet if bound to a trusted value', function() {
expect(using('#bind-html-with-trust').element("div").html()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() {
expect(using('#bind-default').element('div').html()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;");
});
it('should update', function() {
input('snippet').enter('new <b onclick="alert(1)">text</b>');
expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
expect(using('#bind-html-with-trust').element('div').html()).toBe(
'new <b onclick="alert(1)">text</b>');
expect(using('#bind-default').element('div').html()).toBe(
"new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
});
</doc:scenario>
</doc:example>
*/
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP =
/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\s*\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = makeMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));
function makeMap(str) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) obj[items[i]] = true;
return obj;
}
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser( html, handler ) {
var index, chars, match, stack = [], last = html;
stack.last = function() { return stack[ stack.length - 1 ]; };
while ( html ) {
chars = true;
// Make sure we're not in a script or style element
if ( !stack.last() || !specialElements[ stack.last() ] ) {
// Comment
if ( html.indexOf("<!--") === 0 ) {
// comments containing -- are not allowed unless they terminate the comment
index = html.indexOf("--", 4);
if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
if (handler.comment) handler.comment( html.substring( 4, index ) );
html = html.substring( index + 3 );
chars = false;
}
// DOCTYPE
} else if ( DOCTYPE_REGEXP.test(html) ) {
match = html.match( DOCTYPE_REGEXP );
if ( match ) {
html = html.replace( match[0] , '');
chars = false;
}
// end tag
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
match = html.match( END_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( END_TAG_REGEXP, parseEndTag );
chars = false;
}
// start tag
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
match = html.match( START_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
chars = false;
}
}
if ( chars ) {
index = html.indexOf("<");
var text = index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );
if (handler.chars) handler.chars( decodeEntities(text) );
}
} else {
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text){
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
if (handler.chars) handler.chars( decodeEntities(text) );
return "";
});
parseEndTag( "", stack.last() );
}
if ( html == last ) {
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
"of html: {0}", html);
}
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag( tag, tagName, rest, unary ) {
tagName = angular.lowercase(tagName);
if ( blockElements[ tagName ] ) {
while ( stack.last() && inlineElements[ stack.last() ] ) {
parseEndTag( "", stack.last() );
}
}
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
parseEndTag( "", tagName );
}
unary = voidElements[ tagName ] || !!unary;
if ( !unary )
stack.push( tagName );
var attrs = {};
rest.replace(ATTR_REGEXP,
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
var value = doubleQuotedValue
|| singleQuotedValue
|| unquotedValue
|| '';
attrs[name] = decodeEntities(value);
});
if (handler.start) handler.start( tagName, attrs, unary );
}
function parseEndTag( tag, tagName ) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if ( tagName )
// Find the closest opened tag of the same type
for ( pos = stack.length - 1; pos >= 0; pos-- )
if ( stack[ pos ] == tagName )
break;
if ( pos >= 0 ) {
// Close all the open elements, up the stack
for ( i = stack.length - 1; i >= pos; i-- )
if (handler.end) handler.end( stack[ i ] );
// Remove the open elements from the stack
stack.length = pos;
}
}
}
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
if (!value) {
return '';
}
// Note: IE8 does not preserve spaces at the start/end of innerHTML
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
var parts = spaceRe.exec(value);
parts[0] = '';
if (parts[2]) {
hiddenPre.innerHTML=parts[2].replace(/</g,"&lt;");
parts[2] = hiddenPre.innerText || hiddenPre.textContent;
}
return parts.join('');
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs, unary){
tag = angular.lowercase(tag);
if (!ignore && specialElements[tag]) {
ignore = tag;
}
if (!ignore && validElements[tag] === true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out(unary ? '/>' : '>');
}
},
end: function(tag){
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] === true) {
out('</');
out(tag);
out('>');
}
if (tag == ignore) {
ignore = false;
}
},
chars: function(chars){
if (!ignore) {
out(encodeEntities(chars));
}
}
};
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name ngSanitize.filter:linky
* @function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<script>
function Ctrl($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}
</script>
<div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet | linky"></div>
</td>
</tr>
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should linkify the snippet with urls', function() {
expect(using('#linky-filter').binding('snippet | linky')).
toBe('Pretty text with some links:&#10;' +
'<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
});
it ('should not linkify snippet without the linky filter', function() {
expect(using('#escaped-html').binding('snippet')).
toBe("Pretty text with some links:\n" +
"http://angularjs.org/,\n" +
"mailto:us@somewhere.org,\n" +
"another@somewhere.org,\n" +
"and one more: ftp://127.0.0.1/.");
});
it('should update', function() {
input('snippet').enter('new http://link.');
expect(using('#linky-filter').binding('snippet | linky')).
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
});
it('should work with the target property', function() {
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
});
</doc:scenario>
</doc:example>
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
}]);
})(window, window.angular);

14
vendor/angular/angular-sanitize.min.js vendored

@ -0,0 +1,14 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(n,h,q){'use strict';function F(a){var e=[];t(e,h.noop).chars(a);return e.join("")}function k(a){var e={};a=a.split(",");var d;for(d=0;d<a.length;d++)e[a[d]]=!0;return e}function G(a,e){function d(a,b,d,g){b=h.lowercase(b);if(u[b])for(;f.last()&&v[f.last()];)c("",f.last());w[b]&&f.last()==b&&c("",b);(g=x[b]||!!g)||f.push(b);var l={};d.replace(H,function(a,b,e,c,m){l[b]=r(e||c||m||"")});e.start&&e.start(b,l,g)}function c(a,b){var c=0,d;if(b=h.lowercase(b))for(c=f.length-1;0<=c&&f[c]!=b;c--);
if(0<=c){for(d=f.length-1;d>=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(f.last()&&y[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");e.chars&&e.chars(r(b));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&&e.comment(a.substring(4,b)),a=a.substring(b+3),g=!1);else if(z.test(a)){if(b=a.match(z))a=
a.replace(b[0],""),g=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,c),g=!1}else L.test(a)&&(b=a.match(B))&&(a=a.substring(b[0].length),b[0].replace(B,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw M("badparse",a);l=a}c()}function r(a){if(!a)return"";a=/^(\s*)([\s\S]*?)(\s*)$/.exec(a);a[0]="";a[2]&&(s.innerHTML=a[2].replace(/</g,"&lt;"),a[2]=s.innerText||s.textContent);return a.join("")}function C(a){return a.replace(/&/g,
"&amp;").replace(N,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function t(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&y[a]&&(d=a);d||!0!==D[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===E[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(C(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==D[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||
c(C(a))}}}var M=h.$$minErr("$sanitize"),B=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,A=/^<\s*\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^</,K=/^<\s*\//,I=/\x3c!--(.*?)--\x3e/g,z=/<!DOCTYPE([^>]*?)>/i,J=/<!\[CDATA\[(.*?)]]\x3e/g,N=/([^\#-~| |!])/g,x=k("area,br,col,hr,img,wbr");n=k("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");q=k("rp,rt");var w=h.extend({},q,n),u=h.extend({},n,k("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),
v=h.extend({},q,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),y=k("script,style"),D=h.extend({},x,u,v,w),E=k("background,cite,href,longdesc,src,usemap"),O=h.extend({},E,k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),
s=document.createElement("pre");h.module("ngSanitize",[]).provider("$sanitize",function(){this.$get=["$$sanitizeUri",function(a){return function(e){var d=[];G(e,t(d,function(c,b){return!/^unsafe/.test(a(c,b))}));return d.join("")}}]});h.module("ngSanitize").filter("linky",["$sanitize",function(a){var e=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(F(a))}function f(a,c){m.push("<a ");h.isDefined(b)&&(m.push('target="'),
m.push(b),m.push('" '));m.push('href="');m.push(a);m.push('">');g(c);m.push("</a>")}if(!c)return c;for(var l,k=c,m=[],p,n;l=k.match(e);)p=l[0],l[2]==l[3]&&(p="mailto:"+p),n=l.index,g(k.substr(0,n)),f(p,l[0].replace(d,"")),k=k.substring(n+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular);
//# sourceMappingURL=angular-sanitize.min.js.map

8
vendor/angular/angular-sanitize.min.js.map vendored

File diff suppressed because one or more lines are too long

563
vendor/angular/angular-touch.js vendored

@ -0,0 +1,563 @@
/**
* @license AngularJS v1.2.3
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/**
* @ngdoc overview
* @name ngTouch
* @description
*
* # ngTouch
*
* The `ngTouch` module provides touch events and other helpers for touch-enabled devices.
* The implementation is based on jQuery Mobile touch event handling
* ([jquerymobile.com](http://jquerymobile.com/)).
*
* {@installModule touch}
*
* See {@link ngTouch.$swipe `$swipe`} for usage.
*
* <div doc-module-components="ngTouch"></div>
*
*/
// define ngTouch module
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);
/* global ngTouch: false */
/**
* @ngdoc object
* @name ngTouch.$swipe
*
* @description
* The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
* behavior, to make implementing swipe-related directives more convenient.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by
* `ngCarousel` in a separate component.
*
* # Usage
* The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
* which is to be watched for swipes, and an object with four handler functions. See the
* documentation for `bind` below.
*/
ngTouch.factory('$swipe', [function() {
// The total distance in any direction before we make the call on swipe vs. scroll.
var MOVE_BUFFER_RADIUS = 10;
function getCoordinates(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = (event.changedTouches && event.changedTouches[0]) ||
(event.originalEvent && event.originalEvent.changedTouches &&
event.originalEvent.changedTouches[0]) ||
touches[0].originalEvent || touches[0];
return {
x: e.clientX,
y: e.clientY
};
}
return {
/**
* @ngdoc method
* @name ngTouch.$swipe#bind
* @methodOf ngTouch.$swipe
*
* @description
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
* object containing event handlers.
*
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
*
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
* watching for `touchmove` or `mousemove` events. These events are ignored until the total
* distance moved in either dimension exceeds a small threshold.
*
* Once this threshold is exceeded, either the horizontal or vertical delta is greater.
* - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow.
* - If the vertical distance is greater, this is a scroll, and we let the browser take over.
* A `cancel` event is sent.
*
* `move` is called on `mousemove` and `touchmove` after the above logic has determined that
* a swipe is in progress.
*
* `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
*
* `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
* as described above.
*
*/
bind: function(element, eventHandlers) {
// Absolute total movement, used to control swipe vs. scroll.
var totalX, totalY;
// Coordinates of the start position.
var startCoords;
// Last event's position.
var lastPos;
// Whether a swipe is active.
var active = false;
element.on('touchstart mousedown', function(event) {
startCoords = getCoordinates(event);
active = true;
totalX = 0;
totalY = 0;
lastPos = startCoords;
eventHandlers['start'] && eventHandlers['start'](startCoords, event);
});
element.on('touchcancel', function(event) {
active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event);
});
element.on('touchmove mousemove', function(event) {
if (!active) return;
// Android will send a touchcancel if it thinks we're starting to scroll.
// So when the total distance (+ or - or both) exceeds 10px in either direction,
// we either:
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
// - On totalY > totalX, we let the browser handle it as a scroll.
if (!startCoords) return;
var coords = getCoordinates(event);
totalX += Math.abs(coords.x - lastPos.x);
totalY += Math.abs(coords.y - lastPos.y);
lastPos = coords;
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
return;
}
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
if (totalY > totalX) {
// Allow native scrolling to take over.
active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event);
return;
} else {
// Prevent the browser from scrolling.
event.preventDefault();
eventHandlers['move'] && eventHandlers['move'](coords, event);
}
});
element.on('touchend mouseup', function(event) {
if (!active) return;
active = false;
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event);
});
}
};
}]);
/* global ngTouch: false */
/**
* @ngdoc directive
* @name ngTouch.directive:ngClick
*
* @description
* A more powerful replacement for the default ngClick designed to be used on touchscreen
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
* the click event. This version handles them immediately, and then prevents the
* following click event from propagating.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* This directive can fall back to using an ordinary click event, and so works on desktop
* browsers as well as mobile.
*
* This directive also sets the CSS class `ng-click-active` while the element is being held
* down (by a mouse click or touch) so you can restyle the depressed element if you wish.
*
* @element ANY
* @param {expression} ngClick {@link guide/expression Expression} to evaluate
* upon tap. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<button ng-click="count = count + 1" ng-init="count=0">
Increment
</button>
count: {{ count }}
</doc:source>
</doc:example>
*/
ngTouch.config(['$provide', function($provide) {
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
// drop the default ngClick directive
$delegate.shift();
return $delegate;
}]);
}]);
ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
function($parse, $timeout, $rootElement) {
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
var ACTIVE_CLASS_NAME = 'ng-click-active';
var lastPreventedTime;
var touchCoordinates;
// TAP EVENTS AND GHOST CLICKS
//
// Why tap events?
// Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
// double-tapping, and then fire a click event.
//
// This delay sucks and makes mobile apps feel unresponsive.
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
// the user has tapped on something.
//
// What happens when the browser then generates a click event?
// The browser, of course, also detects the tap and fires a click after a delay. This results in
// tapping/clicking twice. So we do "clickbusting" to prevent it.
//
// How does it work?
// We attach global touchstart and click handlers, that run during the capture (early) phase.
// So the sequence for a tap is:
// - global touchstart: Sets an "allowable region" at the point touched.
// - element's touchstart: Starts a touch
// (- touchmove or touchcancel ends the touch, no click follows)
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
// - preventGhostClick() removes the allowable region the global touchstart created.
// - The browser generates a click event.
// - The global click handler catches the click, and checks whether it was in an allowable region.
// - If preventGhostClick was called, the region will have been removed, the click is busted.
// - If the region is still there, the click proceeds normally. Therefore clicks on links and
// other elements without ngTap on them work normally.
//
// This is an ugly, terrible hack!
// Yeah, tell me about it. The alternatives are using the slow click events, or making our users
// deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
// encapsulates this ugly logic away from the user.
//
// Why not just put click handlers on the element?
// We do that too, just to be sure. The problem is that the tap event might have caused the DOM
// to change, so that the click fires in the same position but something else is there now. So
// the handlers are global and care only about coordinates and not elements.
// Checks if the coordinates are close enough to be within the region.
function hit(x1, y1, x2, y2) {
return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
}
// Checks a list of allowable regions against a click location.
// Returns true if the click should be allowed.
// Splices out the allowable region from the list after it has been used.
function checkAllowableRegions(touchCoordinates, x, y) {
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
touchCoordinates.splice(i, i + 2);
return true; // allowable region
}
}
return false; // No allowable region; bust it.
}
// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
// was called recently.
function onClick(event) {
if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
return; // Too old.
}
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
// and on the input element). Depending on the exact browser, this second click we don't want
// to bust has either (0,0) or negative coordinates.
if (x < 1 && y < 1) {
return; // offscreen
}
// Look for an allowable region containing this click.
// If we find one, that means it was created by touchstart and not removed by
// preventGhostClick, so we don't bust it.
if (checkAllowableRegions(touchCoordinates, x, y)) {
return;
}
// If we didn't find an allowable region, bust the click.
event.stopPropagation();
event.preventDefault();
// Blur focused form elements
event.target && event.target.blur();
}
// Global touchstart handler that creates an allowable region for a click event.
// This allowable region can be removed by preventGhostClick if we want to bust it.
function onTouchStart(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
touchCoordinates.push(x, y);
$timeout(function() {
// Remove the allowable region.
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
touchCoordinates.splice(i, i + 2);
return;
}
}
}, PREVENT_DURATION, false);
}
// On the first call, attaches some event handlers. Then whenever it gets called, it creates a
// zone around the touchstart where clicks will get busted.
function preventGhostClick(x, y) {
if (!touchCoordinates) {
$rootElement[0].addEventListener('click', onClick, true);
$rootElement[0].addEventListener('touchstart', onTouchStart, true);
touchCoordinates = [];
}
lastPreventedTime = Date.now();
checkAllowableRegions(touchCoordinates, x, y);
}
// Actual linking function.
return function(scope, element, attr) {
var clickHandler = $parse(attr.ngClick),
tapping = false,
tapElement, // Used to blur the element after a tap.
startTime, // Used to check if the tap was held too long.
touchStartX,
touchStartY;
function resetState() {
tapping = false;
element.removeClass(ACTIVE_CLASS_NAME);
}
element.on('touchstart', function(event) {
tapping = true;
tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
// Hack for Safari, which can target text nodes instead of containers.
if(tapElement.nodeType == 3) {
tapElement = tapElement.parentNode;
}
element.addClass(ACTIVE_CLASS_NAME);
startTime = Date.now();
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = touches[0].originalEvent || touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
});
element.on('touchmove', function(event) {
resetState();
});
element.on('touchcancel', function(event) {
resetState();
});
element.on('touchend', function(event) {
var diff = Date.now() - startTime;
var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
((event.touches && event.touches.length) ? event.touches : [event]);
var e = touches[0].originalEvent || touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
// Call preventGhostClick so the clickbuster will catch the corresponding click.
preventGhostClick(x, y);
// Blur the focused element (the button, probably) before firing the callback.
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
// I couldn't get anything to work reliably on Android Chrome.
if (tapElement) {
tapElement.blur();
}
if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
element.triggerHandler('click', [event]);
}
}
resetState();
});
// Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
// something else nearby.
element.onclick = function(event) { };
// Actual click handler.
// There are three different kinds of clicks, only two of which reach this point.
// - On desktop browsers without touch events, their clicks will always come here.
// - On mobile browsers, the simulated "fast" click will call this.
// - But the browser's follow-up slow click will be "busted" before it reaches this handler.
// Therefore it's safe to use this directive on both mobile and desktop.
element.on('click', function(event, touchend) {
scope.$apply(function() {
clickHandler(scope, {$event: (touchend || event)});
});
});
element.on('mousedown', function(event) {
element.addClass(ACTIVE_CLASS_NAME);
});
element.on('mousemove mouseup', function(event) {
element.removeClass(ACTIVE_CLASS_NAME);
});
};
}]);
/* global ngTouch: false */
/**
* @ngdoc directive
* @name ngTouch.directive:ngSwipeLeft
*
* @description
* Specify custom behavior when an element is swiped to the left on a touchscreen device.
* A leftward swipe is a quick, right-to-left slide of the finger.
* Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
* too.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* @element ANY
* @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
* upon left swipe. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox
</div>
<div ng-show="showActions" ng-swipe-right="showActions = false">
<button ng-click="reply()">Reply</button>
<button ng-click="delete()">Delete</button>
</div>
</doc:source>
</doc:example>
*/
/**
* @ngdoc directive
* @name ngTouch.directive:ngSwipeRight
*
* @description
* Specify custom behavior when an element is swiped to the right on a touchscreen device.
* A rightward swipe is a quick, left-to-right slide of the finger.
* Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag
* too.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* @element ANY
* @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
* upon right swipe. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox
</div>
<div ng-show="showActions" ng-swipe-right="showActions = false">
<button ng-click="reply()">Reply</button>
<button ng-click="delete()">Delete</button>
</div>
</doc:source>
</doc:example>
*/
function makeSwipeDirective(directiveName, direction, eventName) {
ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
// The maximum vertical delta for a swipe should be less than 75px.
var MAX_VERTICAL_DISTANCE = 75;
// Vertical distance should not be more than a fraction of the horizontal distance.
var MAX_VERTICAL_RATIO = 0.3;
// At least a 30px lateral motion is necessary for a swipe.
var MIN_HORIZONTAL_DISTANCE = 30;
return function(scope, element, attr) {
var swipeHandler = $parse(attr[directiveName]);
var startCoords, valid;
function validSwipe(coords) {
// Check that it's within the coordinates.
// Absolute vertical distance must be within tolerances.
// Horizontal distance, we take the current X - the starting X.
// This is negative for leftward swipes and positive for rightward swipes.
// After multiplying by the direction (-1 for left, +1 for right), legal swipes
// (ie. same direction as the directive wants) will have a positive delta and
// illegal ones a negative delta.
// Therefore this delta must be positive, and larger than the minimum.
if (!startCoords) return false;
var deltaY = Math.abs(coords.y - startCoords.y);
var deltaX = (coords.x - startCoords.x) * direction;
return valid && // Short circuit for already-invalidated swipes.
deltaY < MAX_VERTICAL_DISTANCE &&
deltaX > 0 &&
deltaX > MIN_HORIZONTAL_DISTANCE &&
deltaY / deltaX < MAX_VERTICAL_RATIO;
}
$swipe.bind(element, {
'start': function(coords, event) {
startCoords = coords;
valid = true;
},
'cancel': function(event) {
valid = false;
},
'end': function(coords, event) {
if (validSwipe(coords)) {
scope.$apply(function() {
element.triggerHandler(eventName);
swipeHandler(scope, {$event: event});
});
}
}
});
};
}]);
}
// Left is negative X-coordinate, right is positive.
makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
makeSwipeDirective('ngSwipeRight', 1, 'swiperight');
})(window, window.angular);

13
vendor/angular/angular-touch.min.js vendored

@ -0,0 +1,13 @@
/*
AngularJS v1.2.3
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(y,v,z){'use strict';function t(g,a,b){q.directive(g,["$parse","$swipe",function(l,n){var r=75,h=0.3,d=30;return function(p,m,k){function e(e){if(!u)return!1;var c=Math.abs(e.y-u.y);e=(e.x-u.x)*a;return f&&c<r&&0<e&&e>d&&c/e<h}var c=l(k[g]),u,f;n.bind(m,{start:function(e,c){u=e;f=!0},cancel:function(e){f=!1},end:function(a,f){e(a)&&p.$apply(function(){m.triggerHandler(b);c(p,{$event:f})})}})}}])}var q=v.module("ngTouch",[]);q.factory("$swipe",[function(){function g(a){var b=a.touches&&a.touches.length?
a.touches:[a];a=a.changedTouches&&a.changedTouches[0]||a.originalEvent&&a.originalEvent.changedTouches&&a.originalEvent.changedTouches[0]||b[0].originalEvent||b[0];return{x:a.clientX,y:a.clientY}}return{bind:function(a,b){var l,n,r,h,d=!1;a.on("touchstart mousedown",function(a){r=g(a);d=!0;n=l=0;h=r;b.start&&b.start(r,a)});a.on("touchcancel",function(a){d=!1;b.cancel&&b.cancel(a)});a.on("touchmove mousemove",function(a){if(d&&r){var m=g(a);l+=Math.abs(m.x-h.x);n+=Math.abs(m.y-h.y);h=m;10>l&&10>n||
(n>l?(d=!1,b.cancel&&b.cancel(a)):(a.preventDefault(),b.move&&b.move(m,a)))}});a.on("touchend mouseup",function(a){d&&(d=!1,b.end&&b.end(g(a),a))})}}}]);q.config(["$provide",function(g){g.decorator("ngClickDirective",["$delegate",function(a){a.shift();return a}])}]);q.directive("ngClick",["$parse","$timeout","$rootElement",function(g,a,b){function l(a,c,b){for(var f=0;f<a.length;f+=2)if(Math.abs(a[f]-c)<d&&Math.abs(a[f+1]-b)<d)return a.splice(f,f+2),!0;return!1}function n(a){if(!(Date.now()-m>h)){var c=
a.touches&&a.touches.length?a.touches:[a],b=c[0].clientX,c=c[0].clientY;1>b&&1>c||l(k,b,c)||(a.stopPropagation(),a.preventDefault(),a.target&&a.target.blur())}}function r(b){b=b.touches&&b.touches.length?b.touches:[b];var c=b[0].clientX,d=b[0].clientY;k.push(c,d);a(function(){for(var a=0;a<k.length;a+=2)if(k[a]==c&&k[a+1]==d){k.splice(a,a+2);break}},h,!1)}var h=2500,d=25,p="ng-click-active",m,k;return function(a,c,d){function f(){q=!1;c.removeClass(p)}var h=g(d.ngClick),q=!1,s,t,w,x;c.on("touchstart",
function(a){q=!0;s=a.target?a.target:a.srcElement;3==s.nodeType&&(s=s.parentNode);c.addClass(p);t=Date.now();a=a.touches&&a.touches.length?a.touches:[a];a=a[0].originalEvent||a[0];w=a.clientX;x=a.clientY});c.on("touchmove",function(a){f()});c.on("touchcancel",function(a){f()});c.on("touchend",function(a){var h=Date.now()-t,e=a.changedTouches&&a.changedTouches.length?a.changedTouches:a.touches&&a.touches.length?a.touches:[a],g=e[0].originalEvent||e[0],e=g.clientX,g=g.clientY,p=Math.sqrt(Math.pow(e-
w,2)+Math.pow(g-x,2));q&&(750>h&&12>p)&&(k||(b[0].addEventListener("click",n,!0),b[0].addEventListener("touchstart",r,!0),k=[]),m=Date.now(),l(k,e,g),s&&s.blur(),v.isDefined(d.disabled)&&!1!==d.disabled||c.triggerHandler("click",[a]));f()});c.onclick=function(a){};c.on("click",function(b,c){a.$apply(function(){h(a,{$event:c||b})})});c.on("mousedown",function(a){c.addClass(p)});c.on("mousemove mouseup",function(a){c.removeClass(p)})}}]);t("ngSwipeLeft",-1,"swipeleft");t("ngSwipeRight",1,"swiperight")})(window,
window.angular);
//# sourceMappingURL=angular-touch.min.js.map

8
vendor/angular/angular-touch.min.js.map vendored

File diff suppressed because one or more lines are too long

20282
vendor/angular/angular.js vendored

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save