Browse Source

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	app/webogram.appcache
master
Igor Zhukov 7 years ago
parent
commit
0e1631d410
  1. 172
      CHANGELOG.md
  2. BIN
      app/img/icons/IconsetW.png
  3. BIN
      app/img/icons/IconsetW_2x.png
  4. 8
      app/index.html
  5. 2
      app/js/app.js
  6. 5
      app/js/background.js
  7. 39
      app/js/controllers.js
  8. 356
      app/js/directives.js
  9. 2
      app/js/directives_mobile.js
  10. 2
      app/js/filters.js
  11. 2
      app/js/lib/bin_utils.js
  12. 8
      app/js/lib/config.js
  13. 2
      app/js/lib/crypto_worker.js
  14. 7
      app/js/lib/mtproto.js
  15. 16
      app/js/lib/mtproto_wrapper.js
  16. 34
      app/js/lib/ng_utils.js
  17. 133
      app/js/lib/schema.tl
  18. 2
      app/js/lib/tl_utils.js
  19. 3
      app/js/lib/utils.js
  20. 14
      app/js/locales/de-de.json
  21. 14
      app/js/locales/en-us.json
  22. 12
      app/js/locales/es-es.json
  23. 12
      app/js/locales/it-it.json
  24. 14
      app/js/locales/nl-nl.json
  25. 42
      app/js/locales/pt-br.json
  26. 12
      app/js/locales/ru-ru.json
  27. 4
      app/js/message_composer.js
  28. 180
      app/js/messages_manager.js
  29. 90
      app/js/services.js
  30. 162
      app/less/app.less
  31. 90
      app/less/desktop.less
  32. 125
      app/less/mobile.less
  33. 2
      app/manifest.json
  34. 5
      app/manifest.webapp
  35. 12
      app/partials/desktop/audio_player.html
  36. 343
      app/partials/desktop/changelog_modal.html
  37. 2
      app/partials/desktop/channel_modal.html
  38. 14
      app/partials/desktop/error_modal.html
  39. 27
      app/partials/desktop/full_round.html
  40. 1
      app/partials/desktop/head.html
  41. 50
      app/partials/desktop/im.html
  42. 2
      app/partials/desktop/message_attach_document.html
  43. 1
      app/partials/desktop/message_service.html
  44. 68
      app/partials/desktop/send_form.html
  45. 3
      app/partials/desktop/short_message.html
  46. 25
      app/partials/mobile/audio_player.html
  47. 80
      app/partials/mobile/channel_modal.html
  48. 7
      app/partials/mobile/chat_modal.html
  49. 1
      app/partials/mobile/head.html
  50. 44
      app/partials/mobile/im.html
  51. 2
      app/partials/mobile/message_attach_document.html
  52. 2
      app/partials/mobile/message_service.html
  53. 59
      app/partials/mobile/send_form.html
  54. 2
      app/partials/mobile/settings_modal.html
  55. 2
      app/vendor/README.md
  56. 15
      app/vendor/angular-media-player/angular-media-player.js
  57. 21
      app/vendor/ogv.js/COPYING
  58. 28
      app/vendor/ogv.js/COPYING-ogg.txt
  59. 44
      app/vendor/ogv.js/COPYING-opus.txt
  60. 28
      app/vendor/ogv.js/COPYING-theora.txt
  61. 28
      app/vendor/ogv.js/COPYING-vorbis.txt
  62. 13
      app/vendor/ogv.js/LICENSE-nestegg.txt
  63. 31
      app/vendor/ogv.js/LICENSE-vpx.txt
  64. 23
      app/vendor/ogv.js/PATENTS-vpx.txt
  65. 371
      app/vendor/ogv.js/README.md
  66. BIN
      app/vendor/ogv.js/dynamicaudio.swf
  67. 17
      app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.js
  68. BIN
      app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.wasm
  69. 32
      app/vendor/ogv.js/ogv-decoder-audio-opus.js
  70. 17
      app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.js
  71. BIN
      app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.wasm
  72. 30
      app/vendor/ogv.js/ogv-decoder-audio-vorbis.js
  73. 17
      app/vendor/ogv.js/ogv-decoder-video-theora-wasm.js
  74. BIN
      app/vendor/ogv.js/ogv-decoder-video-theora-wasm.wasm
  75. 30
      app/vendor/ogv.js/ogv-decoder-video-theora.js
  76. 31
      app/vendor/ogv.js/ogv-decoder-video-vp8-mt.js
  77. 17
      app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.js
  78. BIN
      app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.wasm
  79. 31
      app/vendor/ogv.js/ogv-decoder-video-vp8.js
  80. 33
      app/vendor/ogv.js/ogv-decoder-video-vp9-mt.js
  81. 17
      app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.js
  82. BIN
      app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.wasm
  83. 33
      app/vendor/ogv.js/ogv-decoder-video-vp9.js
  84. 17
      app/vendor/ogv.js/ogv-demuxer-ogg-wasm.js
  85. BIN
      app/vendor/ogv.js/ogv-demuxer-ogg-wasm.wasm
  86. 30
      app/vendor/ogv.js/ogv-demuxer-ogg.js
  87. 17
      app/vendor/ogv.js/ogv-demuxer-webm-wasm.js
  88. BIN
      app/vendor/ogv.js/ogv-demuxer-webm-wasm.wasm
  89. 30
      app/vendor/ogv.js/ogv-demuxer-webm.js
  90. 272
      app/vendor/ogv.js/ogv-support.js
  91. 70
      app/vendor/ogv.js/ogv-version.js
  92. 701
      app/vendor/ogv.js/ogv-worker-audio.js
  93. 700
      app/vendor/ogv.js/ogv-worker-video.js
  94. 9663
      app/vendor/ogv.js/ogv.js
  95. 103
      app/vendor/ogv.js/pthread-main.js
  96. 18
      app/vendor/recorderjs/encoder_worker.js
  97. 245
      app/vendor/recorderjs/recorder.js
  98. 1
      app/vendor/recorderjs/recorder.min.js
  99. 2
      app/webogram.appcache
  100. 7
      gulpfile.js
  101. Some files were not shown because too many files have changed in this diff Show More

172
CHANGELOG.md

@ -0,0 +1,172 @@ @@ -0,0 +1,172 @@
### 0.5.7
* Video messages and Telescope
* Notifications about new logins
* Changelog is now received as a message
### 0.5.6
* Edit the text of your messages after sending them. This works across all Telegram chats, including groups and one-on-one conversations. Select a message and click 'Edit' or just press the up arrow button to edit your last message.
* Unsend Messages: retract any messages within 48 hours of sending them. Check out the [Telegram Blog](https://telegram.org/blog/unsend-and-usage) for more info.
* Pinned chats. Check out the [Telegram Blog](https://telegram.org/blog/pin-and-ifttt) for more info.
* Sticker suggestions by emoji.
* Search for messages in specific chats.
* Background notifications in Chrome and Firefox (can be disabled in Settings)
### 0.5.5
* Introducing Drafts: Seamless syncing for unsent messages on all your devices. Drafts are now visible in your chats list.
* Mention people in groups by typing @ and selecting them from the list — even if they don't have a username.
* Share links to specific posts in channels via quick forwarding menu (click on the date in a message to try this out).
### 0.5.4
* Introducing Bot API 2.0, the biggest update to our bot platform since June 2015.
* New inline keyboards with callback, 'open URL' or 'switch to inline mode' buttons help create seamless interfaces.
* Bots can now update existing messages on the fly as you interact with them.
* Prepare for the rise of location-based bots: all bots can now ask users to share their location.
* Inline bots can now send all attachments supported in Telegram (videos, music, stickers, files, etc.).
* Try out these sample bots to see what's coming your way soon: @music, @sticker, @youtube, @foursquare
* Check out the [Telegram Blog](https://telegram.org/blog/bots-2-0) for more info.
* New quick forwarding in channels (click on the date in a message to try this out).
* Improved performance.
### 0.5.3
* Inline bots: A new way to add bot content to any chat. Type a bot's username and your query in the text field to get instant results and send them to your chat partner. Try typing `@gif dog` in your next chat. Sample bots: @gif, @wiki, @bing, @vid, @bold.
* Check out the [Telegram Blog](https://telegram.org/blog/inline-bots) for more info.
* Improved GIFs: 20x faster sending and downloading, nice animated progress
* Click on message date to reply (or to forward from channels).
* Preview images before sending when pasting from clipboard.
* Improved formatting for copy-pasted history fragments (date, time and sender names inserted automatically).
### 0.5.2
* Unread counters for muted chats now colored in gray.
* Improved previews for sticker sets: Click on a sticker to view the whole set, click on stickers in a set to send right away, added a 'Share' button.
* Improved performance.
### 0.5.1
* Groups that have reached their capacity of 200 users can be upgraded to supergroups of up to 1,000 members.
* Check out the [Telegram Blog](https://telegram.org/blog/supergroups) for more info
### 0.5.0
* Removed annoying "multiple tabs open" error.
* Improved message forwarding.
* Added view counter to messages from channels.
* Improved image loading in Safari and Firefox.
### 0.4.9
* New emoji and sticker menu, tabs for sticker packs.
### 0.4.8
* Introducing Channels – a great new way to broadcast your messages to unlimited audiences.
* Check out the [Telegram Blog](https://telegram.org/blog/channels) for more info
* Improved performance in Safari on OS X El Capitan.
* Added formatting for fixed-width code, surround text with `single backticks` for inline text and ```triple backticks``` for blocks of pre-formatted text.
### 0.4.7
* New bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.
* Check out [Telegram Blog](https://telegram.org/blog/bot-revolution) for more info
* Improved Stickers support: now stickers are loading much faster.
* Click on any custom stickers in chats to view and add sticker sets.
* [Mobile] Reply to a message easily: tap on any message and select "Reply".
### 0.4.6
* Install and share custom sticker sets like this one: telegram.me/addstickers/Animals
* If you're an artist, create custom sticker sets using our @Stickers bot.
* Check out [Telegram Blog](https://telegram.org/blog/stickers-revolution) for more info
### 0.4.5
* Invite links for group chats:
Check out [Telegram Blog](https://telegram.org/blog/invite-links) for more info
* Smart notifications
* 'Listened' status for voice messages
* Places in locations (venues, landmarks)
### 0.4.4
* Link Previews:
Get rich link summaries for tweets, YouTube videos, Instagram photos and other content.
* Check out [Telegram Blog](https://telegram.org/blog/link-preview) for more info.
### 0.4.3
* Sessions List:
View your active Telegram sessions (on desktop, tablet and mobile devices) and close specific sessions remotely.
* Two-step verification:
Set up an additional password that will be required to log into your Telegram account.
* Check out [Telegram Blog](https://telegram.org/blog/sessions-and-2-step-verification) for more info.
### 0.4.2
* Optimization for screens with smaller Y-resolutions.
* Supported Spotify URL embeds.
* Mentions of the current user in group chats are now highlighted.
### 0.4.1
* Reply to specific messages in groups.
* Mention @usernames in groups to notify multiple users.
* Revised notifications in groups: mentioned users and people you reply to will be notified (private chat notification settings apply in this case instead of group settings). Check out <a href="https://telegram.org/blog/replies-mentions-hashtags" target="_blank">Telegram Blog</a> for more info.
* Setting to disable message preview
### 0.4.0
* Full stickers support
* Multisearch box: instantly find chats, usernames and messages
* Emoji autocomplete: e.g., type **:kiss<** in the message field to see the list. [Full cheat sheet](http://www.emoji-cheat-sheet.com/)
* Added 'typing' notification in chats list
* Online members counter in group headers
### 0.3.9
* [Desktop] Material design completed
### 0.3.8
* Telegram.me links open right in Telegram Web when authorized
* @username mentions in messages are clickable and open a conversation with the user
### 0.3.7
* [Desktop] New material design for modal windows
* [Desktop] Forward messages to multiple recipients
### 0.3.6
* New viewer for photos, videos, documents.
* [FirefoxOS] Improved media downloads.
### 0.3.5
* Added embedded Soundcloud tracks and playlists.
* Added global user search to contacts list.
* Added switch to mobile version on window resize.
* Migrate to HTTPS notification
* Bugfixes.
### 0.3.4
* Added embedded Facebook posts and Vimeo videos.
* Improved IE10+ support: downloading files and style fixes.
* Added unsupported media playback warning.
* Bugfixes.
### 0.3.3
* Added Native Client module: dramatically improved encryption speed; Download and upload speed is now as high as in native applications.
* HTTPS. We recommend you to use https://web.telegram.org.
* Added multiple open tabs warning. Please note, that only one tab with Telegram Web will work.
* Added embedded Instagram, Twitter, Vine, YouTube links.
* Jump to selected spot when playing back audio.
* Bugfixes
### 0.3.2
* Usernames support.
* Search can now find public users by username.
* Most popular emoticons shown in 'recent' when empty
* [ChromeApp] Added saving window position and size
* Bugfixes
### 0.3.1
* New languages: Spanish, German and Italian are now available
* New custom-made audio player
* Bad browser page for IE 6-9
* Perfomance improvements and bugfixes
### 0.3.0
* Log in codes may be received in other Telegram apps
* Partner's online status updates automatically
* Added support for non-english hashtags in messages
* Fixed invalid scrollbar width bug
* [Desktop] Added automatic country code detection
* [FirefoxOS] Improved PUSH-notifications for <= 1.1
* [FirefoxOS] Fixed emoji in notifications
* [FirefoxOS] Fixed attachment bug for <= 1.1
* [FirefoxOS] Added phonebook permissions handling
* [FirefoxOS] Added ability to share Gallery photos in Telegram

BIN
app/img/icons/IconsetW.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
app/img/icons/IconsetW_2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

8
app/index.html

@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
<meta property="og:url" content="https://web.telegram.org/">
<meta property="og:image" content="https://web.telegram.org/img/logo_share.png">
<meta property="og:site_name" content="Telegram Web">
<meta property="description" content="Welcome to the Web application of Telegram messenger. See https://github.com/zhukov/webogram for more info.">
<meta property="og:description" content="Welcome to the Web application of Telegram messenger. See https://github.com/zhukov/webogram for more info.">
</head>
@ -72,6 +73,13 @@ @@ -72,6 +73,13 @@
<script type="text/javascript" src="vendor/angularjs-toaster/toaster.js"></script>
<script type="text/javascript" src="vendor/clipboard/clipboard.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-demuxer-ogg.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-decoder-audio-opus.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-decoder-audio-vorbis.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-support.js"></script>
<script type="text/javascript" src="vendor/recorderjs/recorder.js"></script>
<script type="text/javascript" src="js/lib/utils.js"></script>
<script type="text/javascript" src="js/lib/bin_utils.js"></script>

2
app/js/app.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

5
app/js/background.js

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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) {
var isWindows = typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.match(/windows/i) ? true : false
chrome.app.window.create('../index.html', {
id: 'webogram-chat',
innerBounds: {
@ -14,6 +15,6 @@ chrome.app.runtime.onLaunched.addListener(function (launchData) { @@ -14,6 +15,6 @@ chrome.app.runtime.onLaunched.addListener(function (launchData) {
},
minWidth: 320,
minHeight: 400,
frame: { color: "#5682a3" }
frame: isWindows ? { color: '#5682a3' } : 'chrome'
})
})

39
app/js/controllers.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -31,7 +31,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -31,7 +31,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
LayoutSwitchService.start()
})
.controller('AppLoginController', function ($scope, $rootScope, $location, $timeout, $modal, $modalStack, MtpApiManager, ErrorService, NotificationsManager, PasswordManager, ChangelogNotifyService, IdleManager, LayoutSwitchService, TelegramMeWebService, _) {
.controller('AppLoginController', function ($scope, $rootScope, $location, $timeout, $modal, $modalStack, MtpApiManager, ErrorService, NotificationsManager, PasswordManager, ChangelogNotifyService, IdleManager, LayoutSwitchService, WebPushApiManager, TelegramMeWebService, _) {
$modalStack.dismissAll()
IdleManager.start()
@ -47,6 +47,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -47,6 +47,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return
}
TelegramMeWebService.setAuthorized(false)
WebPushApiManager.forceUnsubscribe()
})
var options = {dcID: 2, createNetworker: true}
@ -219,6 +220,10 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -219,6 +220,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.error = {field: 'phone'}
error.handled = true
break
case 'PHONE_NUMBER_APP_SIGNUP_FORBIDDEN':
$scope.error = {field: 'phone'}
break
}
})['finally'](function () {
if ($rootScope.idle.isIDLE || tsNow() - authKeyStarted > 60000) {
@ -1224,7 +1229,8 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -1224,7 +1229,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
photos: 'inputMessagesFilterPhotos',
video: 'inputMessagesFilterVideo',
documents: 'inputMessagesFilterDocument',
audio: 'inputMessagesFilterVoice'
audio: 'inputMessagesFilterVoice',
round: 'inputMessagesFilterRoundVideo',
}
var jump = 0
var moreJump = 0
@ -2146,8 +2152,8 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2146,8 +2152,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var lastIsRead = !historyMessage || !historyMessage.pFlags.unread
for (i = 0; i < len; i++) {
messageID = msgs[i]
if (messageID < maxID ||
history.ids.indexOf(messageID) !== -1) {
if (messageID > 0 && messageID < maxID ||
history.ids.indexOf(messageID) !== -1) {
continue
}
historyMessage = AppMessagesManager.wrapForHistory(messageID)
@ -2315,6 +2321,8 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2315,6 +2321,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
send: submitMessage,
replyClear: replyClear,
fwdsClear: fwdsClear,
toggleSlash: toggleSlash,
replyKeyboardToggle: replyKeyboardToggle,
type: 'new'
}
$scope.mentions = {}
@ -2341,9 +2349,6 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2341,9 +2349,6 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('last_message_edit', setEditLastMessage)
$scope.replyKeyboardToggle = replyKeyboardToggle
$scope.toggleSlash = toggleSlash
$rootScope.$watch('idle.isIDLE', function (newVal) {
if ($rootScope.idle.initial) {
return
@ -3537,7 +3542,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -3537,7 +3542,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}
$scope.$on('history_delete', function (e, historyUpdate) {
if (historyUpdate.msgs[$scope.messageID]) {
if (historyUpdate && historyUpdate.msgs && historyUpdate.msgs[$scope.messageID]) {
$modalInstance.dismiss()
}
})
@ -3571,7 +3576,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -3571,7 +3576,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}
$scope.$on('history_delete', function (e, historyUpdate) {
if (historyUpdate.msgs[$scope.messageID]) {
if (historyUpdate && historyUpdate.msgs && historyUpdate.msgs[$scope.messageID]) {
$modalInstance.dismiss()
}
})
@ -4455,9 +4460,11 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -4455,9 +4460,11 @@ angular.module('myApp.controllers', ['myApp.i18n'])
AppUsersManager.saveApiUser(user)
$modalInstance.close()
}, function (error) {
if (error.type == 'USERNAME_NOT_MODIFIED') {
error.handled = true
$modalInstance.close()
switch (error.type) {
case 'USERNAME_NOT_MODIFIED':
error.handled = true
$modalInstance.close()
break
}
})['finally'](function () {
delete $scope.profile.updating
@ -4470,9 +4477,9 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -4470,9 +4477,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return
}
MtpApiManager.invokeApi('account.checkUsername', {
username: newVal || ''
username: newVal
}).then(function (valid) {
if ($scope.profile.username != newVal) {
if ($scope.profile.username !== newVal) {
return
}
if (valid) {
@ -4481,7 +4488,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -4481,7 +4488,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.checked = {error: true}
}
}, function (error) {
if ($scope.profile.username != newVal) {
if ($scope.profile.username !== newVal) {
return
}
switch (error.type) {

356
app/js/directives.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -1494,6 +1494,9 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1494,6 +1494,9 @@ angular.module('myApp.directives', ['myApp.filters'])
return
}
if ($(sendFormWrap).is(':visible')) {
if (!sendForm || !sendForm.offsetHeight) {
sendForm = $('.im_send_form', element)[0]
}
$(sendFormWrap).css({
height: $(sendForm).height()
})
@ -1547,30 +1550,44 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1547,30 +1550,44 @@ angular.module('myApp.directives', ['myApp.filters'])
}
})
.directive('mySendForm', function (_, $q, $timeout, $compile, $modalStack, $http, $interpolate, Storage, AppStickersManager, AppDocsManager, ErrorService, AppInlineBotsManager, FileManager, shouldFocusOnInteraction) {
.directive('mySendForm', function (_, $q, $timeout, $interval, $window, $compile, $modalStack, $http, $interpolate, Storage, AppStickersManager, AppDocsManager, ErrorService, AppInlineBotsManager, FileManager, shouldFocusOnInteraction) {
return {
link: link,
templateUrl: templateUrl('send_form'),
scope: {
draftMessage: '=',
replyKeyboard: '=',
mentions: '=',
commands: '='
}
}
function link ($scope, element, attrs) {
var messageFieldWrap = $('.im_send_field_wrap', element)[0]
var messageField = $('textarea', element)[0]
var emojiButton = $('.composer_emoji_insert_btn', element)[0]
var emojiPanel = $('.composer_emoji_panel', element)[0]
var fileSelects = $('input', element)
var dropbox = $('.im_send_dropbox_wrap', element)[0]
var messageFieldWrap = $('.im_send_field_wrap', element)[0]
var dragStarted
var dragTimeout
var submitBtn = $('.im_submit', element)[0]
var voiceRecorderWrap = $('.im_voice_recorder_wrap', element)[0]
var voiceRecordBtn = $('.im_record', element)[0]
var stickerImageCompiled = $compile('<a class="composer_sticker_btn" data-sticker="{{::document.id}}" my-load-sticker document="document" thumb="true" img-class="composer_sticker_image"></a>')
var cachedStickerImages = {}
var voiceRecorder = null
var voiceRecordSupported = Recorder.isRecordingSupported()
var voiceRecordDurationInterval = null
if (voiceRecordSupported) {
element.addClass('im_record_supported')
}
$scope.voiceRecorder = {duration: 0, recording: false, processing: false}
var emojiTooltip = new EmojiTooltip(emojiButton, {
getStickers: function (callback) {
AppStickersManager.getStickers().then(callback)
@ -1683,6 +1700,138 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1683,6 +1700,138 @@ angular.module('myApp.directives', ['myApp.filters'])
})
})
$(voiceRecordBtn).on('contextmenu', cancelEvent)
var voiceRecordTouch = Config.Navigator.touch ? true : false
var voiceRecordEvents = {
start: voiceRecordTouch ? 'touchstart' : 'mousedown',
move: voiceRecordTouch ? 'touchmove' : 'mousemove',
stop: voiceRecordTouch ? 'touchend blur' : 'mouseup blur'
}
var onRecordStart, onRecordStreamReady, onRecordStop
$(voiceRecordBtn).on(voiceRecordEvents.start, function(event) {
if ($scope.voiceRecorder.processing) {
return
}
voiceRecorder = new Recorder({
monitorGain: 0,
numberOfChannels: 1,
bitRate: 64000,
encoderSampleRate: 48000,
encoderPath: 'vendor/recorderjs/encoder_worker.js'
})
onRecordStart = function(e) {
var startTime = tsNow(true)
voiceRecordDurationInterval = $interval(function() {
$scope.voiceRecorder.duration = tsNow(true) - startTime
}, 1000)
$scope.$apply(function() {
$scope.voiceRecorder.recording = true
})
}
voiceRecorder.addEventListener('start', onRecordStart)
onRecordStreamReady = function(e) {
voiceRecorder.start()
}
voiceRecorder.addEventListener('streamReady', onRecordStreamReady)
voiceRecorder.initStream()
var curHover = false
var curBoundaries = {}
var updateVoiceHoverBoundaries = function () {
var boundElement = $('.im_bottom_panel_wrap')
var offset = boundElement.offset()
curBoundaries = {
top: offset.top,
left: offset.left,
width: boundElement.outerWidth(),
height: boundElement.outerHeight(),
}
}
var updateVoiceHoveredClass = function (event, returnHover) {
var originalEvent = event.originalEvent || event
var touch = voiceRecordTouch
? originalEvent.changedTouches && originalEvent.changedTouches[0]
: originalEvent
var isHover = touch &&
touch.pageX >= curBoundaries.left &&
touch.pageX <= curBoundaries.left + curBoundaries.width &&
touch.pageY >= curBoundaries.top &&
touch.pageY <= curBoundaries.top + curBoundaries.height
if (curHover != isHover) {
element.toggleClass('im_send_form_hover', isHover)
curHover = isHover
}
return returnHover && isHover
}
updateVoiceHoverBoundaries()
updateVoiceHoveredClass(event)
onRecordStop = function(event) {
$($window).off(voiceRecordEvents.move, updateVoiceHoveredClass)
$($window).off(voiceRecordEvents.stop, onRecordStop)
var isHover = event == 'blur' ? false : updateVoiceHoveredClass(event, true)
if ($scope.voiceRecorder.duration > 0 && isHover) {
$scope.voiceRecorder.processing = true
voiceRecorder.addEventListener('dataAvailable', function(e) {
var blob = blobConstruct([e.detail], 'audio/ogg')
console.warn(dT(), 'got audio', blob)
$scope.$apply(function () {
if (blob.size !== undefined &&
blob.size > 1024) {
$scope.draftMessage.files = [blob]
$scope.draftMessage.isMedia = true
}
$scope.voiceRecorder.processing = false
})
})
}
cancelRecord()
}
if (!Config.Mobile) {
$(voiceRecorderWrap).css({
height: messageFieldWrap.offsetHeight,
width: messageFieldWrap.offsetWidth
})
}
$($window).on(voiceRecordEvents.move, updateVoiceHoveredClass)
$($window).one(voiceRecordEvents.stop, onRecordStop)
})
function cancelRecord() {
if (voiceRecorder) {
voiceRecorder.stop()
voiceRecorder.removeEventListener('streamReady', onRecordStreamReady)
voiceRecorder.removeEventListener('start', onRecordStart)
}
if ($scope.voiceRecorder.recording) {
$interval.cancel(voiceRecordDurationInterval)
$scope.$apply(function() {
$scope.voiceRecorder.recording = false
$scope.voiceRecorder.duration = 0
})
}
}
var sendOnEnter = true
function updateSendSettings () {
Storage.get('send_ctrlenter').then(function (sendOnCtrl) {
@ -1867,12 +2016,11 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1867,12 +2016,11 @@ angular.module('myApp.directives', ['myApp.filters'])
if (e.type == 'dragenter' || e.type == 'dragover') {
if (dragStateChanged) {
if (!Config.Mobile) {
$(emojiButton).hide()
}
$(dropbox)
.css({height: messageFieldWrap.offsetHeight + 2, width: messageFieldWrap.offsetWidth})
.show()
$(dropbox).css({
height: messageFieldWrap.offsetHeight,
width: messageFieldWrap.offsetWidth
})
element.addClass('im_send_form_dragging')
}
} else {
if (e.type == 'drop') {
@ -1882,10 +2030,7 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1882,10 +2030,7 @@ angular.module('myApp.directives', ['myApp.filters'])
})
}
dragTimeout = setTimeout(function () {
$(dropbox).hide()
if (!Config.Mobile) {
$(emojiButton).show()
}
element.removeClass('im_send_form_dragging')
dragStarted = false
dragTimeout = false
}, 300)
@ -2195,13 +2340,95 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2195,13 +2340,95 @@ angular.module('myApp.directives', ['myApp.filters'])
}, 200)
})
}
}
})
.directive('myLoadRound', function (AppDocsManager, $timeout) {
// Autoplay small GIFs
// if (!Config.Mobile &&
// $scope.document.size &&
// $scope.document.size < 1024 * 1024) {
// $scope.toggle()
// }
var currentPlayer = false
var currentPlayerScope = false
return {
link: link,
templateUrl: templateUrl('full_round'),
scope: {
document: '='
}
}
function checkPlayer(newPlayer, newScope) {
if (currentPlayer === newPlayer) {
return false
}
if (currentPlayer) {
currentPlayer.pause()
currentPlayer.currentTime = 0
currentPlayerScope.isActive = false
}
currentPlayer = newPlayer
currentPlayerScope = newScope
}
function link ($scope, element, attrs) {
var imgWrap = $('.img_round_image_wrap', element)
imgWrap.css({width: $scope.document.thumb.width, height: $scope.document.thumb.height})
var downloadPromise = false
$scope.isActive = false
$scope.toggle = function (e) {
if (e && checkClick(e, true)) {
AppDocsManager.saveDocFile($scope.document.id)
return false
}
if ($scope.document.url) {
$scope.isActive = !$scope.isActive
onContentLoaded(function () {
$scope.$emit('ui_height')
var video = $('video', element)[0]
if (video) {
if (!$scope.isActive) {
video.pause()
video.currentTime = 0
} else {
checkPlayer(video, $scope)
video.play()
}
}
})
return
}
if (downloadPromise) {
downloadPromise.cancel()
downloadPromise = false
return
}
downloadPromise = AppDocsManager.downloadDoc($scope.document.id)
downloadPromise.then(function () {
$timeout(function () {
var video = $('video', element)[0]
checkPlayer(video, $scope)
$(video).on('ended', function () {
if ($scope.isActive) {
$scope.toggle()
}
})
$scope.isActive = true
}, 200)
})
}
$scope.$on('ui_history_change', function () {
if ($scope.isActive) {
$scope.toggle()
}
})
}
})
@ -2411,18 +2638,23 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2411,18 +2638,23 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) {
var width = element.attr('width') || 200
var height = element.attr('height') || 200
var apiKey = Config.ExtCredentials.gmaps.api_key
var zoom = width > 200 ? 15 : 13
var useGoogle = false
var src
element.attr('src', 'img/blank.gif')
var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false&center=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=' + zoom + '&size=' + width + 'x' + height + '&scale=2&markers=color:red|size:big|' + $scope.point['lat'] + ',' + $scope.point['long']
var useApiKey = true
if (useApiKey) {
src += '&key=' + apiKey
if (useGoogle) {
var apiKey = Config.ExtCredentials.gmaps.api_key
var useApiKey = true
src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false&center=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=' + zoom + '&size=' + width + 'x' + height + '&scale=2&markers=color:red|size:big|' + $scope.point['lat'] + ',' + $scope.point['long']
if (useApiKey) {
src += '&key=' + apiKey
}
} else {
src = 'https://static-maps.yandex.ru/1.x/?l=map&ll=' + $scope.point['long'] + ',' + $scope.point['lat'] + '&z=' + zoom + '&size=' + width + ',' + height + '&scale=1&pt=' + $scope.point['long'] + ',' + $scope.point['lat'] + ',pm2rdm&lang=en_US'
}
element.attr('src', 'img/blank.gif')
ExternalResourcesManager.downloadByURL(src).then(function (url) {
element.attr('src', url.valueOf())
})
@ -3033,8 +3265,31 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3033,8 +3265,31 @@ angular.module('myApp.directives', ['myApp.filters'])
}
})
.directive('myOgvPlayer', function ($compile) {
return {
link: function ($scope, $element, $attrs) {
var audio = $scope.audio
var playerEl
if (audio.mime_type == 'audio/ogg' &&
// false &&
OGVCompat.hasWebAudio() && // we don't want to use Flash
OGVCompat.supported('OGVPlayer')) {
playerEl = new OGVPlayer({debug: false, worker: false})
} else {
playerEl = document.createElement('audio')
}
$(playerEl).attr('media-player', $attrs.myOgvPlayer)
$(playerEl).attr('src', '{{::' + $attrs.src + '}}')
$compile(playerEl)($scope)
$($element).append(playerEl)
}
}
})
.directive('myAudioPlayer', function ($timeout, $q, Storage, AppDocsManager, AppMessagesManager, ErrorService) {
var currentPlayer = false
var currentPlayerScope = false
var audioVolume = 0.5
Storage.get('audio_volume').then(function (newAudioVolume) {
@ -3060,20 +3315,23 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3060,20 +3315,23 @@ angular.module('myApp.directives', ['myApp.filters'])
return {
link: link,
scope: {
audio: '=',
message: '='
audio: '='
},
templateUrl: templateUrl('audio_player')
}
function checkPlayer (newPlayer) {
if (newPlayer === currentPlayer) {
function checkAudioPlayer (newPlayerScope) {
if (newPlayerScope === currentPlayerScope) {
return false
}
if (currentPlayer) {
currentPlayer.pause()
if (currentPlayerScope) {
;(function ($scope) {
setZeroTimeout(function () {
$scope.mediaPlayer.player.pause()
})
})(currentPlayerScope)
}
currentPlayer = newPlayer
currentPlayerScope = newPlayerScope
}
function link ($scope, element, attrs) {
@ -3081,20 +3339,34 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3081,20 +3339,34 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.volume = audioVolume
$scope.mediaPlayer = {}
if ($scope.$parent.messageId) {
$scope.message = AppMessagesManager.wrapForHistory($scope.$parent.messageId)
}
$scope.download = function () {
AppDocsManager.saveDocFile($scope.audio.id)
}
$scope.duration = function () {
if ($scope.mediaPlayer.player &&
$scope.mediaPlayer.player.duration > 0 &&
$scope.mediaPlayer.player.duration < Infinity) {
return $scope.mediaPlayer.player.duration
}
return $scope.audio && $scope.audio.duration || 0
}
$scope.togglePlay = function () {
if ($scope.audio.url) {
checkPlayer($scope.mediaPlayer.player)
$scope.mediaPlayer.player.playPause()
checkAudioPlayer($scope)
setZeroTimeout(function () {
$scope.mediaPlayer.player.playPause()
})
} else if ($scope.audio.progress && $scope.audio.progress.enabled) {
} else {
AppDocsManager.downloadDoc($scope.audio.id).then(function () {
onContentLoaded(function () {
var errorListenerEl = $('audio', element)[0] || element[0]
var errorListenerEl = $('audio, ogvjs', element)[0] || element[0]
if (errorListenerEl) {
var errorAlready = false
var onAudioError = function (event) {
@ -3122,13 +3394,13 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3122,13 +3394,13 @@ angular.module('myApp.directives', ['myApp.filters'])
})
}
setTimeout(function () {
checkPlayer($scope.mediaPlayer.player)
checkAudioPlayer($scope)
$scope.mediaPlayer.player.setVolume(audioVolume)
$scope.mediaPlayer.player.play()
if ($scope.message &&
!$scope.message.pFlags.out &&
$scope.message.pFlags.media_unread) {
!$scope.message.pFlags.out &&
$scope.message.pFlags.media_unread) {
AppMessagesManager.readMessages([$scope.message.mid])
}
}, 300)
@ -3773,4 +4045,4 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3773,4 +4045,4 @@ angular.module('myApp.directives', ['myApp.filters'])
return {
link: link
}
})
})

2
app/js/directives_mobile.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

2
app/js/filters.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

2
app/js/lib/bin_utils.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

8
app/js/lib/config.js

File diff suppressed because one or more lines are too long

2
app/js/lib/crypto_worker.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

7
app/js/lib/mtproto.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -1179,7 +1179,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) @@ -1179,7 +1179,6 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.pendingAcks = []
var self = this
this.sendEncryptedRequest(message).then(function (result) {
self.toggleOffline(false)
// console.log('parse for', message)
@ -1610,7 +1609,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) @@ -1610,7 +1609,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
break
}
case 'msg_new_detailed_info':
// this.ackMessage(message.answer_msg_id)
if (this.pendingAcks.indexOf(message.answer_msg_id)) {
break
}
this.reqResendMessage(message.answer_msg_id)
break

16
app/js/lib/mtproto_wrapper.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, MtpSingleInstanceService, AppRuntimeManager, ErrorService, qSync, $rootScope, $q, TelegramMeWebService) {
.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, MtpSingleInstanceService, AppRuntimeManager, ErrorService, qSync, $rootScope, $q, WebPushApiManager, TelegramMeWebService) {
var cachedNetworkers = {}
var cachedUploadNetworkers = {}
var cachedExportPromise = {}
@ -47,11 +47,12 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) @@ -47,11 +47,12 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
for (var dcID = 1; dcID <= 5; dcID++) {
storageKeys.push('dc' + dcID + '_auth_key')
}
WebPushApiManager.forceUnsubscribe()
return Storage.get(storageKeys).then(function (storageResult) {
var logoutPromises = []
for (var i = 0; i < storageResult.length; i++) {
if (storageResult[i]) {
logoutPromises.push(mtpInvokeApi('auth.logOut', {}, {dcID: i + 1}))
logoutPromises.push(mtpInvokeApi('auth.logOut', {}, {dcID: i + 1, ignoreErrors: true}))
}
}
return $q.all(logoutPromises).then(function () {
@ -71,7 +72,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) @@ -71,7 +72,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}
function mtpClearStorage () {
var saveKeys = []
var saveKeys = ['user_auth', 't_user_auth', 'dc', 't_dc']
for (var dcID = 1; dcID <= 5; dcID++) {
saveKeys.push('dc' + dcID + '_auth_key')
saveKeys.push('t_dc' + dcID + '_auth_key')
@ -156,6 +157,13 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) @@ -156,6 +157,13 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
error = {message: error}
}
deferred.reject(error)
if (options.ignoreErrors) {
return
}
if (error.code == 406) {
error.handled = true
}
if (!options.noErrorBox) {
error.input = method

34
app/js/lib/ng_utils.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -1918,14 +1918,15 @@ angular.module('izhukov.utils', []) @@ -1918,14 +1918,15 @@ angular.module('izhukov.utils', [])
}
function wrapUrl (url, unsafe) {
if (!url.match(/^https?:\/\//i)) {
if (!url.match(/^(https?|tg):\/\//i)) {
url = 'http://' + url
}
var tgMeMatch
var telescoPeMatch
if (unsafe == 2) {
url = 'tg://unsafe_url?url=' + encodeURIComponent(url)
}
else if ( (tgMeMatch = url.match(/^https?:\/\/t(?:elegram)?\.me\/(.+)/))) {
else if ((tgMeMatch = url.match(/^https?:\/\/t(?:elegram)?\.me\/(.+)/))) {
var path = tgMeMatch[1].split('/')
switch (path[0]) {
case 'joinchat':
@ -1944,6 +1945,9 @@ angular.module('izhukov.utils', []) @@ -1944,6 +1945,9 @@ angular.module('izhukov.utils', [])
}
}
}
else if ((telescoPeMatch = url.match(/^https?:\/\/telesco\.pe\/([^/?]+)\/(\d+)/))) {
url = 'tg://resolve?domain=' + telescoPeMatch[1] + '&post=' + telescoPeMatch[2]
}
else if (unsafe) {
url = 'tg://unsafe_url?url=' + encodeURIComponent(url)
}
@ -2079,6 +2083,29 @@ angular.module('izhukov.utils', []) @@ -2079,6 +2083,29 @@ angular.module('izhukov.utils', [])
})
}
function forceUnsubscribe() {
if (!isAvailable) {
return
}
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.getSubscription().then(function (subscription) {
console.warn('force unsubscribe', subscription)
if (subscription) {
subscription.unsubscribe().then(function(successful) {
console.warn('force unsubscribe successful', successful)
isPushEnabled = false
}).catch(function(e) {
console.error('Unsubscription error: ', e)
})
}
}).catch(function(e) {
console.error('Error thrown while unsubscribing from ' +
'push messaging.', e)
})
})
}
function isAliveNotify() {
if (!isAvailable ||
$rootScope.idle && $rootScope.idle.deactivated) {
@ -2172,6 +2199,7 @@ angular.module('izhukov.utils', []) @@ -2172,6 +2199,7 @@ angular.module('izhukov.utils', [])
isPushEnabled: isPushEnabled,
subscribe: subscribe,
unsubscribe: unsubscribe,
forceUnsubscribe: forceUnsubscribe,
hidePushNotifications: hidePushNotifications,
setLocalNotificationsDisabled: setLocalNotificationsDisabled,
setSettings: setSettings

133
app/js/lib/schema.tl.txt → app/js/lib/schema.tl

@ -37,6 +37,7 @@ inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; @@ -37,6 +37,7 @@ inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
inputMediaPhotoExternal#b55f4f18 url:string caption:string = InputMedia;
inputMediaDocumentExternal#e5e9607c url:string caption:string = InputMedia;
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#92153685 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string start_param:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@ -59,13 +60,13 @@ peerChat#bad0e5bb chat_id:int = Peer; @@ -59,13 +60,13 @@ peerChat#bad0e5bb chat_id:int = Peer;
peerChannel#bddde532 channel_id:int = Peer;
storage.fileUnknown#aa963b05 = storage.FileType;
storage.filePartial#40bc6f52 = storage.FileType;
storage.fileJpeg#7efe0e = storage.FileType;
storage.fileGif#cae1aadf = storage.FileType;
storage.filePng#a4f63c0 = storage.FileType;
storage.filePdf#ae1e508d = storage.FileType;
storage.fileMp3#528a0677 = storage.FileType;
storage.fileMov#4b09ebbc = storage.FileType;
storage.filePartial#40bc6f52 = storage.FileType;
storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
@ -73,7 +74,7 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL @@ -73,7 +74,7 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
userEmpty#200250ba id:int = User;
user#d10d979a flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string = User;
user#2e13f4c3 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
@ -117,6 +118,7 @@ messageMediaDocument#f3e02ea8 document:Document caption:string = MessageMedia; @@ -117,6 +118,7 @@ messageMediaDocument#f3e02ea8 document:Document caption:string = MessageMedia;
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia;
messageMediaGame#fdb19008 game:Game = MessageMedia;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
@ -132,6 +134,8 @@ messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageActio @@ -132,6 +134,8 @@ messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageActio
messageActionPinMessage#94bd38ed = MessageAction;
messageActionHistoryClear#9fbab604 = MessageAction;
messageActionGameScore#92a72876 game_id:long score:int = MessageAction;
messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;
messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
dialog#66ffba14 flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
@ -180,7 +184,7 @@ inputReportReasonViolence#1e22c78d = ReportReason; @@ -180,7 +184,7 @@ inputReportReasonViolence#1e22c78d = ReportReason;
inputReportReasonPornography#2e59d922 = ReportReason;
inputReportReasonOther#e1746d0a text:string = ReportReason;
userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull;
userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact;
@ -226,6 +230,8 @@ inputMessagesFilterVoice#50f5c392 = MessagesFilter; @@ -226,6 +230,8 @@ inputMessagesFilterVoice#50f5c392 = MessagesFilter;
inputMessagesFilterMusic#3751b49e = MessagesFilter;
inputMessagesFilterChatPhotos#3a20ecb8 = MessagesFilter;
inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFilter;
inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter;
inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update;
@ -280,9 +286,13 @@ updateRecentStickers#9a422c20 = Update; @@ -280,9 +286,13 @@ updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
updatePtsChanged#3354678f = Update;
updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update;
updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
updateDialogPinned#d711a2cc flags:# pinned:flags.0?true peer:Peer = Update;
updatePinnedDialogs#d8caf68d flags:# order:flags.0?Vector<Peer> = Update;
updateBotWebhookJSON#8317c0c3 data:DataJSON = Update;
updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update;
updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -305,10 +315,11 @@ photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> = @@ -305,10 +315,11 @@ photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> =
photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
upload.fileCdnRedirect#1508485a dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true id:int ip_address:string port:int = DcOption;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true id:int ip_address:string port:int = DcOption;
config#3af6fb5f flags:# phonecalls_enabled:flags.1?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int disabled_features:Vector<DisabledFeature> = Config;
config#cb601684 flags:# phonecalls_enabled:flags.1?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string disabled_features:Vector<DisabledFeature> = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -366,6 +377,8 @@ sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction; @@ -366,6 +377,8 @@ sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
sendMessageChooseContactAction#628cbc6f = SendMessageAction;
sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
contacts.found#1aa1f784 results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
@ -398,7 +411,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; @@ -398,7 +411,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
documentAttributeAnimated#11b58939 = DocumentAttribute;
documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute;
documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
@ -465,6 +478,7 @@ keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; @@ -465,6 +478,7 @@ keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
@ -473,9 +487,6 @@ replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags @@ -473,9 +487,6 @@ replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags
replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
help.appChangelogEmpty#af7e0394 = help.AppChangelog;
help.appChangelog#2a137e7c message:string media:MessageMedia entities:Vector<MessageEntity> = help.AppChangelog;
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
messageEntityMention#fa04579d offset:int length:int = MessageEntity;
messageEntityHashtag#6f635b0d offset:int length:int = MessageEntity;
@ -656,17 +667,66 @@ pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3? @@ -656,17 +667,66 @@ pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3?
pageBlockEmbedPost#292c7be9 url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockCollage#8b31c4f items:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockSlideshow#130c8963 items:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockChannel#ef1751b5 channel:Chat = PageBlock;
pagePart#8dee6c44 blocks:Vector<PageBlock> photos:Vector<Photo> videos:Vector<Document> = Page;
pageFull#d7a19d69 blocks:Vector<PageBlock> photos:Vector<Photo> videos:Vector<Document> = Page;
phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason;
phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason;
dataJSON#7d748d04 data:string = DataJSON;
labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true currency:string prices:Vector<LabeledPrice> = Invoice;
paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge;
postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress;
paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> dc_id:int = WebDocument;
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
payments.paymentVerficationNeeded#6b56b921 url:string = payments.PaymentResult;
payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
shippingOption#b6213cdf id:string title:string prices:Vector<LabeledPrice> = ShippingOption;
inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem;
inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
phoneCallEmpty#5366c915 id:long = PhoneCall;
phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
phoneCallRequested#6c448ae8 id:long access_hash:long date:int admin_id:int participant_id:int g_a:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
@ -674,10 +734,12 @@ phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?tr @@ -674,10 +734,12 @@ phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?tr
phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason;
phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason;
upload.cdnFileReuploadNeeded#eea8e46e request_token:bytes = upload.CdnFile;
upload.cdnFile#a99fca4f bytes:bytes = upload.CdnFile;
cdnPublicKey#c982eaba dc_id:int public_key:string = CdnPublicKey;
cdnConfig#5725e40a public_keys:Vector<CdnPublicKey> = CdnConfig;
---functions---
@ -731,6 +793,7 @@ account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.Passw @@ -731,6 +793,7 @@ account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.Passw
account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool;
account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode;
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
@ -747,7 +810,7 @@ contacts.exportCard#84e53737 = Vector<int>; @@ -747,7 +810,7 @@ contacts.exportCard#84e53737 = Vector<int>;
contacts.importCard#4fe196fe export_card:Vector<int> = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
messages.getMessages#4222fa74 id:Vector<int> = messages.Messages;
@ -833,6 +896,9 @@ messages.getWebPage#32ca8f91 url:string hash:int = WebPage; @@ -833,6 +896,9 @@ messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
messages.toggleDialogPin#3289be6a flags:# pinned:flags.0?true peer:InputPeer = Bool;
messages.reorderPinnedDialogs#959ff644 flags:# force:flags.0?true order:Vector<InputPeer> = Bool;
messages.getPinnedDialogs#e254d64e = messages.PeerDialogs;
messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;
messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -846,6 +912,9 @@ photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int @@ -846,6 +912,9 @@ photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
upload.getFile#e3a6cfb5 location:InputFileLocation offset:int limit:int = upload.File;
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
upload.reuploadCdnFile#2e7a2020 file_token:bytes request_token:bytes = Bool;
help.getConfig#c4f9186b = Config;
help.getNearestDc#1fb33026 = NearestDc;
@ -853,9 +922,10 @@ help.getAppUpdate#ae2de196 = help.AppUpdate; @@ -853,9 +922,10 @@ help.getAppUpdate#ae2de196 = help.AppUpdate;
help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getInviteText#4d392343 = help.InviteText;
help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#b921197a = help.AppChangelog;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@ -885,7 +955,26 @@ channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; @@ -885,7 +955,26 @@ channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats;
phone.requestCall#a41aa5e4 user_id:InputUser random_id:int g_a:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.acceptCall#220f0b20 peer:InputPhoneCall g_b:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
phone.discardCall#5dfbcddc peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Bool;
phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.removeStickerFromSet#4255934 sticker:InputDocument = Bool;
stickers.changeStickerPosition#4ed705ca sticker:InputDocument position:int = Bool;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
phone.getCallConfig#55451fa9 = DataJSON;
phone.requestCall#5b95b3d4 user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;

2
app/js/lib/tl_utils.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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

3
app/js/lib/utils.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -381,6 +381,7 @@ function templateUrl (tplName) { @@ -381,6 +381,7 @@ function templateUrl (tplName) {
media_modal_layout: 'desktop',
slider: 'desktop',
reply_message: 'desktop',
full_round: 'desktop',
message_body: 'desktop',
message_media: 'desktop',
message_attach_game: 'desktop',

14
app/js/locales/de-de.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Löschen und verlassen",
"group_modal_menu_clear_history": "Verlauf löschen",
"group_modal_delete_group": "Gruppe löschen",
"group_modal_join": "Gruppe beitreten",
"group_modal_settings": "Einstellungen",
"group_modal_notifications": "Benachrichtigungen",
"group_modal_menu_share_link": "Per Link zur Gruppe einladen",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Beschreibung",
"channel_modal_share_link": "Link teilen",
"channel_modal_share_loading": "Lade{dots}",
"channel_modal_menu_edit": "Kanal bearbeiten",
"channel_modal_join": "Kanal beitreten",
"channel_modal_add_member": "Mitglied hinzufügen",
"channel_modal_leave_channel": "Kanal verlassen",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Aktualisierungen bei **Telegram Web**",
"changelog_modal_header_new_updates_md": "**Telegram Web** wurde aktualisiert!",
"changelog_modal_title_current_version": "aktuelle Version",
"changelog_modal_full_description_md": "Offizielle Messaging App basierend auf Telegram API für Geschwindigkeit und Sicherheit.\n\nDiese Software steht unter der GNU GPL Version 3.",
"changelog_modal_changelog_link": "Changelog",
"group_create_contacts_modal_title": "Neue Gruppe",
"group_create_modal_title": "Gruppe erstellen",
"group_create_name": "Gruppenname",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Entwurf:",
"conversation_media_photo": "Bild",
"conversation_media_video": "Video",
"conversation_media_round": "Videonachricht",
"conversation_media_document": "Datei",
"conversation_media_sticker": "Sticker",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Standort",
"conversation_media_contact": "Kontakt",
"conversation_media_attachment": "Anhang",
"conversation_media_unsupported": "Nicht unterstützte Datei",
"conversation_search_peer": "Diesen Chat durchsuchen",
"conversation_group_created": "hat die Gruppe erstellt",
"conversation_group_renamed": "hat den Gruppennamen geändert",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Bild geändert",
"message_service_removed_channel_photo": "Bild gelöscht",
"message_service_scored_X": "{'one': 'erzielte {} Punkte', 'other': 'erzielten {} Punkte'}",
"message_service_payment_sent": "Zahlung gesendet",
"message_action_reply": "Antworten",
"message_action_edit": "Bearbeiten",
"message_action_delete": "Löschen",
@ -377,7 +384,8 @@ @@ -377,7 +384,8 @@
"error_modal_media_not_supported_description": "Dein Browser kann diese Mediendatei nicht wiedergeben. Lade die Datei herunter und versuche sie mit einem externen Player zu öffnen.",
"error_modal_username_not_found_description": "Es gibt leider kein Telegram Konto mit dem Benutzernamen.",
"error_modal_phonecalls_not_supported_description_md": "Leider werden Anrufe in der Web App noch nicht unterstützt.\n\nDu kannst {user} über unsere mobilen Apps oder Desktop Clients anrufen.\n{download-link: Download »}",
"error_modal_bad_request_description": "Eine Parameter fehlt oder ist ungültig.",
"error_modal_app_signup_forbidden_md": "Du hast noch kein Telegram Konto, bitte **registriere** dich zuerst über {signup-link: Android / iPhone}.",
"error_modal_bad_request_description": "Ein Parameter fehlt oder ist ungültig.",
"error_modal_unauthorized_description": "Diese Aktion benötigt autorisierten Zugriff. Bitte {login-link: melde dich an}.",
"error_modal_forbidden_description": "Diese Aktion ist für dich nicht erlaubt.",
"error_modal_not_found_description": "Die Seite wurde nicht gefunden.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Videos",
"head_media_documents": "Dateien",
"head_media_audio": "Sprachnachrichten",
"head_media_round": "Videonachrichten",
"head_media_search": "Suchen",
"head_about": "Info",
"head_clear_all": "Verlauf löschen",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Senden",
"im_submit_edit_message": "Speichern",
"im_edit_message_title": "Nachricht bearbeiten",
"im_voice_recording_label": "Zum Abbrechen rausbewegen",
"im_voice_recording_cancel_label": "Loslassen, um abzubrechen",
"im_voice_processing_label": "Verarbeite{dots}",
"login_sign_in": "Anmelden",
"login_enter_number_description": "Land auswählen und Nummer eintragen",
"login_incorrect_number": "Falsche Telefonnummer",

14
app/js/locales/en-us.json

@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
"group_modal_menu_delete_group": "Delete and exit",
"group_modal_menu_clear_history": "Clear history",
"group_modal_delete_group": "Delete group",
"group_modal_join": "Join group",
"group_modal_settings": "Settings",
"group_modal_notifications": "Notifications",
"group_modal_menu_share_link": "Invite to group via link",
@ -40,6 +41,7 @@ @@ -40,6 +41,7 @@
"channel_modal_description": "Description",
"channel_modal_share_link": "Share link",
"channel_modal_share_loading": "Loading{dots}",
"channel_modal_menu_edit": "Edit channel",
"channel_modal_join": "Join channel",
"channel_modal_add_member": "Invite members",
"channel_modal_leave_channel": "Leave channel",
@ -187,6 +189,9 @@ @@ -187,6 +189,9 @@
"changelog_modal_header_recent_updates_md": "Recent updates in **Telegram Web**",
"changelog_modal_header_new_updates_md": "**Telegram Web** has been updated!",
"changelog_modal_title_current_version": "current version",
"changelog_modal_full_description_md": "Official free messaging app based on Telegram API for speed and security.\n\nThis software is licensed under GNU GPL version 3.",
"changelog_modal_changelog_link": "Changelog",
"changelog_app_version": "Version {version}",
"group_create_contacts_modal_title": "New group",
"group_create_modal_title": "Create group",
@ -311,6 +316,7 @@ @@ -311,6 +316,7 @@
"conversation_draft": "Draft:",
"conversation_media_photo": "Photo",
"conversation_media_video": "Video",
"conversation_media_round": "Video message",
"conversation_media_document": "File",
"conversation_media_sticker": "Sticker",
"conversation_media_gif": "GIF",
@ -318,6 +324,7 @@ @@ -318,6 +324,7 @@
"conversation_media_location": "Location",
"conversation_media_contact": "Contact",
"conversation_media_attachment": "Attachment",
"conversation_media_unsupported": "Unsupported attachment",
"conversation_search_peer": "Search in this chat",
@ -374,6 +381,7 @@ @@ -374,6 +381,7 @@
"message_service_changed_channel_photo": "Channel photo updated",
"message_service_removed_channel_photo": "Channel photo removed",
"message_service_scored_X": "{'one': 'scored {}', 'other': 'scored {}'}",
"message_service_payment_sent": "Payment sent",
"message_action_reply": "Reply",
"message_action_edit": "Edit",
@ -416,6 +424,7 @@ @@ -416,6 +424,7 @@
"error_modal_media_not_supported_description": "Your browser cannot play this media file. Try downloading the file and opening it in a standalone player.",
"error_modal_username_not_found_description": "There is no Telegram account with the username you provided.",
"error_modal_phonecalls_not_supported_description_md": "Unfortunately calls are not supported in the Web App at the moment.\n\nYou can call {user} using our mobile apps or native desktop applications.\n{download-link: Download »}",
"error_modal_app_signup_forbidden_md": "You don't have a Telegram account yet, please **sign up** with {signup-link: Android / iPhone} first.",
"error_modal_bad_request_description": "One of the params is missing or invalid.",
"error_modal_unauthorized_description": "This action requires authorization access. Please {login-link: log in}.",
@ -450,6 +459,7 @@ @@ -450,6 +459,7 @@
"head_media_video": "Videos",
"head_media_documents": "Files",
"head_media_audio": "Voice messages",
"head_media_round": "Video messages",
"head_media_search": "Search",
"head_about": "About",
"head_clear_all": "Clear history",
@ -521,7 +531,9 @@ @@ -521,7 +531,9 @@
"im_submit_message": "Send",
"im_submit_edit_message": "Save",
"im_edit_message_title": "Edit message",
"im_voice_recording_label": "Release outside this form to cancel",
"im_voice_recording_cancel_label": "Release to cancel record",
"im_voice_processing_label": "Processing{dots}",
"login_sign_in": "Sign in",
"login_enter_number_description": "Please choose your country and enter your full phone number.",
"login_incorrect_number": "Incorrect phone number",

12
app/js/locales/es-es.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Eliminar y salir",
"group_modal_menu_clear_history": "Eliminar historial",
"group_modal_delete_group": "Eliminar grupo",
"group_modal_join": "Unirme al grupo",
"group_modal_settings": "Ajustes",
"group_modal_notifications": "Notificaciones",
"group_modal_menu_share_link": "Invitar al grupo con un enlace",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Descripción",
"channel_modal_share_link": "Compartir enlace",
"channel_modal_share_loading": "Cargando{dots}",
"channel_modal_menu_edit": "Editar canal",
"channel_modal_join": "Unirme al canal",
"channel_modal_add_member": "Invitar miembros",
"channel_modal_leave_channel": "Salir del canal",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Últimas actualizaciones en **Telegram Web**",
"changelog_modal_header_new_updates_md": "¡**Telegram Web** se ha actualizado!",
"changelog_modal_title_current_version": "versión actual",
"changelog_modal_full_description_md": "App oficial de mensajería gratuita basada en la API de Telegram para la velocidad y seguridad.\n\nEste software está bajo licencia GNU GPL versión 3.",
"changelog_modal_changelog_link": "Novedades",
"group_create_contacts_modal_title": "Nuevo grupo",
"group_create_modal_title": "Crear grupo",
"group_create_name": "Nombre del grupo",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Borrador:",
"conversation_media_photo": "Foto",
"conversation_media_video": "Vídeo",
"conversation_media_round": "Videomensaje",
"conversation_media_document": "Archivo",
"conversation_media_sticker": "Sticker",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Ubicación",
"conversation_media_contact": "Contacto",
"conversation_media_attachment": "Archivo adjunto",
"conversation_media_unsupported": "Adjunto no soportado",
"conversation_search_peer": "Buscar en el chat",
"conversation_group_created": "creó el grupo",
"conversation_group_renamed": "cambió el nombre del grupo",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Foto del canal actualizada",
"message_service_removed_channel_photo": "Foto del canal eliminada",
"message_service_scored_X": "{'one': 'consiguió {} punto', 'other': 'consiguió {} puntos'}",
"message_service_payment_sent": "Pago enviado",
"message_action_reply": "Responder",
"message_action_edit": "Editar",
"message_action_delete": "Eliminar",
@ -377,6 +384,7 @@ @@ -377,6 +384,7 @@
"error_modal_media_not_supported_description": "Tu navegador no puede reproducir este archivo multimedia. Prueba descargándolo y abriéndolo en un reproductor independiente.",
"error_modal_username_not_found_description": "No hay una cuenta de Telegram con el alias que entregaste.",
"error_modal_phonecalls_not_supported_description_md": "Las llamadas no están soportadas en la versión Web, por el momento.\n\nPuedes llamar a {user} con una app móvil o de escritorio.\n{download-link: Download »}",
"error_modal_app_signup_forbidden_md": "Aún no tienes una cuenta de Telegram. Por favor, **regístrate** con {signup-link: Android / iPhone} primero.",
"error_modal_bad_request_description": "Falta uno de los parámetros o es inválido.",
"error_modal_unauthorized_description": "Esta acción requiere acceso autorizado. Por favor, {login-link: inicia sesión}.",
"error_modal_forbidden_description": "No estás autorizado para esta acción.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Vídeos",
"head_media_documents": "Archivos",
"head_media_audio": "Mensajes de voz",
"head_media_round": "Videomensajes",
"head_media_search": "Buscar",
"head_about": "Acerca de",
"head_clear_all": "Eliminar historial",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Enviar",
"im_submit_edit_message": "Guardar",
"im_edit_message_title": "Editar mensaje",
"im_voice_recording_label": "Suelta fuera de aquí para cancelar",
"im_voice_recording_cancel_label": "Suelta para cancelar la grabación",
"im_voice_processing_label": "Procesando{dots}",
"login_sign_in": "Registrarse",
"login_enter_number_description": "Por favor, escoge tu país y pon tu número de teléfono completo.",
"login_incorrect_number": "Número de teléfono incorrecto",

12
app/js/locales/it-it.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Elimina ed esci",
"group_modal_menu_clear_history": "Cancella cronologia",
"group_modal_delete_group": "Elimina gruppo",
"group_modal_join": "Unisciti al gruppo",
"group_modal_settings": "Impostazioni",
"group_modal_notifications": "Notifiche",
"group_modal_menu_share_link": "Invita nel gruppo tramite link",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Descrizione",
"channel_modal_share_link": "Condividi link",
"channel_modal_share_loading": "Carico{dots}",
"channel_modal_menu_edit": "Modifica canale",
"channel_modal_join": "Unisciti al canale",
"channel_modal_add_member": "Invita membri",
"channel_modal_leave_channel": "Lascia il canale",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Aggiornamenti recenti di **Telegram Web**",
"changelog_modal_header_new_updates_md": "**Telegram Web** è stato aggiornato!",
"changelog_modal_title_current_version": "versione corrente",
"changelog_modal_full_description_md": "App ufficiale basata sulle API di Telegram per velocità e sicurezza.\n\nQuesto software è sotto licenza GNU GPL versione 3.",
"changelog_modal_changelog_link": "Novità",
"group_create_contacts_modal_title": "Nuovo gruppo",
"group_create_modal_title": "Crea gruppo",
"group_create_name": "Nome del gruppo",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Bozza:",
"conversation_media_photo": "Foto",
"conversation_media_video": "Video",
"conversation_media_round": "Videomessaggio",
"conversation_media_document": "File",
"conversation_media_sticker": "Sticker",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Posizione",
"conversation_media_contact": "Contatto",
"conversation_media_attachment": "Allegato",
"conversation_media_unsupported": "Allegato non supportato",
"conversation_search_peer": "Cerca in questa chat",
"conversation_group_created": "ha creato il gruppo",
"conversation_group_renamed": "ha cambiato il nome del gruppo",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Foto del canale aggiornata",
"message_service_removed_channel_photo": "Foto del canale rimossa",
"message_service_scored_X": "{'one': 'ha totalizzato {} punto', 'other': 'ha totalizzato {} punti'}",
"message_service_payment_sent": "Pagamento inviato",
"message_action_reply": "Rispondi",
"message_action_edit": "Modifica",
"message_action_delete": "Elimina",
@ -377,6 +384,7 @@ @@ -377,6 +384,7 @@
"error_modal_media_not_supported_description": "Il tuo browser non può riprodurre questo file. Prova a scaricarlo e ad aprirlo in un player esterno.",
"error_modal_username_not_found_description": "Non esiste alcun account Telegram con l'username che hai fornito.",
"error_modal_phonecalls_not_supported_description_md": "Sfortunatamente le chiamate non sono ancora supportate nell'app Web al momento.\n\nPuoi chiamare {user} usando le nostre app mobile o le app native per computer.\n{download-link: Scarica »}",
"error_modal_app_signup_forbidden_md": "Se non hai ancora un account Telegram, per favore **iscriviti** prima da {signup-link: Android / iPhone}.",
"error_modal_bad_request_description": "Un parametro è mancante o errato.",
"error_modal_unauthorized_description": "L'azione necessita dell'autorizzazione d'accesso. Per favore {login-link: accedi}.",
"error_modal_forbidden_description": "Non sei autorizzato a questa operazione.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Video",
"head_media_documents": "File",
"head_media_audio": "Messaggi vocali",
"head_media_round": "Videomessaggi",
"head_media_search": "Cerca",
"head_about": "Info",
"head_clear_all": "Cancella cronologia",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Invia",
"im_submit_edit_message": "Salva",
"im_edit_message_title": "Modifica messaggio",
"im_voice_recording_label": "Rilascia fuori da qui per annullare",
"im_voice_recording_cancel_label": "Rilascia per annullare la registrazione",
"im_voice_processing_label": "Elaboro{dots}",
"login_sign_in": "Accedi",
"login_enter_number_description": "Inserisci il tuo numero di telefono completo.",
"login_incorrect_number": "Numero di telefono errato",

14
app/js/locales/nl-nl.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Verwijderen en verlaten",
"group_modal_menu_clear_history": "Geschiedenis wissen",
"group_modal_delete_group": "Groep verwijderen",
"group_modal_join": "Join group",
"group_modal_settings": "Instellingen",
"group_modal_notifications": "Meldingen",
"group_modal_menu_share_link": "Uitnodigingslink sturen",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Beschrijving",
"channel_modal_share_link": "Link delen",
"channel_modal_share_loading": "Laden{dots}",
"channel_modal_menu_edit": "Kanaal wijzigen",
"channel_modal_join": "Lid worden van kanaal",
"channel_modal_add_member": "Leden toevoegen",
"channel_modal_leave_channel": "Kanaal verlaten",
@ -61,7 +63,7 @@ @@ -61,7 +63,7 @@
"settings_modal_language": "Taal",
"settings_modal_notifications": "Desktopmeldingen",
"settings_modal_pushes": "Achtergrondmeldingen",
"settings_modal_message_preview": "Voorvertoning",
"settings_modal_message_preview": "Voorbeeld",
"settings_modal_sound": "Geluid",
"settings_modal_enter_send_description_md": "**Enter** - bericht versturen, **Shift + Enter** - nieuwe regel",
"settings_modal_ctrl_enter_send_description_md": "**Ctrl + Enter** - bericht versturen. **Enter** - nieuwe regel",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Recente updates in **Telegram Web**",
"changelog_modal_header_new_updates_md": "**Telegram Web** is bijgewerkt!",
"changelog_modal_title_current_version": "huidige versie",
"changelog_modal_full_description_md": "Officiële, gratis Telegram app gebaseerd op de Telegram API voor snelheid en veiligheid.\n\nDeze software valt onder de GNU GPL versie 3 licentie.",
"changelog_modal_changelog_link": "Wijzigingen",
"group_create_contacts_modal_title": "Nieuwe groep",
"group_create_modal_title": "Groep maken",
"group_create_name": "Groepsnaam",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Concept:",
"conversation_media_photo": "Foto",
"conversation_media_video": "Video",
"conversation_media_round": "Videobericht",
"conversation_media_document": "Bestand",
"conversation_media_sticker": "sticker",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Locatie",
"conversation_media_contact": "Contact",
"conversation_media_attachment": "Bijlage",
"conversation_media_unsupported": "Bestandstype niet ondersteund",
"conversation_search_peer": "In chat zoeken",
"conversation_group_created": "heeft de groep gemaakt",
"conversation_group_renamed": "heeft de groepsnaam gewijzigd",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Kanaalfoto bijgewerkt",
"message_service_removed_channel_photo": "Kanaalfoto verwijderd",
"message_service_scored_X": "{'one': '{} punt', 'other': '{} punten'}",
"message_service_payment_sent": "Betaling verzonden",
"message_action_reply": "Antwoord",
"message_action_edit": "Wijzig",
"message_action_delete": "Verwijder",
@ -377,6 +384,7 @@ @@ -377,6 +384,7 @@
"error_modal_media_not_supported_description": "Je browser kan dit mediabestand niet afspelen. Probeer het bestand te downloaden en af te spelen in een vrijstaande afspeelapplicatie.",
"error_modal_username_not_found_description": "Geen Telegram-account gevonden voor de opgegeven gebruikersnaam. ",
"error_modal_phonecalls_not_supported_description_md": "Helaas zijn oproepen nog niet beschikbaar in de Web-versie.\n\nJe kunt {user} bellen via onze mobiele- of desktop-applicaties.\n{download-link: Download »}",
"error_modal_app_signup_forbidden_md": "Je hebt nog geen Telegram-account, **meld je eerst aan** via {signup-link: Android / iPhone} ",
"error_modal_bad_request_description": "Éen van de parameters mist of is ongeldig.",
"error_modal_unauthorized_description": "Deze actie vereist authenticatie. {login-link: Inloggen}",
"error_modal_forbidden_description": "Je bent niet bevoegd deze bewerking uit te voeren.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Video's",
"head_media_documents": "Bestanden",
"head_media_audio": "Spraakberichten",
"head_media_round": "Videoberichten",
"head_media_search": "Zoeken",
"head_about": "Over",
"head_clear_all": "Geschiedenis wissen",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Stuur",
"im_submit_edit_message": "Opslaan",
"im_edit_message_title": "Bericht wijzigen",
"im_voice_recording_label": "Buiten het veld loslaten om te annuleren",
"im_voice_recording_cancel_label": "Release to cancel record",
"im_voice_processing_label": "Verwerken{dots}",
"login_sign_in": "Inloggen",
"login_enter_number_description": "Kies je land en voer je volledige telefoonnummer in.",
"login_incorrect_number": "Onjuist telefoonnummer",

42
app/js/locales/pt-br.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Apagar e sair",
"group_modal_menu_clear_history": "Limpar histórico",
"group_modal_delete_group": "Apagar grupo",
"group_modal_join": "Join group",
"group_modal_settings": "Configurações",
"group_modal_notifications": "Notificações",
"group_modal_menu_share_link": "Convidar ao grupo via link",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Descrição",
"channel_modal_share_link": "Compartilhar link",
"channel_modal_share_loading": "Carregando{dots}",
"channel_modal_menu_edit": "Edit channel",
"channel_modal_join": "Entrar no canal",
"channel_modal_add_member": "Convidar membros",
"channel_modal_leave_channel": "Sair do canal",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Atualizações recentes no **Telegram Web**",
"changelog_modal_header_new_updates_md": "**Telegram Web** foi atualizado!",
"changelog_modal_title_current_version": "versão atual",
"changelog_modal_full_description_md": "Official free messaging app based on Telegram API for speed and security.\n\nThis software is licensed under GNU GPL version 3.",
"changelog_modal_changelog_link": "Changelog",
"group_create_contacts_modal_title": "Novo grupo",
"group_create_modal_title": "Criar grupo",
"group_create_name": "Nome do grupo",
@ -203,13 +207,13 @@ @@ -203,13 +207,13 @@
"confirm_modal_clipboard_file_send": "Você tem certeza que deseja enviar arquivo(s) da área de transferência?",
"confirm_modal_clipboard_X_files_send": "{'one': 'Você tem certeza que deseja enviar o arquivo da área de transferência?', 'other': 'Você tem certeza que deseja enviar {} arquivos da área de transferência?'}",
"confirm_modal_message_delete": "Você tem certeza que deseja apagar esta mensagem?",
"confirm_modal_delete_X_messages": "{'one': '{} message', 'other': '{} messages'}",
"confirm_modal_delete_messages": "Are you sure you want to delete {messages}?",
"confirm_modal_message_revoke": "Delete for {recipient}",
"confirm_modal_message_revoke_recipient_chat": "everyone",
"confirm_modal_delete_messages_for_everyone_chat": "This will delete messages for everyone in this chat.",
"confirm_modal_delete_messages_for_you_only_pm": "This will delete messages just for you, not for {user}.",
"confirm_modal_delete_messages_for_you_only_chat": "This will delete messages just for you, not for other participants of the chat.",
"confirm_modal_delete_X_messages": "{'one': '{} mensagem', 'other': '{} mensagens'}",
"confirm_modal_delete_messages": "Você tem certeza que deseja apagar {mensagens}?",
"confirm_modal_message_revoke": "Apagar para {recipient}",
"confirm_modal_message_revoke_recipient_chat": "todos",
"confirm_modal_delete_messages_for_everyone_chat": "Isso apagará para todos nessa conversa.",
"confirm_modal_delete_messages_for_you_only_pm": "Isso irá apagar somente para você, não para {user}.",
"confirm_modal_delete_messages_for_you_only_chat": "Isso apagará as mensagens somente para você, não para os outros participantes dessa conversa.",
"confirm_modal_photo_delete": "Você tem certeza que deseja apagar a foto?",
"confirm_modal_contacts_import": "Telegram sincronizará os seus contatos para encontrar seus amigos.",
"confirm_modal_login_phone_correct": "O número de telefone está correto?",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Rascunho",
"conversation_media_photo": "Foto",
"conversation_media_video": "Vídeo",
"conversation_media_round": "Video message",
"conversation_media_document": "Arquivo",
"conversation_media_sticker": "Sticker",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Localização",
"conversation_media_contact": "Contato",
"conversation_media_attachment": "Anexo",
"conversation_media_unsupported": "Unsupported attachment",
"conversation_search_peer": "Buscar neste chat",
"conversation_group_created": "criou o grupo",
"conversation_group_renamed": "alterou o nome do grupo",
@ -324,11 +330,11 @@ @@ -324,11 +330,11 @@
"message_service_joined_by_link": "entrou para o grupo via link de convite",
"message_service_joined": "entrou no grupo",
"message_service_pinned_message": "fixou «{message}»",
"message_service_phonecall_incoming": "Incoming Call",
"message_service_phonecall_outgoing": "Outgoing Call",
"message_service_phonecall_missed": "Missed Call",
"message_service_phonecall_canceled": "Cancelled Call",
"message_service_phonecall": "Phone call {duration}",
"message_service_phonecall_incoming": "Chamada Recebida",
"message_service_phonecall_outgoing": "Chamada Efetuada",
"message_service_phonecall_missed": "Chamada Perdida",
"message_service_phonecall_canceled": "Chamada Cancelada",
"message_service_phonecall": "Chamada {duration}",
"message_service_scored_game": "{scored} em {message}",
"message_service_unsupported_action": "ação sem suporte {action}",
"message_service_bot_intro_header": "O que esse bot pode fazer?",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Foto do canal atualizada",
"message_service_removed_channel_photo": "Foto do canal removida",
"message_service_scored_X": "{'one': 'marcou {} ponto', 'other': 'marcou {} pontos'}",
"message_service_payment_sent": "Payment sent",
"message_action_reply": "Responder",
"message_action_edit": "Editar",
"message_action_delete": "Apagar",
@ -359,7 +366,7 @@ @@ -359,7 +366,7 @@
"error_modal_password_success_title": "Sucesso!",
"error_modal_password_disabled_title": "Senha desativada",
"error_modal_media_not_supported_title": "Mídia não suportada",
"error_modal_phonecalls_not_supported_title": "Calls are not available yet",
"error_modal_phonecalls_not_supported_title": "As chamadas ainda não estão disponíveis.",
"error_modal_recovery_na_title": "Desculpe",
"error_modal_network_description": "Por favor, verifique sua conexão com a internet.",
"error_modal_firstname_invali_description": "O nome inserido é inválido.",
@ -376,7 +383,8 @@ @@ -376,7 +383,8 @@
"error_modal_username_occupied_description": "Desculpe, esse nome de usuário já está em uso.",
"error_modal_media_not_supported_description": "Seu navegador não pode visualizar este tipo de mídia. Tente baixar o arquivo e abri-lo em outro reprodutor. ",
"error_modal_username_not_found_description": "Não existe nenhuma conta do Telegram com o número informado.",
"error_modal_phonecalls_not_supported_description_md": "Unfortunately calls are not supported in the Web App at the moment.\n\nYou can call {user} using our mobile apps or native desktop applications.\n{download-link: Download »}",
"error_modal_phonecalls_not_supported_description_md": "Infelizmente as chamadas ainda não são suportadas pelo App Web.\n\nVocê pode ligar para {user} usando nossos apps para celular ou aplicações desktop.\n{download-link: Download »}",
"error_modal_app_signup_forbidden_md": "You don't have a Telegram account yet, please **sign up** with {signup-link: Android / iPhone} first.",
"error_modal_bad_request_description": "Um dos parâmetros está faltando ou inválido.",
"error_modal_unauthorized_description": "Essa ação requer autorização. Por favor {login-link: entre}.",
"error_modal_forbidden_description": "Você não tem permissão para esta ação.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Vídeos",
"head_media_documents": "Arquivos",
"head_media_audio": "Mensagens de voz",
"head_media_round": "Video messages",
"head_media_search": "Pesquisar",
"head_about": "Sobre",
"head_clear_all": "Limpar histórico",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Enviar",
"im_submit_edit_message": "Salvar",
"im_edit_message_title": "Editar mensagem",
"im_voice_recording_label": "Release outside this form to cancel",
"im_voice_recording_cancel_label": "Release to cancel record",
"im_voice_processing_label": "Processing{dots}",
"login_sign_in": "Entrar",
"login_enter_number_description": "Escolha seu país e insira seu número de telefone com DDD.",
"login_incorrect_number": "Número de telefone incorreto",
@ -505,7 +517,7 @@ @@ -505,7 +517,7 @@
"login_signing_up": "Inscrevendo-se",
"login_sign_up": "Inscrever-se",
"login_about_title": "Sobre",
"login_about_hide": "hide",
"login_about_hide": "esconder",
"login_about_desc1_md": "O cliente web do Telegram é grátis, rápido e seguro para usufruir de muitas das funcionalidades do **Telegram** diretamente do seu **navegador**.",
"login_about_desc2_md": "Está sempre sincronizado com o **aplicativo do Telegram** em seu dispositivo celular, o que o torna uma ferramenta perfeita para mensagens e compartilhamento de arquivos.",
"login_about_desc3_md": "Nosso {source-link: source code} é aberto, então todos podem contribuir.",

12
app/js/locales/ru-ru.json

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
"group_modal_menu_delete_group": "Удалить и выйти",
"group_modal_menu_clear_history": "Очистить историю",
"group_modal_delete_group": "Удалить группу",
"group_modal_join": "Join group",
"group_modal_settings": "Настройки",
"group_modal_notifications": "Уведомления",
"group_modal_menu_share_link": "Пригласить в группу по ссылке",
@ -38,6 +39,7 @@ @@ -38,6 +39,7 @@
"channel_modal_description": "Описание",
"channel_modal_share_link": "Поделиться ссылкой",
"channel_modal_share_loading": "Загрузка{dots}",
"channel_modal_menu_edit": "Edit channel",
"channel_modal_join": "Подписаться на канал",
"channel_modal_add_member": "Пригласить участников",
"channel_modal_leave_channel": "Покинуть канал",
@ -169,6 +171,8 @@ @@ -169,6 +171,8 @@
"changelog_modal_header_recent_updates_md": "Последние обновления в **веб-клиенте Telegram**",
"changelog_modal_header_new_updates_md": "**Веб-клиент Telegram** обновился!",
"changelog_modal_title_current_version": "текущая версия",
"changelog_modal_full_description_md": "Official free messaging app based on Telegram API for speed and security.\n\nThis software is licensed under GNU GPL version 3.",
"changelog_modal_changelog_link": "Changelog",
"group_create_contacts_modal_title": "Новая группа",
"group_create_modal_title": "Создать группу",
"group_create_name": "Название группы",
@ -280,6 +284,7 @@ @@ -280,6 +284,7 @@
"conversation_draft": "Draft:",
"conversation_media_photo": "Фотография",
"conversation_media_video": "Видео",
"conversation_media_round": "Video message",
"conversation_media_document": "Файл",
"conversation_media_sticker": "Стикер",
"conversation_media_gif": "GIF",
@ -287,6 +292,7 @@ @@ -287,6 +292,7 @@
"conversation_media_location": "Местоположение",
"conversation_media_contact": "Контакт",
"conversation_media_attachment": "Прикрепление",
"conversation_media_unsupported": "Unsupported attachment",
"conversation_search_peer": "Search in this chat",
"conversation_group_created": "создал(а) группу",
"conversation_group_renamed": "изменил(а) название группы",
@ -338,6 +344,7 @@ @@ -338,6 +344,7 @@
"message_service_changed_channel_photo": "Фото канала изменено",
"message_service_removed_channel_photo": "Фото канала удалено",
"message_service_scored_X": "{'one': 'scored {}', 'other': 'scored {}'}",
"message_service_payment_sent": "Payment sent",
"message_action_reply": "Ответить",
"message_action_edit": "Edit",
"message_action_delete": "Удалить",
@ -377,6 +384,7 @@ @@ -377,6 +384,7 @@
"error_modal_media_not_supported_description": "Ваш браузер не может воспроизвести этот медиафайл. Попробуйте загрузить этот файл и открыть в стороннем плеере.",
"error_modal_username_not_found_description": "Нет аккаунта Telegram с указанным вами именем пользователя.",
"error_modal_phonecalls_not_supported_description_md": "Unfortunately calls are not supported in the Web App at the moment.\n\nYou can call {user} using our mobile apps or native desktop applications.\n{download-link: Download »}",
"error_modal_app_signup_forbidden_md": "You don't have a Telegram account yet, please **sign up** with {signup-link: Android / iPhone} first.",
"error_modal_bad_request_description": "Один из параметров отсутствует или некорректен.",
"error_modal_unauthorized_description": "Для этого действия необходима авторизация. Пожалуйста, {login-link: войдите}.",
"error_modal_forbidden_description": "Вам запрещено это действие.",
@ -408,6 +416,7 @@ @@ -408,6 +416,7 @@
"head_media_video": "Видео",
"head_media_documents": "Файлы",
"head_media_audio": "Голосовые сообщения",
"head_media_round": "Video messages",
"head_media_search": "Search",
"head_about": "О приложении",
"head_clear_all": "Clear history",
@ -476,6 +485,9 @@ @@ -476,6 +485,9 @@
"im_submit_message": "Отправить",
"im_submit_edit_message": "Save",
"im_edit_message_title": "Edit message",
"im_voice_recording_label": "Release outside this form to cancel",
"im_voice_recording_cancel_label": "Release to cancel record",
"im_voice_processing_label": "Processing{dots}",
"login_sign_in": "Войти",
"login_enter_number_description": "Выберите вашу страну из списка и введите номер телефона:",
"login_incorrect_number": "Некорректный номер",

4
app/js/message_composer.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -1610,7 +1610,7 @@ MessageComposer.prototype.resetTyping = function () { @@ -1610,7 +1610,7 @@ MessageComposer.prototype.resetTyping = function () {
}
MessageComposer.prototype.setPlaceholder = function (newPlaceholder) {
(this.richTextareaEl || this.textareaEl).attr('placeholder', newPlaceholder)
;(this.richTextareaEl || this.textareaEl).attr('placeholder', newPlaceholder)
}
function Scroller (content, options) {

180
app/js/messages_manager.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -18,6 +18,7 @@ angular.module('myApp.services') @@ -18,6 +18,7 @@ angular.module('myApp.services')
var pendingByRandomID = {}
var pendingByMessageID = {}
var pendingAfterMsgs = {}
var pendingTopMsgs = {}
var sendFilePromise = $q.when()
var tempID = -1
@ -135,8 +136,25 @@ angular.module('myApp.services') @@ -135,8 +136,25 @@ angular.module('myApp.services')
SearchIndexManager.indexObject(peerID, peerText, dialogsIndex)
var isMegagroup = AppPeersManager.isMegagroup(channelID)
var mid = AppMessagesIDsManager.getFullMessageID(dialog.top_message, channelID)
var message = getMessage(mid)
if (dialog.top_message) {
var mid = AppMessagesIDsManager.getFullMessageID(dialog.top_message, channelID)
var message = getMessage(mid)
} else {
var mid = tempID--
var message = {
_: 'message',
id: mid,
mid: mid,
from_id: AppUsersManager.getSelf().id,
to_id: AppPeersManager.getOutputPeer(peerID),
deleted: true,
flags: 0,
pFlags: {unread: false, out: true},
date: 0,
message: ''
}
saveMessages([message])
}
var offsetDate = message.date
if (!channelID && peerID < 0) {
@ -181,7 +199,11 @@ angular.module('myApp.services') @@ -181,7 +199,11 @@ angular.module('myApp.services')
if (historiesStorage[peerID] === undefined &&
!message.deleted) {
var historyStorage = {count: null, history: [mid], pending: []}
var historyStorage = {count: null, history: [], pending: []}
historyStorage[mid > 0 ? 'history' : 'pending'].push(mid)
if (mid < 0 && message.pFlags.unread) {
dialog.unread_count++
}
historiesStorage[peerID] = historyStorage
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID})
@ -526,6 +548,9 @@ angular.module('myApp.services') @@ -526,6 +548,9 @@ angular.module('myApp.services')
historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}
}
if (maxID < 0) {
maxID = 0
}
var isMigrated = false
var reqPeerID = peerID
if (migratedToFrom[peerID]) {
@ -778,6 +803,11 @@ angular.module('myApp.services') @@ -778,6 +803,11 @@ angular.module('myApp.services')
neededDocType = 'voice'
break
case 'inputMessagesFilterRoundVideo':
neededContents['messageMediaDocument'] = true
neededDocType = 'round'
break
default:
return $q.when({
count: 0,
@ -903,7 +933,11 @@ angular.module('myApp.services') @@ -903,7 +933,11 @@ angular.module('myApp.services')
}
function getMessage (messageID) {
return messagesStorage[messageID] || {deleted: true}
return messagesStorage[messageID] || {
_: 'messageEmpty',
deleted: true,
pFlags: {out: false, unread: false}
}
}
function canMessageBeEdited(message) {
@ -937,16 +971,16 @@ angular.module('myApp.services') @@ -937,16 +971,16 @@ angular.module('myApp.services')
}
var message = messagesStorage[messageID]
if (!message ||
!message.canBeEdited ||
message.date < tsNow(true) - 2 * 86400) {
!message.canBeEdited) {
return false
}
var peerID = getMessagePeer(message)
if (!message.pFlags.out &&
message.peerID != AppUsersManager.getSelf().id) {
if (getMessagePeer(message) == AppUsersManager.getSelf().id) {
return true
}
if (message.date < tsNow(true) - 2 * 86400 ||
!message.pFlags.out) {
return false
}
return true
}
@ -1323,6 +1357,9 @@ angular.module('myApp.services') @@ -1323,6 +1357,9 @@ angular.module('myApp.services')
AppGamesManager.saveGame(apiMessage.media.game, apiMessage.mid, mediaContext)
apiMessage.media.handleMessage = true
break
case 'messageMediaInvoice':
apiMessage.media = {_: 'messageMediaUnsupported'}
break
}
}
if (apiMessage.action) {
@ -2733,6 +2770,10 @@ angular.module('myApp.services') @@ -2733,6 +2770,10 @@ angular.module('myApp.services')
notificationMessage = _('conversation_media_video_raw')
captionEmoji = '📹'
break
case 'round':
notificationMessage = _('conversation_media_round_raw')
captionEmoji = '📹'
break
case 'voice':
case 'audio':
notificationMessage = _('conversation_media_audio_raw')
@ -2759,6 +2800,9 @@ angular.module('myApp.services') @@ -2759,6 +2800,9 @@ angular.module('myApp.services')
case 'messageMediaGame':
notificationMessage = RichTextProcessor.wrapPlainText('🎮 ' + message.media.game.title)
break
case 'messageMediaUnsupported':
notificationMessage = _('conversation_media_unsupported_raw')
break
default:
notificationMessage = _('conversation_media_attachment_raw')
break
@ -2900,6 +2944,7 @@ angular.module('myApp.services') @@ -2900,6 +2944,7 @@ angular.module('myApp.services')
var newDialogsToHandle = {}
var notificationsHandlePromise = false
var notificationsToHandle = {}
var newUpdatesAfterReloadToHandle = {}
function handleNewMessages () {
$timeout.cancel(newMessagesHandlePromise)
@ -2955,10 +3000,7 @@ angular.module('myApp.services') @@ -2955,10 +3000,7 @@ angular.module('myApp.services')
notificationsToHandle = {}
}
$rootScope.$on('apiUpdate', function (e, update) {
// if (update._ != 'updateUserStatus') {
// console.log('on apiUpdate', update)
// }
function handleUpdate(update) {
switch (update._) {
case 'updateMessageID':
var randomID = update.random_id
@ -2982,6 +3024,10 @@ angular.module('myApp.services') @@ -2982,6 +3024,10 @@ angular.module('myApp.services')
if (!newDialogsHandlePromise) {
newDialogsHandlePromise = $timeout(handleNewDialogs, 0)
}
if (newUpdatesAfterReloadToHandle[peerID] === undefined) {
newUpdatesAfterReloadToHandle[peerID] = []
}
newUpdatesAfterReloadToHandle[peerID].push(update)
break
}
@ -2995,29 +3041,30 @@ angular.module('myApp.services') @@ -2995,29 +3041,30 @@ angular.module('myApp.services')
saveMessages([message], {isNew: true})
// console.warn(dT(), 'message unread', message.mid, message.pFlags.unread)
if (historyStorage !== undefined) {
var history = historyStorage.history
if (history.indexOf(message.mid) != -1) {
return false
}
var topMsgID = history[0]
history.unshift(message.mid)
if (message.mid > 0 && message.mid < topMsgID) {
history.sort(function (a, b) {
return b - a
})
}
if (historyStorage.count !== null) {
historyStorage.count++
}
} else {
historyStorage = historiesStorage[peerID] = {
if (historyStorage === undefined) {
historyStorage = historiesStorage[peerID] = {
count: null,
history: [message.mid],
history: [],
pending: []
}
}
var history = message.mid > 0 ? historyStorage.history : historyStorage.pending
if (history.indexOf(message.mid) != -1) {
return false
}
var topMsgID = history[0]
history.unshift(message.mid)
if (message.mid > 0 && message.mid < topMsgID) {
history.sort(function (a, b) {
return b - a
})
}
if (message.mid > 0 &&
historyStorage.count !== null) {
historyStorage.count++
}
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID})
}
@ -3454,7 +3501,57 @@ angular.module('myApp.services') @@ -3454,7 +3501,57 @@ angular.module('myApp.services')
})
}
break
case 'updateServiceNotification':
// update.inbox_date = tsNow(true)
// update.pFlags = {popup: true}
var fromID = 777000
var peerID = fromID
var messageID = tempID--
var message = {
_: 'message',
id: messageID,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer(peerID),
flags: 0,
pFlags: {unread: true},
date: (update.inbox_date || tsNow(true)) + ServerTimeManager.serverTimeOffset,
message: update.message,
media: update.media,
entities: update.entities
}
if (!AppUsersManager.hasUser(fromID)) {
AppUsersManager.saveApiUsers([{
_: 'user',
id: fromID,
pFlags: {verified: true},
access_hash: 0,
first_name: 'Telegram',
phone: '42777'
}])
}
saveMessages([message])
if (update.inbox_date) {
pendingTopMsgs[peerID] = messageID
handleUpdate({
_: 'updateNewMessage',
message: message
})
}
if (update.pFlags.popup && update.message) {
var historyMessage = wrapForHistory(messageID)
ErrorService.show({error: {code: 400, type: 'UPDATE_SERVICE_NOTIFICATION'}, historyMessage: historyMessage})
}
break
}
}
$rootScope.$on('apiUpdate', function (e, update) {
// if (update._ != 'updateUserStatus') {
// console.log('on apiUpdate', update)
// }
handleUpdate(update)
})
function reloadConversation (peerID) {
@ -3474,11 +3571,18 @@ angular.module('myApp.services') @@ -3474,11 +3571,18 @@ angular.module('myApp.services')
var hasUpdated = false
angular.forEach(dialogsResult.dialogs, function (dialog) {
var peerID = AppPeersManager.getPeerID(dialog.peer)
if (dialog.top_message) {
var topMessage = dialog.top_message
var topPendingMesage = pendingTopMsgs[peerID]
if (topPendingMesage) {
if (!topMessage || getMessage(topPendingMesage).date > getMessage(topMessage).date) {
dialog.top_message = topMessage = topPendingMesage
}
}
if (topMessage) {
var wasBefore = getDialogByPeerID(peerID).length > 0
saveConversation(dialog)
if (wasBefore) {
clearDialogCache(dialog.top_message)
clearDialogCache(topMessage)
$rootScope.$broadcast('dialog_top', dialog)
} else {
updatedDialogs[peerID] = dialog
@ -3491,6 +3595,12 @@ angular.module('myApp.services') @@ -3491,6 +3595,12 @@ angular.module('myApp.services')
$rootScope.$broadcast('dialog_drop', {peerID: peerID})
}
}
if (newUpdatesAfterReloadToHandle[peerID] !== undefined) {
angular.forEach(newUpdatesAfterReloadToHandle[peerID], function (update) {
handleUpdate(update)
})
delete newUpdatesAfterReloadToHandle[peerID]
}
})
if (hasUpdated) {
$rootScope.$broadcast('dialogs_multiupdate', updatedDialogs)

90
app/js/services.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*!
* Webogram v0.5.6 - messaging web application for MTProto
* Webogram v0.6.0 - 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
@ -1893,7 +1893,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1893,7 +1893,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
apiDoc.duration = attribute.duration
apiDoc.w = attribute.w
apiDoc.h = attribute.h
if (apiDoc.thumb) {
if (apiDoc.thumb &&
attribute.pFlags.round_message) {
apiDoc.type = 'round'
}
else if (apiDoc.thumb) {
apiDoc.type = 'video'
}
break
@ -1935,6 +1939,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1935,6 +1939,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
apiDoc.mime_type = 'video/mp4'
break
case 'video':
case 'round':
apiDoc.mime_type = 'video/mp4'
break
case 'sticker':
@ -2010,6 +2015,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2010,6 +2015,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
boxHeight = Math.min(windowH - 100, Config.Mobile ? 210 : 260)
break
case 'round':
inlineImage = true
boxWidth = Math.min(windowW - 80, 200)
boxHeight = Math.min(windowH - 100, 200)
break
default:
boxWidth = boxHeight = 100
}
@ -3084,6 +3095,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -3084,6 +3095,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
curState.pendingPtsUpdates.sort(function (a, b) {
return a.pts - b.pts
})
// console.log(dT(), 'pop update', channelID, curState.pendingPtsUpdates)
var curPts = curState.pts
var goodPts = false
@ -3376,7 +3388,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -3376,7 +3388,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
if (update._ == 'updateChannelTooLong') {
getChannelDifference(channelID)
if (!curState.lastPtsUpdateTime ||
curState.lastPtsUpdateTime < tsNow() - 10000) {
// console.trace(dT(), 'channel too long, get diff', channelID, update)
getChannelDifference(channelID)
}
return false
}
@ -3387,12 +3403,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -3387,12 +3403,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
var message = update.message
var toPeerID = AppPeersManager.getPeerID(message.to_id)
var fwdHeader = message.fwd_from || {}
if (message.from_id && !AppUsersManager.hasUser(message.from_id, message.pFlags.post) ||
fwdHeader.from_id && !AppUsersManager.hasUser(fwdHeader.from_id, !!fwdHeader.channel_id) ||
fwdHeader.channel_id && !AppChatsManager.hasChat(fwdHeader.channel_id, true) ||
toPeerID > 0 && !AppUsersManager.hasUser(toPeerID) ||
toPeerID < 0 && !AppChatsManager.hasChat(-toPeerID)) {
console.warn(dT(), 'Not enough data for message update', message)
var reason = false
if (message.from_id && !AppUsersManager.hasUser(message.from_id, message.pFlags.post/* || channelID*/) && (reason = 'author') ||
fwdHeader.from_id && !AppUsersManager.hasUser(fwdHeader.from_id, !!fwdHeader.channel_id) && (reason = 'fwdAuthor') ||
fwdHeader.channel_id && !AppChatsManager.hasChat(fwdHeader.channel_id, true) && (reason = 'fwdChannel') ||
toPeerID > 0 && !AppUsersManager.hasUser(toPeerID) && (reason = 'toPeer User') ||
toPeerID < 0 && !AppChatsManager.hasChat(-toPeerID) && (reason = 'toPeer Chat')) {
console.warn(dT(), 'Not enough data for message update', toPeerID, reason, message)
if (channelID && AppChatsManager.hasChat(channelID)) {
getChannelDifference(channelID)
} else {
@ -3431,6 +3448,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -3431,6 +3448,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (update.pts > curState.pts) {
curState.pts = update.pts
popPts = true
curState.lastPtsUpdateTime = tsNow()
}
else if (update.pts_count) {
// console.warn(dT(), 'Duplicate update', update)
@ -4330,15 +4349,40 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4330,15 +4349,40 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
})
.service('ChangelogNotifyService', function (Storage, $rootScope, $modal) {
.service('ChangelogNotifyService', function (Storage, $rootScope, $modal, $timeout, MtpApiManager, ApiUpdatesManager) {
var checked = false
function checkUpdate () {
Storage.get('last_version').then(function (lastVersion) {
if (lastVersion != Config.App.version) {
if (lastVersion) {
showChangelog(lastVersion)
}
Storage.set({last_version: Config.App.version})
if (checked) {
return
}
checked = true
MtpApiManager.getUserID().then(function (userID) {
if (!userID) {
return
}
$timeout(function () {
Storage.get('last_version').then(function (lastVersion) {
if (lastVersion != Config.App.version) {
if (!lastVersion) {
Storage.set({last_version: Config.App.version})
} else {
MtpApiManager.invokeApi('help.getAppChangelog', {
prev_app_version: lastVersion
}, {
noErrorBox: true,
}).then(function (updates) {
if (updates._ == 'updates' && !updates.updates.length) {
return false
}
ApiUpdatesManager.processUpdateMessage(updates)
Storage.set({last_version: Config.App.version})
})
}
}
})
}, 5000)
})
}
@ -4710,6 +4754,19 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4710,6 +4754,19 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
})
$(document).on('mousedown', function (event) {
var target = event.target
if (target &&
target.tagName == 'A') {
var href = $(target).attr('href') || target.href || ''
if (Config.Modes.chrome_packed &&
href.length &&
$(target).attr('target') == '_blank') {
$(target).attr('rel', '')
}
}
})
$rootScope.$on('$routeUpdate', checkLocationTgAddr)
checkLocationTgAddr()
}
@ -4835,7 +4892,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4835,7 +4892,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
// console.warn(dT(), 'server', draft)
} else {
// console.warn(dT(), 'local', draft)
console.warn(dT(), 'local', draft)
}
var replyToMsgID = draft && draft.replyToMsgID
if (replyToMsgID) {

162
app/less/app.less

@ -2042,6 +2042,15 @@ img.im_message_document_thumb { @@ -2042,6 +2042,15 @@ img.im_message_document_thumb {
}
}
.audio_player_media {
position: absolute;
visibility: hidden;
canvas {
display: none;
}
}
.im_message_upload_progress_wrap,
.im_message_download_progress_wrap {
margin-top: 5px;
@ -2542,6 +2551,21 @@ a.im_message_fwd_photo { @@ -2542,6 +2551,21 @@ a.im_message_fwd_photo {
color: #999;
position: absolute;
}
.im_send_form_dragging {
.im_send_dropbox_wrap {
display: block;
}
.composer_rich_textarea,
.im_message_field,
.composer_emoji_insert_btn,
.composer_progress_icon_wrap,
.composer_command_btn,
.composer_keyboard_btn,
.im_inline_placeholder_wrap {
visibility: hidden;
}
}
.im_send_field_wrap {
position: relative;
}
@ -3496,6 +3520,69 @@ li.inline_result_sticker.composer_autocomplete_option_active a { @@ -3496,6 +3520,69 @@ li.inline_result_sticker.composer_autocomplete_option_active a {
}
.im_voice_recorder_wrap {
display: none;
z-index: 100;
}
.im_recorder_indicator, .im_recorder_time {
float: left;
vertical-align: middle;
color: #333;
}
.im_recorder_indicator i {
background-color: #ff1010;
height: 10px;
width: 10px;
border-radius: 50%;
margin-right: 5px;
vertical-align: baseline;
display: inline-block;
animation: blinker 0.5s cubic-bezier(.5, 0, 1, 1) infinite alternate;
}
@keyframes blinker {
from { opacity: 1; }
to { opacity: 0; }
}
.im_recorder_label {
overflow: auto;
font-size: 12px;
text-align: center;
vertical-align: middle;
color: #3a6d99;
transition: color linear 0.2s;
i {
margin-right: 5px;
}
.im_send_form_hover & {
color: #CCC;
}
}
.im_send_form_hover .im_recorder_label_hout,
.im_recorder_label_hover {
display: none;
}
.im_send_form_hover .im_recorder_label_hover {
display: inline;
}
.im_voice_recording,
.im_processing_recording {
color: #AAA;
.im_voice_recorder_wrap {
display: block;
}
}
.error_modal_window {
.modal-dialog {
max-width: 350px;
@ -4113,6 +4200,81 @@ h5 { @@ -4113,6 +4200,81 @@ h5 {
opacity: 1;
}
/* Round documents */
.img_round_image_wrap {
position: relative;
overflow: hidden;
border-radius: 50%;
overflow: hidden;
}
.img_round_meta {
background: rgba(0,0,0,0.4);
width: 40px;
height: 40px;
line-height: 0;
position: absolute;
z-index: 2;
border-radius: 50%;
overflow: hidden;
margin: 0 auto;
top: 50%;
left: 50%;
margin-left: -20px;
margin-top: -20px;
pointer-events: none;
}
.icon-cancel {
position: absolute;
top: 50%;
left: 50%;
margin-left: -9px;
margin-top: -1px;
.icon-bar {
display: block;
width: 18px;
height: 2px;
background: #FFF;
transform-origin: 50% 50%;
&:first-child {
.transform(rotate(-45deg));
}
&:last-child {
.transform(translate3d(0,-2px,0) rotate(45deg));
}
}
}
.img_round_thumb {
-webkit-filter: blur(2px);
-moz-filter: blur(2px);
-o-filter: blur(2px);
-ms-filter: blur(2px);
filter: blur(2px);
filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='3');
transform-origin: center center;
-webkit-transform-origin: center center;
-webkit-transform: scale(1.02, 1.02);
transform: scale(1.02, 1.02);
max-width: 100%;
height: auto;
}
.img_round_meta_contents {
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s;
}
.img_round_meta_contents.ng-leave.ng-leave-active,
.img_round_meta_contents.ng-enter {
opacity: 0;
}
.img_round_meta_contents.ng-leave,
.img_round_meta_contents.ng-enter.ng-enter-active {
opacity: 1;
}
.countries_modal_window {
.modal-dialog {
max-width: 392px;

90
app/less/desktop.less

@ -1290,6 +1290,75 @@ a.im_panel_peer_photo .peer_initials { @@ -1290,6 +1290,75 @@ a.im_panel_peer_photo .peer_initials {
}
}
.im_record {
display: none;
width: 50px;
height: 50px;
margin: -10px 0 0 -18px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
padding: 13px 16px 13px 16px;
border-radius: 50px;
overflow: hidden;
background: #fff;
transition: background-color linear 0.2s;
.im_record_supported & {
display: block;
}
}
.im_send_form_hover .im_voice_recording .im_record {
background: #cae9ff;
}
.icon-mic {
display: inline-block;
width: 18px;
height: 22px;
vertical-align: text-top;
opacity: 0.8;
.image-2x('../img/icons/IconsetW.png', 42px, 1171px);
background-position: -12px -285px;
background-color: transparent;
.im_record:hover & {
opacity: 1;
}
.im_record:active &,
.im_voice_recording & {
background-position: -12px -705px;
}
}
.im_voice_recorder_wrap {
padding: 17px 10px 0;
display: none;
position: absolute;
}
.im_voice_recording,
.im_processing_recording {
.im_voice_recorder_wrap {
display: block;
}
.composer_rich_textarea,
.im_message_field,
.composer_emoji_insert_btn,
.composer_progress_icon_wrap,
.composer_command_btn,
.composer_keyboard_btn,
.im_inline_placeholder_wrap {
visibility: hidden;
}
}
@media (max-height: 600px) {
a {
&.im_panel_peer_photo,
@ -1340,6 +1409,10 @@ a.im_panel_peer_photo .peer_initials { @@ -1340,6 +1409,10 @@ a.im_panel_peer_photo .peer_initials {
top: 0;
left: 100%;
margin: 0 0 0 15px;
.im_record_supported .im_send_form_empty & {
display: none;
}
}
.im_media_attach {
position: absolute;
@ -1412,6 +1485,23 @@ a.im_panel_peer_photo .peer_initials { @@ -1412,6 +1485,23 @@ a.im_panel_peer_photo .peer_initials {
padding-top: 5px;
padding-bottom: 5px;
}
.im_record {
display: none;
position: absolute;
top: -4px;
right: -56px;
margin-top: -8px;
.im_record_supported .im_send_form_empty & {
display: block;
}
}
.im_voice_recorder_wrap {
padding-top: 4px;
}
}
.im_edit_panel {

125
app/less/mobile.less

@ -574,6 +574,16 @@ html { @@ -574,6 +574,16 @@ html {
.audio_player_button {
margin-right: 8px;
}
.audio_player_volume_slider .tg_slider_wrap {
display: none;
}
.audio_player_seek_slider {
width: 100%;
}
.audio_player_seek_slider .tg_slider_track {
background: rgba(200, 200, 200, 0.6);
}
.im_message_body_media {
.im_message_document,
@ -1329,6 +1339,11 @@ a.im_message_fwd_author { @@ -1329,6 +1339,11 @@ a.im_message_fwd_author {
&_modal_section_value {
font-size: 15px;
padding: 0 12px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
}
&_modal_section_body {
@ -1416,9 +1431,9 @@ a.im_message_fwd_author { @@ -1416,9 +1431,9 @@ a.im_message_fwd_author {
}
}
.icon-paperclip {
.icon-paperclip, .icon-mic {
display: inline-block;
width: 19px;
width: 18px;
height: 23px;
vertical-align: text-top;
opacity: 0.8;
@ -1427,17 +1442,25 @@ a.im_message_fwd_author { @@ -1427,17 +1442,25 @@ a.im_message_fwd_author {
background-position: -12px -68px;
}
.icon-mic {
background-position: -12px -285px;
}
.im_voice_recording .icon-mic {
background-position: -12px -705px;
}
.im_attach {
cursor: pointer;
display: none;
display: block;
overflow: hidden;
position: absolute;
right: 0;
left: 0;
top: 0;
margin: 0;
width: 50px;
height: 32px;
padding: 3px 13px 4px 16px;
right: auto;
&:active {
.icon-paperclip {
@ -1447,14 +1470,74 @@ a.im_message_fwd_author { @@ -1447,14 +1470,74 @@ a.im_message_fwd_author {
}
}
.im_send_form_empty {
.im_submit {
display: none;
.im_record {
display: none;
right: 0;
top: -8px;
width: 50px;
height: 50px;
position: absolute;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
padding: 13px 16px 13px 16px;
border-radius: 50px;
overflow: hidden;
background: #fff;
transition: background-color linear 0.2s;
.im_record_supported .im_send_form_empty & {
display: block;
}
}
.im_send_form_hover .im_voice_recording .im_record {
background: #cae9ff;
}
.im_attach {
.im_send_form_empty .im_submit {
opacity: 0.4;
}
.im_record_supported .im_send_form_empty .im_submit {
display: none;
}
.im_voice_recorder_wrap {
height: 32px;
line-height: 32px;
right: 50px;
left: 0;
padding: 0 0 0 20px;
}
.im_recorder_label {
padding-right: 48px;
}
.im_voice_recording,
.im_processing_recording {
color: #AAA;
.im_voice_recorder_wrap {
display: block;
}
.im_send_field_wrap,
.im_submit,
.im_attach {
display: none;
// visibility: hidden;
}
}
.im_processing_recording {
.im_recorder_indicator i {
background-color: green;
}
.im_record {
display: none;
}
}
.icon-emoji {
@ -1470,17 +1553,12 @@ a.im_message_fwd_author { @@ -1470,17 +1553,12 @@ a.im_message_fwd_author {
}
.composer_emoji_insert_btn {
position: absolute;
left: 0;
top: 0;
margin: 0;
padding: 3px 13px 4px 13px;
width: 48px;
height: 32px;
top: 3px;
right: 5px;
&.on,
&.composer_emoji_insert_btn_on,
&:active,
.is_1x &.on,
.is_1x &.composer_emoji_insert_btn_on,
.is_1x &:active {
.icon-emoji {
background-position: -10px -803px;
@ -1524,8 +1602,8 @@ a.im_message_fwd_author { @@ -1524,8 +1602,8 @@ a.im_message_fwd_author {
}
.composer_emoji_tooltip {
margin-left: 6px;
margin-top: -176px;
margin-left: -246px;
margin-top: -181px;
z-index: 10000;
}
.composer_emoji_tooltip_tab {
@ -1844,6 +1922,8 @@ a.media_modal_date:hover { @@ -1844,6 +1922,8 @@ a.media_modal_date:hover {
}
.composer_rich_textarea,
.composer_textarea {
padding-right: 28px;
.im_send_field_wrap_2ndbtn & {
padding-right: 35px;
}
@ -1866,18 +1946,17 @@ a.media_modal_date:hover { @@ -1866,18 +1946,17 @@ a.media_modal_date:hover {
position: relative;
}
.composer_command_btn {
right: 10px;
right: 35px;
top: 6px;
}
.composer_keyboard_btn {
right: 10px;
right: 35px;
top: 6px;
}
.im_send_keyboard_wrap {
padding: 0 5px;
}
.composer_progress_icon_wrap {
right: 6px;
top: 4px;
}
}

2
app/manifest.json

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
{
"name": "Telegram",
"description": "Telegram Web App.\nMore info & source code here: https://github.com/zhukov/webogram",
"version": "0.5.6",
"version": "0.6.0",
"short_name": "Telegram",
"manifest_version": 2,
"app": {

5
app/manifest.webapp

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
{
"name": "Telegram",
"description": "Telegram Web App.\nMore info & source code here: https://github.com/zhukov/webogram",
"version": "0.5.6",
"version": "0.6.0",
"type": "privileged",
"launch_path": "/index.html",
"developer": {
@ -44,6 +44,9 @@ @@ -44,6 +44,9 @@
"device-storage:videos": {
"description": "Required for videos download",
"access": "createonly"
},
"audio-capture" : {
"description" : "Required to record voice messages"
}
},
"activities": {

12
app/partials/desktop/audio_player.html

@ -4,8 +4,8 @@ @@ -4,8 +4,8 @@
</a>
<div class="audio_player_title_wrap">
<div class="audio_player_meta pull-right" ng-if="audio.downloaded &amp;&amp; (mediaPlayer.player.duration || audio.duration)" ng-switch="mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0">
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : duration()"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="duration() | duration"></span>
</div>
<span class="copyonly">[ </span>
<a ng-attr-title="{{audio.file_name}}" ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
</a>
<span class="copyonly">]</span>
<i ng-if="::message.pFlags.media_unread || false" ng-show="message.pFlags.media_unread" class="icon icon-audio-unread"></i>
<div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<div class="audio_player_meta" ng-if="!audio.downloaded || !duration()" ng-switch="audio.progress.enabled">
<span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>
</div>
@ -37,11 +37,9 @@ @@ -37,11 +37,9 @@
</div>
</div>
<div ng-switch-default class="im_message_playback_progress_wrap">
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="mediaPlayer.player.duration || audio.duration" slider-onchange="seek(value)"></div>
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="duration()" slider-onchange="seek(value)"></div>
<div class="audio_player_volume_slider" my-slider slider-model="mediaPlayer.player.volume" slider-min="0" slider-max="1" slider-onchange="setVolume(value)"></div>
</div>
</div>
<audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="{{audio.mime_type || 'audio/ogg'}}" volume="{{::volume}}" />
</audio>
<div class="audio_player_media" ng-if="audio.url" my-ogv-player="mediaPlayer.player" src="audio.url" volume="{{::volume}}"></div>
</div>

343
app/partials/desktop/changelog_modal.html

@ -14,7 +14,9 @@ @@ -14,7 +14,9 @@
</div>
<div class="peer_modal_profile">
<div class="peer_modal_profile_name">Telegram Web</div>
<div class="peer_modal_profile_description">Version {{currentVersion}}</div>
<div class="peer_modal_profile_description" my-i18n="changelog_app_version">
<my-i18n-param name="version" ng-bind="currentVersion"></my-i18n-param>
</div>
</div>
</div>
</div>
@ -31,341 +33,8 @@ @@ -31,341 +33,8 @@
<div class="md_modal_sections">
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.6')">
<div class="md_modal_section_version">0.5.6</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Edit the text of your messages after sending them. This works across all Telegram chats, including groups and one-on-one conversations. Select a message and click 'Edit' or just press the up arrow button to edit your last message.</li>
<li>Unsend Messages: retract any messages within 48 hours of sending them. Check out the <a href="https://telegram.org/blog/unsend-and-usage" target="_blank">Telegram Blog</a> for more info.</li>
<li>Pinned chats. Check out the <a href="https://telegram.org/blog/pin-and-ifttt" target="_blank">Telegram Blog</a> for more info.</li>
<li>Sticker suggestions by emoji.</li>
<li>Search for messages in specific chats.</li>
<li>Background notifications in Chrome and Firefox (can be disabled in Settings)</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.5')">
<div class="md_modal_section_version">0.5.5</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Introducing Drafts: Seamless syncing for unsent messages on all your devices. Drafts are now visible in your chats list.</li>
<li>Mention people in groups by typing @ and selecting them from the list — even if they don't have a username.</li>
<li>Share links to specific posts in channels via quick forwarding menu (click on the date in a message to try this out).</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.4')">
<div class="md_modal_section_version">0.5.4</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Introducing Bot API 2.0, the biggest update to our bot platform since June 2015.</li>
<li>New inline keyboards with callback, 'open URL' or 'switch to inline mode' buttons help create seamless interfaces.</li>
<li>Bots can now update existing messages on the fly as you interact with them.</li>
<li>Prepare for the rise of location-based bots: all bots can now ask users to share their location.</li>
<li>Inline bots can now send all attachments supported in Telegram (videos, music, stickers, files, etc.).</li>
<li>Try out these sample bots to see what's coming your way soon: @music, @sticker, @youtube, @foursquare</li>
<li>Check out the <a href="https://telegram.org/blog/bots-2-0" target="_blank">Telegram Blog</a> for more info.</li>
<li>New quick forwarding in channels (click on the date in a message to try this out).</li>
<li>Improved performance.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.3')">
<div class="md_modal_section_version">0.5.3</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Inline bots: A new way to add bot content to any chat. Type a bot's username and your query in the text field to get instant results and send them to your chat partner. Try typing <code>@gif dog</code> in your next chat. Sample bots: @gif, @wiki, @bing, @vid, @bold.</li>
<li>Check out the <a href="https://telegram.org/blog/inline-bots" target="_blank">Telegram Blog</a> for more info.</li>
<li>Improved GIFs: 20x faster sending and downloading, nice animated progress</li>
<li>Click on message date to reply (or to forward from channels).</li>
<li>Preview images before sending when pasting from clipboard.</li>
<li>Improved formatting for copy-pasted history fragments (date, time and sender names inserted automatically).</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.2')">
<div class="md_modal_section_version">0.5.2</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Unread counters for muted chats now colored in gray.</li>
<li>Improved previews for sticker sets: Click on a sticker to view the whole set, click on stickers in a set to send right away, added a 'Share' button.</li>
<li>Improved performance.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.1')">
<div class="md_modal_section_version">0.5.1</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Groups that have reached their capacity of 200 users can be upgraded to supergroups of up to 1,000 members.</li>
<li>Check out the <a href="https://telegram.org/blog/supergroups" target="_blank">Telegram Blog</a> for more info</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.5.0')">
<div class="md_modal_section_version">0.5.0</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Removed annoying "multiple tabs open" error.</li>
<li>Improved message forwarding.</li>
<li>Added view counter to messages from channels.</li>
<li>Improved image loading in Safari and Firefox.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.9')">
<div class="md_modal_section_version">0.4.9</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>New emoji and sticker menu, tabs for sticker packs.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.8')">
<div class="md_modal_section_version">0.4.8</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Introducing Channels – a great new way to broadcast your messages to unlimited audiences.</li>
<li>Check out the <a href="https://telegram.org/blog/channels" target="_blank">Telegram Blog</a> for more info</li>
<li>Improved performance in Safari on OS X El Capitan.</li>
<li>Added formatting for fixed-width code, surround text with `single backticks` for inline text and ```triple backticks``` for blocks of pre-formatted text.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.7')">
<div class="md_modal_section_version">0.4.7</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>New bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.
</li>
<li>Check out <a href="https://telegram.org/blog/bot-revolution" target="_blank">Telegram Blog</a> for more info</li>
<li>Improved Stickers support: now stickers are loading much faster.</li>
<li>Click on any custom stickers in chats to view and add sticker sets.</li>
<li>[Mobile] Reply to a message easily: tap on any message and select "Reply".</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.6')">
<div class="md_modal_section_version">0.4.6</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Install and share custom sticker sets like this one: <a href="tg://addstickers?set=Animals">telegram.me/addstickers/Animals</a></li>
<li>If you're an artist, create custom sticker sets using our <a href="tg://resolve?domain=stickers">@Stickers</a> bot.</li>
<li>Check out <a href="https://telegram.org/blog/stickers-revolution" target="_blank">Telegram Blog</a> for more info</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.5')">
<div class="md_modal_section_version">0.4.5</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>
Invite links for group chats:<br/>
Check out <a href="https://telegram.org/blog/invite-links" target="_blank">Telegram Blog</a> for more info
</li>
<li>Smart notifications</li>
<li>'Listened' status for voice messages</li>
<li>Places in locations (venues, landmarks)</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.4')">
<div class="md_modal_section_version">0.4.4</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Link Previews:<br/>
Get rich link summaries for tweets, YouTube videos, Instagram photos and other content.</li>
<li>Check out <a href="https://telegram.org/blog/link-preview" target="_blank">Telegram Blog</a> for more info.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.3')">
<div class="md_modal_section_version">0.4.3</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Sessions List:<br/>
View your active Telegram sessions (on desktop, tablet and mobile devices) and close specific sessions remotely.</li>
<li>Two-step verification:<br/>
Set up an additional password that will be required to log into your Telegram account.</li>
<li>Check out <a href="https://telegram.org/blog/sessions-and-2-step-verification" target="_blank">Telegram Blog</a> for more info.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.2')">
<div class="md_modal_section_version">0.4.2</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Optimization for screens with smaller Y-resolutions.</li>
<li>Supported Spotify URL embeds.</li>
<li>Mentions of the current user in group chats are now highlighted.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.1')">
<div class="md_modal_section_version">0.4.1</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Reply to specific messages in groups.</li>
<li>Mention @usernames in groups to notify multiple users.</li>
<li>Revised notifications in groups: mentioned users and people you reply to will be notified (private chat notification settings apply in this case instead of group settings). Check out <a href="https://telegram.org/blog/replies-mentions-hashtags" target="_blank">Telegram Blog</a> for more info.</li>
<li>Setting to disable message preview</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.4.0')">
<div class="md_modal_section_version">0.4.0</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Full stickers support</li>
<li>Multisearch box: instantly find chats, usernames and messages</li>
<li>Emoji autocomplete: e.g., type <strong>:kiss</strong> in the message field to see the list. <a href="http://www.emoji-cheat-sheet.com/" target="_blank">Full cheat sheet &raquo;</a></li>
<li>Added 'typing' notification in chats list</li>
<li>Online members counter in group headers</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.9')">
<div class="md_modal_section_version">0.3.9</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>[Desktop] Material design completed</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.8')">
<div class="md_modal_section_version">0.3.8</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Telegram.me links open right in Telegram Web when authorized</li>
<li>@username mentions in messages are clickable and open a conversation with the user</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.7')">
<div class="md_modal_section_version">0.3.7</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>[Desktop] New material design for modal windows</li>
<li>[Desktop] Forward messages to multiple recipients</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.6')">
<div class="md_modal_section_version">0.3.6</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>New viewer for photos, videos, documents.</li>
<li>[FirefoxOS] Improved media downloads.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.5')">
<div class="md_modal_section_version">0.3.5</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Added embedded Soundcloud tracks and playlists.</li>
<li>Added global user search to contacts list.</li>
<li>Added switch to mobile version on window resize.</li>
<li>Migrate to HTTPS notification</li>
<li>Bugfixes.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.4')">
<div class="md_modal_section_version">0.3.4</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Added embedded Facebook posts and Vimeo videos.</li>
<li>Improved IE10+ support: downloading files and style fixes.</li>
<li>Added unsupported media playback warning.</li>
<li>Bugfixes.</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.3')">
<div class="md_modal_section_version">0.3.3</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Added Native Client module: dramatically improved encryption speed; Download and upload speed is now as high as in native applications.</li>
<li>HTTPS. We recommend you to use <a href="https://web.telegram.org" target="_blank">https://web.telegram.org</a>.</li>
<li>Added multiple open tabs warning. Please note, that only one tab with Telegram Web will work.</li>
<li>Added embedded Instagram, Twitter, Vine, YouTube links.</li>
<li>Jump to selected spot when playing back audio.</li>
<li>Bugfixes</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.2')">
<div class="md_modal_section_version">0.3.2</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Usernames support: <a href="" ng-click="changeUsername()">Choose a username right now!</a></li>
<li>Search can now find public users by username.</li>
<li>Most popular emoticons shown in 'recent' when empty</li>
<li>[ChromeApp] Added saving window position and size</li>
<li>Bugfixes</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.1')">
<div class="md_modal_section_version">0.3.1</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>New languages: Spanish, German and Italian are now available </li>
<li>New custom-made audio player</li>
<li>Bad browser page for IE 6-9</li>
<li>Perfomance improvements and bugfixes</li>
</ul>
</div>
</div>
<div class="md_modal_versioned_section_wrap" ng-if="canShowVersion('0.3.0')">
<div class="md_modal_section_version">0.3.0</div>
<div class="md_modal_section_description changelog_version_changes">
<ul class="list-unstyled changelog_version_changes_list">
<li>Log in codes may be received in other Telegram apps</li>
<li>Partner's online status updates automatically</li>
<li>Added support for non-english hashtags in messages</li>
<li>Fixed invalid scrollbar width bug</li>
<li>[Desktop] Added automatic country code detection</li>
<li>[FirefoxOS] Improved PUSH-notifications for &lt;= 1.1</li>
<li>[FirefoxOS] Fixed emoji in notifications</li>
<li>[FirefoxOS] Fixed attachment bug for &lt;= 1.1</li>
<li>[FirefoxOS] Added phonebook permissions handling</li>
<li>[FirefoxOS] Added ability to share Gallery photos in Telegram</li>
</ul>
</div>
<div class="md_modal_versioned_section_wrap">
<div class="md_modal_section_description changelog_version_changes" my-i18n="changelog_modal_full_description_md"></div>
</div>
</div>
@ -373,7 +42,7 @@ @@ -373,7 +42,7 @@
<div class="changelog_footer_wrap clearfix">
<a class="changelog_github_link" href="https://github.com/zhukov/webogram" target="_blank">GitHub</a>
<a ng-show="changelogHidden" class="changelog_more_link" href="" ng-click="showAllVersions()">View Previous Updates</a>
<a class="changelog_more_link" href="https://github.com/zhukov/webogram/blob/master/CHANGELOG.md" target="_blank" my-i18n="changelog_modal_changelog_link"></a>
</div>
</div>

2
app/partials/desktop/channel_modal.html

@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
<div class="md_modal_split_actions_wrap">
<div class="md_modal_split_actions" ng-switch="hasRights('edit_photo')">
<div ng-switch-when="true" class="md_modal_split_action">
<input my-file-upload type="file" multiple="false" class="im_attach_input" size="120" multiple="false" accept="image/x-png, image/png, image/gif, image/jpeg" title="{{'group_modal_update_photo' | i18n}}" />
<input my-file-upload type="file" multiple="false" class="im_attach_input" size="120" accept="image/x-png, image/png, image/gif, image/jpeg" title="{{'group_modal_update_photo' | i18n}}" />
<i class="md_modal_split_action_camera"></i>
</div>
<a ng-switch-default class="md_modal_split_action" href="" ng-click="goToHistory()" title="{{'user_modal_send_message' | i18n}}">

14
app/partials/desktop/error_modal.html

@ -40,6 +40,10 @@ @@ -40,6 +40,10 @@
<span ng-switch-when="USERNAME_INVALID" my-i18n="error_modal_username_invalid_description"></span>
<span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span>
<span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_description"></span>
<span ng-switch-when="PHONE_NUMBER_APP_SIGNUP_FORBIDDEN" my-i18n="error_modal_app_signup_forbidden_md">
<my-i18n-param name="signup-link"><a href="https://telegram.org/dl" target="_blank">{0}</a></my-i18n-param>
</span>
<span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_username_not_found_description"></span>
<span ng-switch-when="USER_NOT_MUTUAL_CONTACT" my-i18n="error_modal_user_not_mutual_contact"></span>
<span ng-switch-when="INVITE_HASH_INVALID" my-i18n="error_modal_invite_link_invalid"></span>
@ -58,9 +62,17 @@ @@ -58,9 +62,17 @@
<my-i18n-param name="download-link"><a href="https://telegram.org/dl" target="_blank">{0}</a></my-i18n-param>
</span>
<span ng-switch-when="CALLBACK_RESPONSE" ng-bind-html="error.description_html"></span>
<span ng-switch-when="UPDATE_SERVICE_NOTIFICATION">
<div my-message-body="historyMessage">
<div class="im_message_text" dir="auto"></div>
<div class="im_message_media"></div>
</div>
</span>
<div ng-switch-default ng-switch="error.code">
<span ng-switch-when="400" my-i18n="error_modal_bad_request_description"></span>

27
app/partials/desktop/full_round.html

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
<a class="img_round_with_progress_wrap" ng-click="toggle($event)">
<div class="img_round_image_wrap" ng-style="::{width: document.thumb.width + 'px'}">
<div class="img_round_meta" ng-show="!isActive" ng-switch="document.progress.enabled">
<div ng-switch-when="true" class="img_round_meta_contents">
<i class="icon icon-cancel">
<i class="icon icon-bar"></i>
<i class="icon icon-bar"></i>
</i>
<div my-arc-progress="document.progress.percent"></div>
</div>
<div ng-switch-default class="img_round_meta_contents"><i class="icon icon-videoplay"></i></div>
</div>
<div ng-if="document.url" ng-show="document.downloaded &amp;&amp; isActive" ng-switch="document.mime_type == 'video/mp4'">
<video ng-switch-when="true" width="{{document.thumb.width}}" height="{{document.thumb.height}}" autoplay class="img_round_video">
<source ng-src="{{document.url}}" type="video/mp4">
</video>
<img ng-switch-default class="img_round_image" ng-src="{{document.url}}" width="{{document.thumb.width}}" height="{{document.thumb.height}}" />
</div>
<img ng-hide="document.downloaded &amp;&amp; isActive" class="img_round_thumb" my-load-thumb thumb="document.thumb" />
</div>
</a>

1
app/partials/desktop/head.html

@ -53,6 +53,7 @@ @@ -53,6 +53,7 @@
<li><a ng-click="toggleMedia('video')" my-i18n="head_media_video"></a></li>
<li><a ng-click="toggleMedia('documents')" my-i18n="head_media_documents"></a></li>
<li><a ng-click="toggleMedia('audio')" my-i18n="head_media_audio"></a></li>
<li><a ng-click="toggleMedia('round')" my-i18n="head_media_round"></a></li>
</ul>
</div>

50
app/partials/desktop/im.html

@ -211,56 +211,8 @@ @@ -211,56 +211,8 @@
</a>
<a class="pull-left im_panel_own_photo" my-peer-photolink="draftMessage.isBroadcast ? historyPeer.id : ownID" img-class="im_panel_own_photo" watch="true" ng-click="openSettings()" no-open="true"></a>
<form my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length, composer_progress_enabled: draftMessage.inlineProgress}">
<div my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" reply-keyboard="historyState.replyKeyboard"></div>
<div class="im_send_form_inline_results" my-inline-results="inlineResults"></div>
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMsgID > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear(true)"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMsgID" watch="true" edit="{{draftMessage.type == 'edit'}}"></a>
</div>
<div class="im_send_reply_wrap im_send_fwds_wrap" ng-if="draftMessage.fwdMessages.length > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.fwdsClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<div class="im_message_reply_wrap" my-forwarded-messages="draftMessage.fwdMessages"></div>
</div>
<div class="im_send_field_wrap hasselect" ng-class="historyState.replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_emoji_insert_btn"><i class="icon icon-emoji"></i></a>
<div class="composer_progress_icon_wrap">
<div class="composer_progress_icon" my-arc-progress width="22" stroke="2.5"></div>
</div>
<a class="composer_command_btn" ng-show="!historyState.replyKeyboard && commands.list.length > 0 && (!draftMessage.text.length || draftMessage.text == '/')" ng-mousedown="toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="replyKeyboardToggle($event)" ng-class="!historyState.replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>
<div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div>
<textarea ng-model="draftMessage.text" class="form-control im_message_field no_outline" dir="auto" ng-trim="false"></textarea>
</div>
<div class="im_send_buttons_wrap clearfix">
<button type="submit" class="btn btn-md im_submit" ng-class="draftMessage.type == 'edit' ? 'im_submit_edit' : 'im_submit_send'">
<span class="im_submit_send_label nocopy" my-i18n="im_submit_message"></span>
<span class="im_submit_edit_label nocopy" my-i18n="im_submit_edit_message"></span>
</button>
<div class="im_attach pull-left">
<input type="file" class="im_attach_input" size="28" multiple="multiple" title="{{'im_attach_file_title' | i18n}}" />
<i class="icon icon-paperclip"></i>
</div>
<div class="im_media_attach pull-left">
<input type="file" class="im_media_attach_input" size="28" multiple="multiple" accept="image/*, video/*, audio/*" title="{{'im_media_attach_title' | i18n}}"/>
<i class="icon icon-camera"></i>
</div>
<div class="composer_emoji_panel"></div>
</div>
<div class="im_send_keyboard_wrap" ng-if="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!historyState.replyKeyboard.pFlags.hidden">
<div my-reply-markup="historyState.replyKeyboard"></div>
</div>
</form>
</div>
</div>

2
app/partials/desktop/message_attach_document.html

@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
<div ng-switch-when="gif" my-load-gif document="media.document"></div>
<div ng-switch-when="round" my-load-round document="media.document"></div>
<div ng-switch-when="sticker" my-load-sticker document="media.document" open="true"></div>
<div ng-switch-when="voice" class="im_message_audio">

1
app/partials/desktop/message_service.html

@ -50,6 +50,7 @@ @@ -50,6 +50,7 @@
<span ng-if="historyMessage.action.duration > 0" ng-bind="historyMessage.action.duration | duration" class="im_service_message_phonecall_duration"></span>
</a>
<span ng-switch-when="messageActionPaymentSent" class="message_service_payment_sent"></span>
<span ng-switch-default my-i18n="message_service_unsupported_action">
<my-i18n-param name="action"><span ng-bind="historyMessage.action._"></span></my-i18n-param>

68
app/partials/desktop/send_form.html

@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
<form class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length, composer_progress_enabled: draftMessage.inlineProgress, im_voice_recording: voiceRecorder.recording, im_processing_recording: voiceRecorder.processing}">
<div class="im_send_form_inline_results" my-inline-results="inlineResults"></div>
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMsgID > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear(true)"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMsgID" watch="true" edit="{{draftMessage.type == 'edit'}}"></a>
</div>
<div class="im_send_reply_wrap im_send_fwds_wrap" ng-if="draftMessage.fwdMessages.length > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.fwdsClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<div class="im_message_reply_wrap" my-forwarded-messages="draftMessage.fwdMessages"></div>
</div>
<div class="im_send_field_wrap hasselect" ng-class="replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_emoji_insert_btn"><i class="icon icon-emoji"></i></a>
<div class="composer_progress_icon_wrap">
<div class="composer_progress_icon" my-arc-progress width="22" stroke="2.5"></div>
</div>
<a class="composer_command_btn" ng-show="!replyKeyboard && commands.list.length > 0 && (!draftMessage.text.length || draftMessage.text == '/')" ng-mousedown="draftMessage.toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="draftMessage.replyKeyboardToggle($event)" ng-class="!replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>
<div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div>
<div class="im_voice_recorder_wrap">
<div class="im_recorder_indicator"><i></i></div>
<div class="im_recorder_time" ng-bind="voiceRecorder.duration | duration"></div>
<div class="im_recorder_label" ng-switch="voiceRecorder.processing">
<span ng-switch-when="true" my-i18n="im_voice_processing_label">
<my-i18n-param name="dots"></my-i18n-param>
</span>
<span ng-switch-default>
<span class="im_recorder_label_hover" my-i18n="im_voice_recording_label"></span>
<span class="im_recorder_label_hout" my-i18n="im_voice_recording_cancel_label"></span>
</span>
</div>
</div>
<textarea ng-model="draftMessage.text" class="form-control im_message_field no_outline" dir="auto" ng-trim="false"></textarea>
</div>
<div class="im_send_buttons_wrap clearfix">
<button type="submit" class="btn btn-md im_submit" ng-class="draftMessage.type == 'edit' ? 'im_submit_edit' : 'im_submit_send'">
<span class="im_submit_send_label nocopy" my-i18n="im_submit_message"></span>
<span class="im_submit_edit_label nocopy" my-i18n="im_submit_edit_message"></span>
</button>
<div class="im_attach pull-left">
<input type="file" class="im_attach_input" size="28" multiple="multiple" title="{{'im_attach_file_title' | i18n}}" />
<i class="icon icon-paperclip"></i>
</div>
<div class="im_media_attach pull-left">
<input type="file" class="im_media_attach_input" size="28" multiple="multiple" accept="image/*, video/*, audio/*" title="{{'im_media_attach_title' | i18n}}"/>
<i class="icon icon-camera"></i>
</div>
<a class="im_record pull-left">
<i class="icon icon-mic"></i>
</a>
<div class="composer_emoji_panel"></div>
</div>
<div class="im_send_keyboard_wrap" ng-if="replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!replyKeyboard.pFlags.hidden">
<div my-reply-markup="replyKeyboard"></div>
</div>
</form>

3
app/partials/desktop/short_message.html

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
<my-i18n msgid="conversation_media_sticker"></my-i18n>
</span>
<span ng-switch-when="gif" my-i18n="conversation_media_gif"></span>
<span ng-switch-when="round" my-i18n="conversation_media_round"></span>
<span ng-switch-when="audio" my-i18n="conversation_media_audio"></span>
<span ng-switch-when="voice" my-i18n="conversation_media_audio"></span>
<span ng-switch-when="video" my-i18n="conversation_media_video"></span>
@ -18,6 +19,7 @@ @@ -18,6 +19,7 @@
<span my-emoji-image="🎮"></span>
<span ng-bind-html="message.media.game.rTitle"></span>
</span>
<span ng-switch-when="messageMediaUnsupported" my-i18n="conversation_media_unsupported"></span>
</span><span class="im_short_message_service" ng-if="message._ == 'messageService'" ng-switch="message.action._">
<span ng-switch-when="messageActionChatCreate" my-i18n="conversation_group_created"></span>
<span ng-switch-when="messageActionChatEditTitle" my-i18n="conversation_group_renamed"></span>
@ -55,5 +57,6 @@ @@ -55,5 +57,6 @@
<span ng-switch-when="out_ok" my-i18n="message_service_phonecall_outgoing"></span>
<span ng-switch-when="in_ok" my-i18n="message_service_phonecall_incoming"></span>
</span>
<span ng-switch-when="messageActionPaymentSent" my-i18n="message_service_payment_sent"></span>
</span><span class="im_short_message_text" ng-if="message.message.length && (!message.media || message.media._ == 'messageMediaWebPage')" ng-bind-html="message.richMessage"></span>

25
app/partials/mobile/audio_player.html

@ -4,10 +4,11 @@ @@ -4,10 +4,11 @@
</a>
<div class="audio_player_title_wrap">
<div class="audio_player_meta pull-right" ng-if="audio.downloaded &amp;&amp; (mediaPlayer.player.duration || audio.duration)" ng-switch="mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0">
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : duration()"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="duration() | duration"></span>
</div>
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span class="copyonly">[ </span>
<a ng-attr-title="{{audio.file_name}}" ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span ng-switch-when="2">
<strong ng-bind="::audio.audioPerformer"></strong>
<span ng-bind="::(audio.audioPerformer ? '– ' : '') + audio.audioTitle"></span>
@ -15,19 +16,20 @@ @@ -15,19 +16,20 @@
<span ng-switch-when="1" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span>
</a>
<span class="copyonly">]</span>
<i ng-if="::message.pFlags.media_unread || false" ng-show="message.pFlags.media_unread" class="icon icon-audio-unread"></i>
<div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<div class="audio_player_meta" ng-if="!audio.downloaded || !duration()" ng-switch="audio.progress.enabled">
<span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>
</div>
</div>
<div class="audio_player_actions" ng-if="!audio.progress.enabled &amp;&amp; !audio.downloaded">
<a ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
<a ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
<div class="audio_player_actions noselect" ng-if="!audio.progress.enabled &amp;&amp; !audio.downloaded">
<a class="nocopy" ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
<a class="nocopy" ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
</div>
<div class="audio_player_progress_wrap" ng-if="audio.progress.enabled || audio.downloaded" ng-switch="audio.progress.enabled">
<div ng-switch-when="true" class="clearfix im_message_cancelable_progress_wrap">
<a class="im_message_media_progress_cancel pull-right" ng-click="audio.progress.cancel()" my-i18n="modal_cancel"></a>
<a class="im_message_media_progress_cancel pull-right nocopy" ng-click="audio.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: audio.progress.percent + '%'}"></div>
@ -35,10 +37,9 @@ @@ -35,10 +37,9 @@
</div>
</div>
<div ng-switch-default class="im_message_playback_progress_wrap">
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="mediaPlayer.player.duration || audio.duration" slider-onchange="seek(value)"></div>
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="duration()" slider-onchange="seek(value)"></div>
<div class="audio_player_volume_slider" my-slider slider-model="mediaPlayer.player.volume" slider-min="0" slider-max="1" slider-onchange="setVolume(value)"></div>
</div>
</div>
<audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="{{audio.mime_type || 'audio/ogg'}}" volume="{{::volume}}" />
</audio>
<div class="audio_player_media" ng-if="audio.url" my-ogv-player="mediaPlayer.player" src="audio.url" volume="{{::volume}}"></div>
</div>

80
app/partials/mobile/channel_modal.html

@ -4,24 +4,21 @@ @@ -4,24 +4,21 @@
<div class="navbar navbar-static-top navbar-inverse">
<div class="container">
<div class="navbar-toggle-wrap dropdown" dropdown ng-if="chatFull.chat.pFlags.creator || !chatFull.chat.pFlags.left && !chatFull.chat.pFlags.kicked">
<div class="navbar-toggle-wrap dropdown" dropdown ng-if="hasRights('edit_title') || hasRights('edit_photo')">
<a class="dropdown-toggle navbar-toggle" dropdown-toggle>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<ul class="dropdown-menu">
<li ng-if="hasRights('edit_photo')">
<li ng-if="chatFull.chat.photo.photo_small" ng-if="hasRights('edit_photo')">
<a ng-click="deletePhoto()" my-i18n="group_modal_menu_delete_photo"></a>
</li>
<li ng-if="hasRights('edit_title')">
<a ng-click="editChannel()" my-i18n="modal_edit"></a>
</li>
<li ng-if="chatFull.chat.pFlags.creator">
<a ng-click="deleteChannel()" my-i18n="channel_modal_delete_channel"></a>
</li>
<li ng-if="!chatFull.chat.pFlags.creator && !chatFull.chat.pFlags.left && !chatFull.chat.pFlags.kicked">
<a ng-click="leaveChannel()" my-i18n="group_modal_menu_leave"></a>
<a ng-click="editChannel()" ng-switch="isMegagroup">
<span ng-switch-when="true" my-i18n="group_modal_menu_edit_group"></span>
<span ng-switch-default my-i18n="channel_modal_menu_edit"></span>
</a>
</li>
</ul>
</div>
@ -32,8 +29,11 @@ @@ -32,8 +29,11 @@
<li>
<a ng-click="$close()" class="navbar-quick-media-back">
<i class="icon icon-back"></i>
<div class="navbar-quick-back-title">
<h4 my-i18n="channel_modal_info"></h4>
<div class="navbar-quick-back-title" ng-switch="isMegagroup">
<h4>
<span ng-switch-when="true" my-i18n="group_modal_info"></span>
<span ng-switch-default my-i18n="channel_modal_info"></span>
</h4>
</div>
</a>
</li>
@ -52,49 +52,59 @@ @@ -52,49 +52,59 @@
<a ng-click="openPhoto(chatFull.chat_photo.id, {p: -chatFull.chat.id})" class="mobile_user_modal_image_wrap pull-left" my-peer-photolink="::-chatFull.chat.id" img-class="mobile_user_modal_image mobile_chat_modal_image" no-open="true" watch="true" ng-class="{disabled: !chatFull.chat.photo.photo_small}" ng-disabled="!chatFull.chat.photo.photo_small"></a>
<div class="mobile_user_modal_info_wrap clearfix">
<h4 class="mobile_user_modal_header" my-peer-link="-chatFull.chat.id" verified="true"></h4>
<h4 class="mobile_user_modal_header" my-peer-link="-chatFull.chat.id"></h4>
<p class="mobile_user_modal_status" ng-if="chatFull.participants_count > 0">
<ng-pluralize count="chatFull.participants_count"
when="group_modal_pluralize_participants">
when="group_modal_pluralize_participants">
</ng-pluralize>
</p>
</div>
</div>
<div class="mobile_modal_section" ng-if="chatFull.rAbout">
<h4 class="mobile_modal_section_header" my-i18n="channel_modal_description"></h4>
<div class="mobile_modal_section_value" ng-bind-html="chatFull.rAbout"></div>
<div class="mobile_modal_section" ng-if="chatFull.chat.username">
<h4 class="mobile_modal_section_header" my-i18n="channel_modal_share_link"></h4>
<div class="mobile_modal_section_value">
<a class="settings_modal_username_link" ng-click="shareLink($event)" ng-bind="'https://t.me/' + chatFull.chat.username" ng-href="https://t.me/{{chatFull.chat.username}}" target="_blank"></a>
</div>
</div>
<div class="mobile_modal_section" ng-if="chatFull.chat.username || chatFull.chat.pFlags.creator">
<div class="mobile_modal_section" ng-if="!chatFull.chat.username &amp;&amp; chatFull.chat.pFlags.creator">
<h4 class="mobile_modal_section_header" my-i18n="channel_modal_share_link"></h4>
<div class="mobile_modal_section_value" ng-switch="chatFull.chat.username.length > 0">
<a ng-switch-when="true" class="settings_modal_username_link" ng-click="shareLink($event)" ng-bind="'https://t.me/' + chatFull.chat.username" ng-href="https://t.me/{{chatFull.chat.username}}" target="_blank"></a>
<a ng-switch-default ng-click="shareLink($event)" ng-bind="chatFull.exported_invite.link" ng-href="{{chatFull.exported_invite.link}}" target="_blank"></a>
<div class="mobile_modal_section_value" ng-switch="chatFull.exported_invite._">
<a ng-switch-when="chatInviteExported" class="settings_modal_username_link" ng-click="shareLink($event)" ng-bind="chatFull.exported_invite.link" ng-href="{{chatFull.exported_invite.link}}" target="_blank"></a>
<span ng-switch-default my-i18n="channel_modal_share_loading">
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param>
</span>
</div>
</div>
<div class="mobile_modal_action_wrap" ng-if="hasRights('invite')">
<a class="mobile_modal_action" ng-click="inviteToChannel()" my-i18n="channel_modal_add_member"></a>
<div class="mobile_modal_section" ng-if="chatFull.rAbout">
<h4 class="mobile_modal_section_header" my-i18n="channel_modal_description"></h4>
<div class="mobile_modal_section_value" ng-bind-html="chatFull.rAbout"></div>
</div>
<div class="mobile_modal_action_wrap" ng-if="chatFull.chat.pFlags.left">
<a class="mobile_modal_action" ng-click="joinChannel()" my-i18n="channel_modal_join"></a>
</div>
<div class="mobile_modal_action_wrap" ng-if="hasRights('edit_photo') &amp;&amp; !photo.updating">
<span class="mobile_modal_action mobile_modal_upload_action">
<input my-file-upload type="file" multiple="false" class="im_attach_input" size="120" multiple="false" accept="image/x-png, image/png, image/gif, image/jpeg" />
<my-i18n="group_modal_update_photo"></my-i18n>
<span my-i18n="group_modal_update_photo"></span>
</span>
</div>
<div class="mobile_modal_action_wrap" ng-if="photo.updating">
<span class="mobile_modal_action" my-i18n>
<my-i18n="group_modal_update_active"></my-i18n>
<span my-i18n="group_modal_update_active"></span>
<span my-loading-dots></span>
</span>
</div>
<div class="mobile_modal_action_wrap" ng-if="hasRights('invite') || chatFull.chat.pFlags.left" ng-switch="chatFull.chat.pFlags.left">
<a ng-switch-when="true" class="mobile_modal_action" ng-click="joinChannel()" ng-switch="isMegagroup">
<span ng-switch-when="true" my-i18n="group_modal_join"></span>
<span ng-switch-default my-i18n="channel_modal_join"></span>
</a>
<a ng-switch-default class="mobile_modal_action" ng-click="inviteToChannel()" my-i18n="channel_modal_add_member"></a>
</div>
<div class="mobile_modal_action_wrap">
<a class="mobile_modal_action tg_checkbox clearfix" ng-click="settings.notifications = !settings.notifications" ng-class="settings.notifications ? 'tg_checkbox_on' : ''">
<span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span>
@ -102,15 +112,27 @@ @@ -102,15 +112,27 @@
</a>
</div>
<div class="mobile_modal_action_wrap" ng-if="!chatFull.chat.pFlags.creator && !chatFull.chat.pFlags.left && !chatFull.chat.pFlags.kicked && !isMegagroup">
<a class="mobile_modal_action" ng-click="leaveChannel()" my-i18n="channel_modal_leave_channel"></a>
</div>
<div class="mobile_modal_action_wrap" ng-if="chatFull.chat.pFlags.creator">
<a class="mobile_modal_action" ng-click="deleteChannel()" ng-switch="isMegagroup">
<span ng-switch-when="true" my-i18n="group_modal_delete_group"></span>
<span ng-switch-default my-i18n="channel_modal_delete_channel"></span>
</a>
</div>
<div class="mobile_modal_section" ng-if="chatFull.participants.participants.length > 0">
<h4 class="mobile_modal_section_header" my-i18n="group_modal_members"></h4>
<div class="mobile_modal_section_body">
<div class="chat_modal_members_list">
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants">
<a ng-if="participant.canKick" ng-click="kickFromChannel(participant.user_id)" class="chat_modal_participant_kick pull-right" my-i18n="group_modal_members_kick"></a>
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants | orderBy:'-user.sortStatus'">
<a ng-if="participant.canLeave" ng-click="leaveChannel()" class="chat_modal_participant_kick pull-right" my-i18n="group_modal_menu_leave"></a>
<a ng-if="participant.canKick" ng-click="kickFromChannel(participant.user_id)" class="chat_modal_participant_kick pull-right" my-i18n="group_modal_members_kick"></a>
<a class="chat_modal_participant_photo pull-left" my-peer-photolink="participant.user_id" img-class="chat_modal_participant_photo" status="true"></a>

7
app/partials/mobile/chat_modal.html

@ -20,6 +20,9 @@ @@ -20,6 +20,9 @@
<li>
<a ng-click="flushHistory(true)" my-i18n="group_modal_menu_clear_history"></a>
</li>
<li ng-if="chatFull.chat.pFlags.creator">
<a ng-click="migrateToSuperGroup()" my-i18n="group_modal_migrate_to_supergroup"></a>
</li>
</ul>
</div>
@ -71,12 +74,12 @@ @@ -71,12 +74,12 @@
<div class="mobile_modal_action_wrap" ng-if="hasRights('edit_photo') &amp;&amp; !photo.updating">
<span class="mobile_modal_action mobile_modal_upload_action">
<input my-file-upload type="file" multiple="false" class="im_attach_input" size="120" multiple="false" accept="image/x-png, image/png, image/gif, image/jpeg" />
<my-i18n="group_modal_update_photo"></my-i18n>
<span my-i18n="group_modal_update_photo"></span>
</span>
</div>
<div class="mobile_modal_action_wrap" ng-if="photo.updating">
<span class="mobile_modal_action" my-i18n>
<my-i18n="group_modal_update_active"></my-i18n>
<span my-i18n="group_modal_update_active"></span>
<span my-loading-dots></span>
</span>
</div>

1
app/partials/mobile/head.html

@ -25,6 +25,7 @@ @@ -25,6 +25,7 @@
<li ng-if="!historyFilter.mediaType"><a ng-click="toggleMedia('video')" my-i18n="head_media_video"></a></li>
<li ng-if="!historyFilter.mediaType"><a ng-click="toggleMedia('documents')" my-i18n="head_media_documents"></a></li>
<li ng-if="!historyFilter.mediaType"><a ng-click="toggleMedia('audio')" my-i18n="head_media_audio"></a></li>
<li ng-if="!historyFilter.mediaType"><a ng-click="toggleMedia('round')" my-i18n="head_media_round"></a></li>
</ul>
</div>

44
app/partials/mobile/im.html

@ -136,47 +136,7 @@ @@ -136,47 +136,7 @@
<div class="im_send_form_wrap1">
<div class="im_send_form_wrap clearfix" ng-controller="AppImSendController">
<form my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length && draftMessage.type != 'edit', composer_progress_enabled: draftMessage.inlineProgress}">
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMsgID > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMsgID" watch="true" edit="{{draftMessage.type == 'edit'}}"></a>
</div>
<div class="im_send_reply_wrap im_send_fwds_wrap" ng-if="draftMessage.fwdMessages.length > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.fwdsClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<div class="im_message_reply_wrap" my-forwarded-messages="draftMessage.fwdMessages"></div>
</div>
<div class="im_send_field_panel">
<div class="im_send_field_wrap" ng-class="historyState.replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_command_btn" ng-show="!historyState.replyKeyboard && commands.list.length > 0 && (!draftMessage.text.length || draftMessage.text[0] == '/')" ng-mousedown="toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="replyKeyboardToggle($event)" ng-class="!historyState.replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>
<div class="composer_progress_icon_wrap">
<div class="composer_progress_icon" my-arc-progress width="22" stroke="2.5"></div>
</div>
<div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div>
<textarea ng-model="draftMessage.text" class="form-control im_message_field no_outline" dir="auto" ng-trim="false"></textarea>
</div>
<div class="im_attach pull-right">
<input type="file" class="im_attach_input" size="28" multiple="true" title="{{'im_media_attach_title' | i18n}}" />
<i class="icon icon-paperclip"></i>
</div>
<a class="composer_emoji_insert_btn pull-right"><i class="icon icon-emoji"></i></a>
<button type="submit" class="btn btn-success im_submit"></button>
</div>
<div class="im_send_keyboard_wrap" ng-if="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!historyState.replyKeyboard.pFlags.hidden">
<div my-reply-markup="historyState.replyKeyboard"></div>
</div>
</form>
<div my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" reply-keyboard="historyState.replyKeyboard"></div>
</div>
</div>
@ -194,4 +154,4 @@ @@ -194,4 +154,4 @@
</div>
<toaster-container toaster-options="{'position-class': 'toast-bottom-center'}"></toaster-container>
<toaster-container toaster-options="{'position-class': 'toast-bottom-center'}"></toaster-container>

2
app/partials/mobile/message_attach_document.html

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
<div ng-switch="::media.document.type">
<div ng-switch-when="gif" my-load-gif document="media.document"></div>
<div ng-switch-when="round" my-load-round document="media.document"></div>
<div ng-switch-when="sticker" my-load-sticker document="media.document" open="true"></div>

2
app/partials/mobile/message_service.html

@ -49,6 +49,8 @@ @@ -49,6 +49,8 @@
<span ng-switch-when="in_ok" my-i18n="message_service_phonecall_incoming"></span>
<span ng-if="historyMessage.action.duration > 0" ng-bind="historyMessage.action.duration | duration" class="im_service_message_phonecall_duration"></span>
</a>
<span ng-switch-when="messageActionPaymentSent" class="message_service_payment_sent"></span>
<span ng-switch-default my-i18n="message_service_unsupported_action">
<my-i18n-param name="action"><span ng-bind="historyMessage.action._"></span></my-i18n-param>

59
app/partials/mobile/send_form.html

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
<form class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length && draftMessage.type != 'edit', composer_progress_enabled: draftMessage.inlineProgress, im_voice_recording: voiceRecorder.recording, im_processing_recording: voiceRecorder.processing}">
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMsgID > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear(true)"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMsgID" watch="true" edit="{{draftMessage.type == 'edit'}}"></a>
</div>
<div class="im_send_reply_wrap im_send_fwds_wrap" ng-if="draftMessage.fwdMessages.length > 0">
<a class="im_send_reply_cancel" ng-mousedown="draftMessage.fwdsClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<div class="im_message_reply_wrap" my-forwarded-messages="draftMessage.fwdMessages"></div>
</div>
<div class="im_send_field_panel">
<div class="im_voice_recorder_wrap">
<div class="im_recorder_indicator"><i></i></div>
<div class="im_recorder_time" ng-bind="voiceRecorder.duration | duration"></div>
<div class="im_recorder_label" ng-switch="voiceRecorder.processing">
<span ng-switch-when="true" my-i18n="im_voice_processing_label">
<my-i18n-param name="dots"></my-i18n-param>
</span>
<span ng-switch-default>
<span class="im_recorder_label_hover" my-i18n="im_voice_recording_label"></span>
<span class="im_recorder_label_hout" my-i18n="im_voice_recording_cancel_label"></span>
</span>
</div>
</div>
<div class="im_send_field_wrap" ng-class="replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_emoji_insert_btn pull-right"><i class="icon icon-emoji"></i></a>
<a class="composer_command_btn" ng-show="!replyKeyboard && commands.list.length > 0 && (!draftMessage.text.length || draftMessage.text[0] == '/')" ng-mousedown="draftMessage.toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="draftMessage.replyKeyboardToggle($event)" ng-class="!replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>
<div class="composer_progress_icon_wrap">
<div class="composer_progress_icon" my-arc-progress width="22" stroke="2.5"></div>
</div>
<div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div>
<textarea ng-model="draftMessage.text" class="form-control im_message_field no_outline" dir="auto" ng-trim="false"></textarea>
</div>
<div class="im_attach pull-left">
<input type="file" class="im_attach_input" size="28" multiple="true" title="{{'im_media_attach_title' | i18n}}" />
<i class="icon icon-paperclip"></i>
</div>
<div class="im_record pull-right">
<i class="icon icon-mic"></i>
</div>
<button type="submit" class="btn btn-success im_submit"></button>
</div>
<div class="im_send_keyboard_wrap" ng-if="replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!replyKeyboard.pFlags.hidden">
<div my-reply-markup="replyKeyboard"></div>
</div>
</form>

2
app/partials/mobile/settings_modal.html

@ -158,7 +158,7 @@ @@ -158,7 +158,7 @@
<a href="https://twitter.com/telegram_web" target="_blank" title="{{'settings_modal_follow_us_twitter' | i18n}}" class="settings_external_service"><i class="icon icon-twitter"></i></a>
</div>
<p>
<a ng-click="openChangelog()" my-i18n="settings_modal_recent_updates">
<a href="https://github.com/zhukov/webogram/blob/master/CHANGELOG.md" target="_blank" my-i18n="settings_modal_recent_updates">
<my-i18n-param name="version" ng-bind="version"></my-i18n-param>
</a>
</p>

2
app/vendor/README.md vendored

@ -46,7 +46,7 @@ Normalize, CSS-framework @@ -46,7 +46,7 @@ Normalize, CSS-framework
### [nanoScrollerJS](https://github.com/jamesflorentino/nanoScrollerJS)
**Author**: James Florentino
**License**: MIT, https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE-MIT
**License**: MIT, https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE
Beautiful OS X Lion-like scrollbars

15
app/vendor/angular-media-player/angular-media-player.js vendored

@ -50,7 +50,9 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -50,7 +50,9 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
this.$domEl.load();
this.ended = undefined;
if (autoplay) {
this.$element.one('canplay', this.play.bind(this));
// ogv.js doesn't have support for canplay event yet
var canPlayEvent = this.$domEl.tagName == 'OGVJS' ? 'loadeddata' : 'canplay'
this.$element.one(canPlayEvent, this.play.bind(this));
}
},
/**
@ -242,6 +244,10 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -242,6 +244,10 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
* Returns an unbinding function
*/
var bindListeners = function (au, al, element) {
var updateTime = function (scope) {
scope.currentTime = al.currentTime;
scope.formatTime = scope.$formatTime(scope.currentTime);
}
var listeners = {
playing: function () {
au.$apply(function (scope) {
@ -261,13 +267,13 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -261,13 +267,13 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
au.$apply(function (scope) {
scope.ended = true;
scope.playing = false; // IE9 does not throw 'pause' when file ends
updateTime(scope)
});
}
},
timeupdate: throttle(1000, false, function () {
au.$apply(function (scope) {
scope.currentTime = al.currentTime;
scope.formatTime = scope.$formatTime(scope.currentTime);
updateTime(scope)
});
}),
loadedmetadata: function () {
@ -278,6 +284,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -278,6 +284,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
if (al.buffered.length) {
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
}
updateTime(scope)
});
},
progress: function () {
@ -434,7 +441,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -434,7 +441,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
scope.$eval(mediaName + ' = player', {player: player});
}
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO') {
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO' && element[0].tagName !== 'OGVJS') {
return new Error('player directive works only when attached to an <audio>/<video> type tag');
}
var mediaElement = [],

21
app/vendor/ogv.js/COPYING vendored

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
ogv.js & ogv.swf wrapper and player code
Copyright (c) 2013-2014 Brion Vibber and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

28
app/vendor/ogv.js/COPYING-ogg.txt vendored

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
Copyright (c) 2002, Xiph.org Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

44
app/vendor/ogv.js/COPYING-opus.txt vendored

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
Jean-Marc Valin, Timothy B. Terriberry,
CSIRO, Gregory Maxwell, Mark Borgerding,
Erik de Castro Lopo
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Opus is subject to the royalty-free patent licenses which are
specified at:
Xiph.Org Foundation:
https://datatracker.ietf.org/ipr/1524/
Microsoft Corporation:
https://datatracker.ietf.org/ipr/1914/
Broadcom Corporation:
https://datatracker.ietf.org/ipr/1526/

28
app/vendor/ogv.js/COPYING-theora.txt vendored

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
Copyright (C) 2002-2009 Xiph.org Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

28
app/vendor/ogv.js/COPYING-vorbis.txt vendored

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
Copyright (c) 2002-2008 Xiph.org Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

13
app/vendor/ogv.js/LICENSE-nestegg.txt vendored

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
Copyright © 2010 Mozilla Foundation
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

31
app/vendor/ogv.js/LICENSE-vpx.txt vendored

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
Copyright (c) 2010, The WebM Project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Google, nor the WebM Project, nor the names
of its contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

23
app/vendor/ogv.js/PATENTS-vpx.txt vendored

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
Additional IP Rights Grant (Patents)
------------------------------------
"These implementations" means the copyrightable works that implement the WebM
codecs distributed by Google as part of the WebM Project.
Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this section) patent license to
make, have made, use, offer to sell, sell, import, transfer, and otherwise
run, modify and propagate the contents of these implementations of WebM, where
such license applies only to those patent claims, both currently owned by
Google and acquired in the future, licensable by Google that are necessarily
infringed by these implementations of WebM. This grant does not include claims
that would be infringed only as a consequence of further modification of these
implementations. If you or your agent or exclusive licensee institute or order
or agree to the institution of patent litigation or any other patent
enforcement activity against any entity (including a cross-claim or
counterclaim in a lawsuit) alleging that any of these implementations of WebM
or any code incorporated within any of these implementations of WebM
constitute direct or contributory patent infringement, or inducement of
patent infringement, then any patent rights granted to you under this License
for these implementations of WebM shall terminate as of the date such
litigation is filed.

371
app/vendor/ogv.js/README.md vendored

@ -0,0 +1,371 @@ @@ -0,0 +1,371 @@
ogv.js
======
Media decoder and player for Ogg Vorbis/Opus/Theora and (experimentally) WebM video.
Based around libogg, libvorbis, libtheora, libopus, libvpx, and libnestegg compiled to JavaScript with Emscripten.
## Updates
1.4.2 - 2017-04-24
* support 8-bit 4:2:2 and 4:4:4 subsampling in VP9
1.4.1 - 2017-04-07
* fix for seek shortly after initialization
* fix for some missing instance constants
1.4.0 - 2017-04-06
* fastSeek() is now fast; seeks to first keyframe found.
* VP9 base profile support in WebM container (8-bit 4:2:0 only).
* Safari no longer complains about missing es6-promise.map source map
* Smoother playback on low-end machines prone to lag spikes: when A/V sync lags, keep audio running smoothly and resync video at the next keyframe. To restore previous behavior, set `sync: 'delay-audio'` in options.
* Experimental Web Assembly builds of all modules; set `wasm: true` in options to force on.
* `error` property now returns an `OGVMediaError` object instead of string.
* Decode pipeline up to 3 frames deep to aid in momentary spikes.
* Experimental multithreaded JS builds for VP8 and VP9; set `threading: true` in options to force on.
* Fixed bad autodetection of files in root dir
1.3.1 - 2017-02-24
* Fix for seeking before load completes
* Fix for bisection seeking in very short Ogg files
1.3.0 - 2017-02-08
* Separated XHR and caching out to stream-file package
* more aggressive in-memory buffering should improve audio seek performance
* improved seek precision on audio files
* fix for Ogg files with stream id of 0
1.2.1 - 2016-09-24
* Performance fixed for playback of Ogg Theora with many duplicate frames ("1000fps" files from ffmpeg)
* Report actual fps (ignoring dupe frames) for Ogg Theora
* Delay loading when using preload="none"
* Fix regression in IE 10 network layer
1.2.0 - 2016-09-19
* Separated software and WebGL paths to yuv-canvas package
* fixed regression in WebM frame rate handling
* buffer up to 3 decoded frames for smoother playback
* smoother audio in the face of short delays (drop late frame if next one is already decoded)
* fixed regression in seeking non-indexed Ogg files
* updated libvpx
1.1.3 - 2016-06-27
* fix play-during-seek bug that interacted with video.js badly
1.1.2 - 2016-06-27
* better a/v sync
* muted autoplay works on iOS
* numerous seeking-related race-condition fixes
* more consistent performance on low-end machines
* supports cross-domain hosting of worker and Flash audio shim
* seeking now works in WebM as well as Ogg
* cleaner multithreading
* lots of little fixes
See more details and history in [CHANGES.md](https://github.com/brion/ogv.js/blob/master/CHANGES.md)
## Current status
Since August 2015, ogv.js can be seen in action [on Wikipedia and Wikimedia Commons](https://commons.wikimedia.org/wiki/Commons:Video) in Safari and IE/Edge where native Ogg and WebM playback is not available. (See [technical details on MediaWiki integration](https://www.mediawiki.org/wiki/Extension:TimedMediaHandler/ogv.js).)
See also a standalone demo with performance metrics at https://brionv.com/misc/ogv.js/demo/
* streaming: yes (with Range header)
* seeking: yes for Ogg and WebM (with Range header)
* color: yes
* audio: yes, with a/v sync (requires Web Audio or Flash)
* background threading: yes (video, audio decoders in Workers)
* [GPU accelerated drawing: yes (WebGL)](https://github.com/brion/ogv.js/wiki/GPU-acceleration)
* GPU accelerated decoding: no
* SIMD acceleration: no
* Web Assembly: yes (experimental; set `options.wasm` to `true`)
* multithreaded VP8, VP9: yes (experimental; set `options.threading` to `true`; requires `SharedArrayBuffer`)
* controls: no (currently provided by demo or other UI harness)
Ogg files are fairly well supported, but WebM is still experimental and is disabled by default.
## Goals
Long-form goal is to create a drop-in replacement for the HTML5 video and audio tags which can be used for basic playback of Ogg Theora and Vorbis or WebM media on browsers that don't support Ogg or WebM natively.
The API isn't quite complete, but works pretty well.
## Compatibility
ogv.js requires a fast JS engine with typed arrays, and either Web Audio or Flash for audio playback.
The primary target browsers are (testing 360p/30fps and up):
* Safari 6.1/7/8/9/10 on Mac OS X 10.7-10.11
* Safari on iOS 8/9/10 64-bit
* Edge on Windows 10 desktop/tablet
* Internet Explorer 10/11 on Windows 7/8/8.1/10 (desktop/tablet)
And for lower-resolution files (testing 160p/15fps):
* Safari on iOS 8/9/10 32-bit
* Edge on Windows 10 Mobile
* Internet Explorer 10/11 on Windows RT
Older versions of Safari have flaky JIT compilers. IE 9 and below lack typed arrays.
(Note that Windows and Mac OS X can support Ogg and WebM by installing codecs or alternate browsers with built-in support, but this is not possible on iOS, Windows RT, or Windows 10 Mobile.)
Testing browsers (these support .ogv and .webm natively):
* Firefox 52
* Chrome 57
## Package installation
Pre-built releases of ogv.js are available as [.zip downloads from the GitHub releases page](https://github.com/brion/ogv.js/releases) and through the npm package manager.
You can load the `ogv.js` main entry point directly in a script tag, or bundle it through whatever build process you like. The other .js files and the .swf file (for audio in IE) must be made available for runtime loading, together in the same directory.
ogv.js will try to auto-detect the path to its resources based on the script element that loads ogv.js or ogv-support.js. If you load ogv.js through another bundler (such as browserify or MediaWiki's ResourceLoader) you may need to override this manually before instantiating players:
```
// Path to ogv-demuxer-ogg.js, ogv-worker-audio.js, dynamicaudio.swf etc
OGVLoader.base = '/path/to/resources';
```
To fetch from npm:
```
npm install ogv
```
The distribution-ready files will appear in 'node_modules/ogv/dist'.
To load the player library into your browserify or webpack project:
```
var ogv = require('ogv');
// Access public classes either as ogv.OGVPlayer or just OGVPlayer.
// Your build/lint tools may be happier with ogv.OGVPlayer!
ogv.OGVLoader.base = '/path/to/resources';
var player = new ogv.OGVPlayer();
```
## Usage
The `OGVPlayer` class implements a player, and supports a subset of the events, properties and methods from [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) and [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement).
```
// Create a new player with the constructor
var player = new OGVPlayer();
// Or with options
var player = new OGVPlayer({
enableWebM: true
});
// Now treat it just like a video or audio element
containerElement.appendChild(player);
player.src = 'path/to/media.ogv';
player.play();
player.addEventListener('ended', function() {
// ta-da!
});
```
To check for compatibility before creating a player, include `ogv-support.js` and use the `OGVCompat` API:
```
if (OGVCompat.supported('OGVPlayer')) {
// go load the full player from ogv.js and instantiate stuff
}
```
This will check for typed arrays, audio/Flash, blacklisted iOS versions, and super-slow/broken JIT compilers.
If you need a URL versioning/cache-buster parameter for dynamic loading of `ogv.js`, you can use the `OGVVersion` symbol provided by `ogv-support.js` or the even tinier `ogv-version.js`:
```
var script = document.createElement('script');
script.src = 'ogv.js?version=' + encodeURIComponent(OGVVersion);
document.querySelector('head').appendChild(script);
```
## Distribution notes
Entry points:
* `ogv.js` contains the main runtime classes, including OGVPlayer, OGVLoader, and OGVCompat.
* `ogv-support.js` contains the OGVCompat class and OGVVersion symbol, useful for checking for runtime support before loading the main `ogv.js`.
* `ogv-version.js` contains only the OGVVersion symbol.
These entry points may be loaded directly from a script element, or concatenated into a larger project, or otherwise loaded as you like.
Further code modules are loaded at runtime, which must be available with their defined names together in a directory. If the files are not hosted same-origin to the web page that includes them, you will need to set up appropriate CORS headers to allow loading of the worker JS modules.
Dynamically loaded assets:
* `ogv-worker-audio.js`, `ogv-worker-video.js`, and `pthread-main.js` are Worker entry points, used to run video and audio decoders in the background.
* `ogv-demuxer-ogg.js` is used in playing .ogg, .oga, and .ogv files.
* `ogv-demuxer-webm.js` is used in playing .webm files.
* `ogv-decoder-audio-vorbis.js` and `ogv-decoder-audio-opus.js` are used in playing both Ogg and WebM files containing audio.
* `ogv-decoder-video-theora.js` is used in playing .ogg and .ogv video files.
* `ogv-decoder-video-vp8.js` and `ogv-decoder-video-vp9.js` are used in playing .webm video files.
* `*-wasm.js` and `*-wasm.wasm` files are the Web Assembly versions of the above modules.
* `*-mt.js` are the multithreaded versions of some of the above modules.
* `dynamicaudio.swf` is the Flash audio shim, used for Internet Explorer 10/11.
If you know you will never use particular formats or codecs you can skip bundling them; for instance if you only need to play Ogg files you don't need `ogv-demuxer-webm.js` or `ogv-decoder-video-vp8.js` which are only used for WebM. Web Assembly and multithreaded modules are experimental and can be left out if not enabled in your runtime options.
## Performance
As of 2015, for SD-or-less resolution basic Ogg Theora decoding speed is reliable on desktop and newer high-end mobile devices; current high-end desktops and laptops can even reach HD resolutions. Older and low-end mobile devices may have difficulty on any but audio and the lowest-resolution video files.
WebM is much slower, and remains experimental.
*Low-res targets*
I've gotten acceptable performance for Vorbis audio and 160p/15fps Theora files on 32-bit iOS devices: iPhone 4s, iPod Touch 5th-gen and iPad 3. These have difficulty at 240p and above, and just won't keep up with higher resolutions.
Meanwhile, newer 64-bit iPhones and iPads are comparable to low-end laptops, and videos at 360p and often 480p play acceptably. Since 32-bit and 64-bit iOS devices have the same user-agent, a benchmark must be used to approximately test minimum CPU speed.
(On iOS, Safari performs significantly better than some alternative browsers that are unable to enable the JIT due to use of the old UIWebView API. Chrome 49 and Firefox for iOS are known to work using the newer WKWebView API internally. Again, a benchmark must be used to detect slow performance, as the browser remains otherwise compatible.)
Windows on 32-bit ARM platforms is similar... IE 11 on Windows RT 8.1 on a Surface tablet (NVidia Tegra 3), and Edge on Windows 10 Mobile build 10166 on a Lumia 635, perform acceptably with audio and with 160p/15fps videos but have trouble starting around 240p.
In both cases, a native application looms as a possibly better alternative. See [OGVKit ](https://github.com/brion/OGVKit) and [OgvRt](https://github.com/brion/OgvRT) projects for experiments in those directions.
Note that at these lower resolutions, Vorbis audio and Theora video decoding are about equally expensive operations -- dual-core phones and tablets should be able to eek out a little parallelism here thanks to audio and video being in separate Worker threads.
*WebGL drawing acceleration*
Accelerated YCbCr->RGB conversion and drawing is done using WebGL on supporting browsers, or through software CPU conversion if not. This is abstracted in the [yuv-canvas](https://github.com/brion/yuv-canvas) package, now separately installable.
It may be possible to do further acceleration of actual decoding operations using WebGL shaders, but this could be ... tricky. WebGL is also only available on the main thread, and there are no compute shaders yet so would have to use fragment shaders.
## Difficulties
*Threading*
Currently the video and audio codecs run in worker threads by default, while the demuxer
and player logic run on the UI thread. This seems to work pretty well.
There is some overhead in extracting data out of each emscripten module's heap and in the thread-to-thread communications, but the parallelism and smoother main thread makes up for it.
*Streaming download*
In Firefox, the 'moz-chunked-array' responseType on XHR is used to read data as ArrayBuffer chunks during download. Safari and Chrome use a 'binary string' read which requires manually converting input to ArrayBuffer chunks. In IE and Edge have an (MS-prefixed) Stream/StreamReader interface which can be used to read data on demand into ArrayBuffer objects, but it has proved problematic especially with intermediate proxies; as of 1.1.2 IE and Edge use the same chunked binary-string method as Safari and Chrome.
The Firefox and Safari/Chrome cases have been hacked up to do streaming buffering by chunking the requests at up to a megabyte each, using the HTTP Range header. For cross-site playback, this requires CORS setup to whitelist the Range header!
[Safari has a bug with Range headers](https://bugs.webkit.org/show_bug.cgi?id=82672) which is worked around as necessary with a 'cache-busting' URL string parameter. Hopefully this will be fixed in future versions of Mac OS X and iOS.
*Seeking*
Seeking is implemented via the HTTP Range: header.
For Ogg files with keyframe indices in a skeleton index, seeking is very fast. Otherwise, a bisection search is used to locate the target frame or audio position, which is very slow over the internet as it creates a lot of short-lived HTTP requests.
For WebM files with cues, efficient seeking is supported as well as of 1.1.2.
As with chunked streaming, cross-site playback requires CORS support for the Range header.
*Audio output*
Audio output is handled through the [AudioFeeder](https://github.com/brion/audio-feeder) library, which encapsulates use of Web Audio API or Flash depending on browser support:
Firefox, Safari, Chrome, and Edge support the W3C Web Audio API.
IE doesn't support Web Audio, but does bundle the Flash player in Windows 8/8.1/RT. A small Flash shim is included here and used as a fallback -- thanks to Maik Merten for hacking some pieces together and getting this working!
A/V synchronization is performed on files with both audio and video, and seems to
actually work. Yay!
Note that autoplay with audio doesn't work on iOS Safari due to limitations with starting audio playback from event handlers; if playback is started outside an event handler, the player will hang due to broken audio.
As of 1.1.1, muting before script-triggered playback allows things to work:
```
player = new OGVPlayer();
player.muted = true;
player.src = 'path/to/file-with-audio.ogv';
player.play();
```
You can then unmute the video in response to a touch or click handler. Alternately if audio is not required, do not include an audio track in the file.
*WebM*
WebM support was added in June 2015, with some major issues finally worked out in May 2016. Initial VP9 support was added in February 2017. It remains experimental, but should be fully enabled in the future once a few more bugs are worked out.
To enable, set `enableWebM: true` in your `options` array.
Beware that performance of WebM VP8 is much slower than Ogg Theora, and VP9 is slower still.
For best WebM decode speed, consider encoding VP8 with "profile 1" (simple deblocking filter) which will sacrifice quality modestly, mainly in high-motion scenes. When encoding with ffmpeg, this is the `-profile:v 1` option to the `libvpx` codec.
It is also recommended to use the `-slices` option for VP8, or `-tile-columns` for VP9, to maximize ability to use multithreaded decoding when available.
## Upstream library notes
We've experimented with tremor (libivorbis), an integer-only variant of libvorbis. This actually does *not* decode faster, but does save about 200kb off our generated JavaScript, presumably thanks to not including an encoder in the library. However on slow devices like iPod Touch 5th-generation, it makes a significant negative impact on the decode time so we've gone back to libvorbis.
The Ogg Skeleton library (libskeleton) is a bit ... unfinished and is slightly modified here.
libvpx is slightly modified to work around emscripten threading limitations in the VP8 decoder.
## Web Assembly
Experimental Web Assembly (WASM) versions of the emscripten cross-compiled modules are also included, used if `options.wasm` is true.
The WASM versions of the modules are more compact than the cross-compiled asm.js-style JavaScript, and should download and parse faster. Some browsers may also compile the module differently, providing more consistent performance at the beginning of playback.
Currently Firefox and Chrome are the only release versions of browsers that support Web Assembly, but it's available in Safari Technical Preview and behind the 'experimental JS options' flag in Edge in Windows 10 version 1703.
If you are making a slim build and will not use the `wasm` option, you can leave out the `*-wasm.js` and `*-wasm.wasm` files.
## Multithreading
Experimental multithreaded VP8 and VP9 decoding up to 4 cores is available for VP8 and VP9 video, used if `options.threading` is true. This requires browser support for the new `SharedArrayBuffer` and `Atomics` APIs, currently available in Safari 10.1 / iOS 10.3 and in Firefox developer & nightly builds, and in Chrome behind a flag.
Threading is not currently compatible with Web Assembly.
Speedups will only be noticeable when using the "slices" or "token partitions" option for VP8 encoding, or the "tile columns" option for VP9 encoding.
Currently, getting a successful multithreaded build requires a [patch to the emscripten compiler](https://github.com/kripken/emscripten/pull/5016); without this patch, the resulting multithreaded modules will build but fail to initialize correctly.
If you are making a slim build and will not use the `threading` option, you can leave out the `*-mt.js` files, as well as `pthread-main.js`.
## Building JS components
Building ogv.js is known to work on Mac OS X and Linux (tested Ubuntu 15.04).
1. You will need autoconf, automake, libtool, pkg-config, and node (nodejs). These can be installed through Homebrew on Mac OS X, or through distribution-specific methods on Linux.
2. Install [Emscripten](http://kripken.github.io/emscripten-site/docs/getting_started/Tutorial.html); currently using the incoming branch (of what will be 1.38) for distribution builds for latest WASM support, plus [multithreading patch](https://github.com/kripken/emscripten/pull/5016)
3. `git submodule update --init`
4. Run `npm install` to install build utilities
5. Run `make js` to configure and build the libraries and the C wrapper
## Building the demo
If you did all the setup above, just run `make demo` or `make`. Look in build/demo/ and enjoy!
## License
libogg, libvorbis, libtheora, libopus, nestegg, and libvpx are available under their respective licenses, and the JavaScript and C wrapper code in this repo is licensed under MIT.
Based on build scripts from https://github.com/devongovett/ogg.js
[AudioFeeder](https://github.com/brion/audio-feeder)'s dynamicaudio.as and other Flash-related bits are based on code under BSD license, (c) 2010 Ben Firshman.
See [AUTHORS.md](https://github.com/brion/ogv.js/blob/master/AUTHORS.md) and/or the git history for a list of contributors.

BIN
app/vendor/ogv.js/dynamicaudio.swf vendored

Binary file not shown.

17
app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.wasm vendored

Binary file not shown.

32
app/vendor/ogv.js/ogv-decoder-audio-opus.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-decoder-audio-vorbis.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-theora-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-theora-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-decoder-video-theora.js vendored

File diff suppressed because one or more lines are too long

31
app/vendor/ogv.js/ogv-decoder-video-vp8-mt.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.wasm vendored

Binary file not shown.

31
app/vendor/ogv.js/ogv-decoder-video-vp8.js vendored

File diff suppressed because one or more lines are too long

33
app/vendor/ogv.js/ogv-decoder-video-vp9-mt.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.wasm vendored

Binary file not shown.

33
app/vendor/ogv.js/ogv-decoder-video-vp9.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-demuxer-ogg-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-demuxer-ogg-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-demuxer-ogg.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-demuxer-webm-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-demuxer-webm-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-demuxer-webm.js vendored

File diff suppressed because one or more lines are too long

272
app/vendor/ogv.js/ogv-support.js vendored

@ -0,0 +1,272 @@ @@ -0,0 +1,272 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
//
// -- ogv-support.js
// https://github.com/brion/ogv.js
// Copyright (c) 2013-2016 Brion Vibber
//
(function() {
var OGVCompat = __webpack_require__(1),
OGVVersion = ("1.4.2-20170425024925-504d7197");
if (window) {
// 1.0-compat globals
window.OGVCompat = OGVCompat;
window.OGVVersion = OGVVersion;
}
module.exports = {
OGVCompat: OGVCompat,
OGVVersion: OGVVersion
};
})();
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var BogoSlow = __webpack_require__(2);
var OGVCompat = {
benchmark: new BogoSlow(),
hasTypedArrays: function() {
// emscripten-compiled code requires typed arrays
return !!window.Uint32Array;
},
hasWebAudio: function() {
return !!(window.AudioContext || window.webkitAudioContext);
},
hasFlash: function() {
if (navigator.userAgent.indexOf('Trident') !== -1) {
// We only do the ActiveX test because we only need Flash in
// Internet Explorer 10/11. Other browsers use Web Audio directly
// (Edge, Safari) or native playback, so there's no need to test
// other ways of loading Flash.
try {
var obj = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
return true;
} catch(e) {
return false;
}
}
return false;
},
hasAudio: function() {
return this.hasWebAudio() || this.hasFlash();
},
isBlacklisted: function(userAgent) {
// JIT bugs in old Safari versions
var blacklist = [
/\(i.* OS [67]_.* like Mac OS X\).* Mobile\/.* Safari\//,
/\(Macintosh.* Version\/6\..* Safari\/\d/
];
var blacklisted = false;
blacklist.forEach(function(regex) {
if (userAgent.match(regex)) {
blacklisted = true;
}
});
return blacklisted;
},
isSlow: function() {
return this.benchmark.slow;
},
isTooSlow: function() {
return this.benchmark.tooSlow;
},
supported: function(component) {
if (component === 'OGVDecoder') {
return (this.hasTypedArrays() && !this.isBlacklisted(navigator.userAgent));
}
if (component === 'OGVPlayer') {
return (this.supported('OGVDecoder') && this.hasAudio() && !this.isTooSlow());
}
return false;
}
};
module.exports = OGVCompat;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
/**
* A quick CPU/JS engine benchmark to guesstimate whether we're
* fast enough to handle 360p video in JavaScript.
*/
function BogoSlow() {
var self = this;
var timer;
// FIXME: avoid to use window scope here
if (window.performance && window.performance.now) {
timer = function() {
return window.performance.now();
};
} else {
timer = function() {
return Date.now();
};
}
var savedSpeed = null;
function run() {
var ops = 0;
var fibonacci = function(n) {
ops++;
if (n < 2) {
return n;
} else {
return fibonacci(n - 2) + fibonacci(n - 1);
}
};
var start = timer();
fibonacci(30);
var delta = timer() - start;
savedSpeed = (ops / delta);
}
/**
* Return a scale value of operations/sec from the benchmark.
* If the benchmark has already been run, uses a memoized result.
*
* @property {number}
*/
Object.defineProperty(self, 'speed', {
get: function() {
if (savedSpeed === null) {
run();
}
return savedSpeed;
}
});
/**
* Return the defined cutoff speed value for 'slow' devices,
* based on results measured from some test devices.
*
* @property {number}
*/
Object.defineProperty(self, 'slowCutoff', {
get: function() {
// 2012 Retina MacBook Pro (Safari 7) ~150,000
// 2009 Dell T5500 (IE 11) ~100,000
// iPad Air (iOS 7) ~65,000
// 2010 MBP / OS X 10.9 (Safari 7) ~62,500
// 2010 MBP / Win7 VM (IE 11) ~50,000+-
// ^ these play 360p ok
// ----------- line of moderate doom ----------
return 50000;
// v these play 160p ok
// iPad Mini non-Retina (iOS 8 beta) ~25,000
// Dell Inspiron Duo (IE 11) ~25,000
// Surface RT (IE 11) ~18,000
// iPod Touch 5th-gen (iOS 8 beta) ~16,000
}
});
/**
* Return the defined cutoff speed value for 'too slow' devices,
* based on results measured from some test devices.
*
* No longer used.
*
* @property {number}
* @deprecated
*/
Object.defineProperty(self, 'tooSlowCutoff', {
get: function() {
return 0;
}
});
/**
* 'Slow' devices can play audio and should sorta play our
* extra-tiny Wikimedia 160p15 transcodes
*
* @property {boolean}
*/
Object.defineProperty(self, 'slow', {
get: function() {
return (self.speed < self.slowCutoff);
}
});
/**
* 'Too slow' devices aren't reliable at all
*
* @property {boolean}
*/
Object.defineProperty(self, 'tooSlow', {
get: function() {
return (self.speed < self.tooSlowCutoff);
}
});
}
module.exports = BogoSlow;
/***/ })
/******/ ]);

70
app/vendor/ogv.js/ogv-version.js vendored

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
//
// -- ogv-support.js
// https://github.com/brion/ogv.js
// Copyright (c) 2013-2016 Brion Vibber
//
(function() {
var OGVVersion = ("1.4.2-20170425024925-504d7197");
if (window) {
// 1.0-compat globals
window.OGVVersion = OGVVersion;
}
module.exports = {
OGVVersion: OGVVersion
};
})();
/***/ })
/******/ ]);

701
app/vendor/ogv.js/ogv-worker-audio.js vendored

@ -0,0 +1,701 @@ @@ -0,0 +1,701 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var OGVWorkerSupport = __webpack_require__(2);
var proxy = new OGVWorkerSupport([
'loadedMetadata',
'audioFormat',
'audioBuffer',
'cpuTime'
], {
init: function(args, callback) {
this.target.init(callback);
},
processHeader: function(args, callback) {
this.target.processHeader(args[0], function(ok) {
callback([ok]);
});
},
processAudio: function(args, callback) {
this.target.processAudio(args[0], function(ok) {
callback([ok]);
});
}
});
module.exports = proxy;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
var OGVLoader = __webpack_require__(3);
/**
* Web Worker wrapper for codec fun
*/
function OGVWorkerSupport(propList, handlers) {
var transferables = (function() {
var buffer = new ArrayBuffer(1024),
bytes = new Uint8Array(buffer);
try {
postMessage({
action: 'transferTest',
bytes: bytes
}, [buffer]);
if (buffer.byteLength) {
// No transferable support
return false;
} else {
return true;
}
} catch (e) {
return false;
}
})();
var self = this;
self.target = null;
var sentProps = {},
pendingEvents = [];
function copyObject(obj) {
var copy = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
copy[prop] = obj[prop];
}
}
return copy;
}
function copyAudioBuffer(data) {
if (data == null) {
return null;
} else {
// Array of Float32Arrays
var copy = [];
for (var i = 0; i < data.length; i++) {
copy[i] = new Float32Array(data[i]);
}
return copy;
}
}
function handleEvent(data) {
handlers[data.action].call(self, data.args, function(args) {
args = args || [];
// Collect and send any changed properties...
var props = {},
transfers = [];
propList.forEach(function(propName) {
var propVal = self.target[propName];
if (sentProps[propName] !== propVal) {
// Save this value for later reference...
sentProps[propName] = propVal;
if (propName == 'duration' && isNaN(propVal) && isNaN(sentProps[propName])) {
// NaN is not === itself. Nice!
// no need to update it here.
} else if (propName == 'audioBuffer') {
// Don't send the entire emscripten heap!
propVal = copyAudioBuffer(propVal);
props[propName] = propVal;
if (propVal) {
for (var i = 0; i < propVal.length; i++) {
transfers.push(propVal[i].buffer);
}
}
} else if (propName == 'frameBuffer') {
// We already extract ahead of time now,
// so transfer the small buffers.
props[propName] = propVal;
if (propVal) {
transfers.push(propVal.y.bytes.buffer);
transfers.push(propVal.u.bytes.buffer);
transfers.push(propVal.v.bytes.buffer);
}
} else {
props[propName] = propVal;
}
}
});
var out = {
action: 'callback',
callbackId: data.callbackId,
args: args,
props: props
};
if (transferables) {
postMessage(out, transfers);
} else {
postMessage(out);
}
});
}
handlers.construct = function(args, callback) {
var className = args[0],
options = args[1];
OGVLoader.loadClass(className, function(classObj) {
self.target = new classObj(options);
callback();
while (pendingEvents.length) {
handleEvent(pendingEvents.shift());
}
});
};
addEventListener('message', function workerOnMessage(event) {
var data = event.data;
if (!data || typeof data !== 'object') {
// invalid
return;
} else if (data.action == 'transferTest') {
// ignore
} else if (typeof data.action !== 'string' || typeof data.callbackId !== 'string' || typeof data.args !== 'object') {
console.log('invalid message data', data);
} else if (!(data.action in handlers)) {
console.log('invalid message action', data.action);
} else if (data.action == 'construct') {
// always handle constructor
handleEvent(data);
} else if (!self.target) {
// queue until constructed
pendingEvents.push(data);
} else {
handleEvent(data);
}
});
}
module.exports = OGVWorkerSupport;
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
var OGVVersion = ("1.4.2-20170425024925-504d7197");
(function() {
var global = this;
var scriptMap = {
OGVDemuxerOgg: 'ogv-demuxer-ogg.js',
OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js',
OGVDemuxerWebM: 'ogv-demuxer-webm.js',
OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js',
OGVDecoderAudioOpus: 'ogv-decoder-audio-opus.js',
OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js',
OGVDecoderAudioVorbis: 'ogv-decoder-audio-vorbis.js',
OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js',
OGVDecoderVideoTheora: 'ogv-decoder-video-theora.js',
OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js',
OGVDecoderVideoVP8: 'ogv-decoder-video-vp8.js',
OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js',
OGVDecoderVideoVP8MT: 'ogv-decoder-video-vp8-mt.js',
OGVDecoderVideoVP9: 'ogv-decoder-video-vp9.js',
OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js',
OGVDecoderVideoVP9MT: 'ogv-decoder-video-vp9-mt.js'
};
// @fixme make this less awful
var proxyTypes = {
OGVDecoderAudioOpus: 'audio',
OGVDecoderAudioOpusW: 'audio',
OGVDecoderAudioVorbis: 'audio',
OGVDecoderAudioVorbisW: 'audio',
OGVDecoderVideoTheora: 'video',
OGVDecoderVideoTheoraW: 'video',
OGVDecoderVideoVP8: 'video',
OGVDecoderVideoVP8W: 'video',
OGVDecoderVideoVP9: 'video',
OGVDecoderVideoVP9W: 'video'
};
var proxyInfo = {
audio: {
proxy: __webpack_require__(4),
worker: 'ogv-worker-audio.js',
},
video: {
proxy: __webpack_require__(6),
worker: 'ogv-worker-video.js'
}
}
function urlForClass(className) {
var scriptName = scriptMap[className];
if (scriptName) {
return urlForScript(scriptName);
} else {
throw new Error('asked for URL for unknown class ' + className);
}
};
function urlForScript(scriptName) {
if (scriptName) {
var base = OGVLoader.base;
if (base === undefined) {
base = '';
} else {
base += '/';
}
return base + scriptName + '?version=' + encodeURIComponent(OGVVersion);
} else {
throw new Error('asked for URL for unknown script ' + scriptName);
}
};
var scriptStatus = {},
scriptCallbacks = {};
function loadWebScript(src, callback) {
if (scriptStatus[src] == 'done') {
callback();
} else if (scriptStatus[src] == 'loading') {
scriptCallbacks[src].push(callback);
} else {
scriptStatus[src] = 'loading';
scriptCallbacks[src] = [callback];
var scriptNode = document.createElement('script');
function done(event) {
var callbacks = scriptCallbacks[src];
delete scriptCallbacks[src];
scriptStatus[src] = 'done';
callbacks.forEach(function(cb) {
cb();
});
}
scriptNode.addEventListener('load', done);
scriptNode.addEventListener('error', done);
scriptNode.src = src;
document.querySelector('head').appendChild(scriptNode);
}
}
function loadWebAssembly(src, callback) {
if (!src.match(/-wasm\.js/)) {
callback(null);
} else {
var wasmSrc = src.replace(/-wasm\.js/, '-wasm.wasm');
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
callback(xhr.response);
};
xhr.onerror = function() {
callback(null);
};
xhr.open('GET', wasmSrc);
xhr.send();
}
}
function defaultBase() {
if (typeof global.window === 'object') {
// for browser, try to autodetect
var scriptNodes = document.querySelectorAll('script'),
regex = /^(?:|(.*)\/)ogv(?:-support)?\.js(?:\?|#|$)/,
path,
matches;
for (var i = 0; i < scriptNodes.length; i++) {
path = scriptNodes[i].getAttribute('src');
if (path) {
matches = path.match(regex);
if (matches) {
return matches[1];
}
}
}
return undefined; // current dir
} else {
// for workers, assume current directory
// if not a worker, too bad.
return undefined;
}
}
var OGVLoader = {
base: defaultBase(),
loadClass: function(className, callback, options) {
options = options || {};
if (options.worker) {
this.workerProxy(className, callback);
return;
}
var url = urlForClass(className);
loadWebAssembly(url, function(wasmBinary) {
function wasmClassWrapper(options) {
options = options || {};
if (wasmBinary !== null) {
options.wasmBinary = wasmBinary;
}
return new global[className](options);
}
if (typeof global[className] === 'function') {
// already loaded!
callback(wasmClassWrapper);
} else if (typeof global.window === 'object') {
loadWebScript(url, function() {
callback(wasmClassWrapper);
});
} else if (typeof global.importScripts === 'function') {
// worker has convenient sync importScripts
global.importScripts(url);
callback(wasmClassWrapper);
}
});
},
workerProxy: function(className, callback) {
var proxyType = proxyTypes[className],
info = proxyInfo[proxyType];
if (!info) {
throw new Error('Requested worker for class with no proxy: ' + className);
}
var proxyClass = info.proxy,
workerScript = info.worker,
codecUrl = urlForScript(scriptMap[className]),
workerUrl = urlForScript(workerScript),
worker;
var construct = function(options) {
return new proxyClass(worker, className, options);
};
if (workerUrl.match(/^https?:|\/\//i)) {
// Can't load workers natively cross-domain, but if CORS
// is set up we can fetch the worker stub and the desired
// class and load them from a blob.
var getCodec,
getWorker,
codecResponse,
workerResponse,
codecLoaded = false,
workerLoaded = false,
blob;
function completionCheck() {
if ((codecLoaded == true) && (workerLoaded == true)) {
try {
blob = new Blob([codecResponse + " " + workerResponse], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(codecResponse + " " + workerResponse);
blob = blob.getBlob();
}
// Create the web worker
worker = new Worker(URL.createObjectURL(blob));
callback(construct);
}
}
// Load the codec
getCodec = new XMLHttpRequest();
getCodec.open("GET", codecUrl, true);
getCodec.onreadystatechange = function() {
if(getCodec.readyState == 4 && getCodec.status == 200) {
codecResponse = getCodec.responseText;
// Update the codec response loaded flag
codecLoaded = true;
completionCheck();
}
};
getCodec.send();
// Load the worker
getWorker = new XMLHttpRequest();
getWorker.open("GET", workerUrl, true);
getWorker.onreadystatechange = function() {
if(getWorker.readyState == 4 && getWorker.status == 200) {
workerResponse = getWorker.responseText;
// Update the worker response loaded flag
workerLoaded = true;
completionCheck();
}
};
getWorker.send();
} else {
// Local URL; load it directly for simplicity.
worker = new Worker(workerUrl);
callback(construct);
}
}
};
module.exports = OGVLoader;
})();
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
var OGVProxyClass = __webpack_require__(5);
var OGVDecoderAudioProxy = OGVProxyClass({
loadedMetadata: false,
audioFormat: null,
audioBuffer: null,
cpuTime: 0
}, {
init: function(callback) {
this.proxy('init', [], callback);
},
processHeader: function(data, callback) {
this.proxy('processHeader', [data], callback, [data]);
},
processAudio: function(data, callback) {
this.proxy('processAudio', [data], callback, [data]);
},
close: function() {
this.terminate();
}
});
module.exports = OGVDecoderAudioProxy;
/***/ }),
/* 5 */
/***/ (function(module, exports) {
/**
* Proxy object for web worker interface for codec classes.
*
* Used by the high-level player interface.
*
* @author Brion Vibber <brion@pobox.com>
* @copyright 2015
* @license MIT-style
*/
function OGVProxyClass(initialProps, methods) {
return function(worker, className, options) {
options = options || {};
var self = this;
var transferables = (function() {
var buffer = new ArrayBuffer(1024),
bytes = new Uint8Array(buffer);
try {
worker.postMessage({
action: 'transferTest',
bytes: bytes
}, [buffer]);
if (buffer.byteLength) {
// No transferable support
return false;
} else {
return true;
}
} catch (e) {
return false;
}
})();
// Set up proxied property getters
var props = {};
for (var iPropName in initialProps) {
if (initialProps.hasOwnProperty(iPropName)) {
(function(propName) {
props[propName] = initialProps[propName];
Object.defineProperty(self, propName, {
get: function getProperty() {
return props[propName];
}
});
})(iPropName);
}
}
// Current player wants to avoid async confusion.
var processingQueue = 0;
Object.defineProperty(self, 'processing', {
get: function() {
return (processingQueue > 0);
}
});
// Set up proxied methods
for (var method in methods) {
if (methods.hasOwnProperty(method)) {
self[method] = methods[method];
}
}
// And some infrastructure!
var messageCount = 0,
pendingCallbacks = {};
this.proxy = function(action, args, callback, transfers) {
if (!worker) {
throw 'Tried to call "' + action + '" method on closed proxy object';
}
var callbackId = 'callback-' + (++messageCount) + '-' + action;
if (callback) {
pendingCallbacks[callbackId] = callback;
}
var out = {
'action': action,
'callbackId': callbackId,
'args': args || []
};
processingQueue++;
if (transferables) {
worker.postMessage(out, transfers || []);
} else {
worker.postMessage(out);
}
};
this.terminate = function() {
if (worker) {
worker.terminate();
worker = null;
processingQueue = 0;
pendingCallbacks = {};
}
};
worker.addEventListener('message', function proxyOnMessage(event) {
processingQueue--;
if (event.data.action !== 'callback') {
// ignore
return;
}
var data = event.data,
callbackId = data.callbackId,
args = data.args,
callback = pendingCallbacks[callbackId];
// Save any updated properties returned to us...
if (data.props) {
for (var propName in data.props) {
if (data.props.hasOwnProperty(propName)) {
props[propName] = data.props[propName];
}
}
}
if (callback) {
delete pendingCallbacks[callbackId];
callback.apply(this, args);
}
});
// Tell the proxy to load and initialize the appropriate class
self.proxy('construct', [className, options], function() {});
return self;
};
}
module.exports = OGVProxyClass;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
var OGVProxyClass = __webpack_require__(5);
var OGVDecoderVideoProxy = OGVProxyClass({
loadedMetadata: false,
videoFormat: null,
frameBuffer: null,
cpuTime: 0
}, {
init: function(callback) {
this.proxy('init', [], callback);
},
processHeader: function(data, callback) {
this.proxy('processHeader', [data], callback, [data]);
},
processFrame: function(data, callback) {
this.proxy('processFrame', [data], callback, [data]);
},
close: function() {
this.terminate();
}
});
module.exports = OGVDecoderVideoProxy;
/***/ })
/******/ ]);

700
app/vendor/ogv.js/ogv-worker-video.js vendored

@ -0,0 +1,700 @@ @@ -0,0 +1,700 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var OGVWorkerSupport = __webpack_require__(2);
var proxy = new OGVWorkerSupport([
'loadedMetadata',
'videoFormat',
'frameBuffer',
'cpuTime'
], {
init: function(args, callback) {
this.target.init(callback);
},
processHeader: function(args, callback) {
this.target.processHeader(args[0], function(ok) {
callback([ok]);
});
},
processFrame: function(args, callback) {
this.target.processFrame(args[0], function(ok) {
callback([ok]);
});
}
});
module.exports = proxy;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
var OGVLoader = __webpack_require__(3);
/**
* Web Worker wrapper for codec fun
*/
function OGVWorkerSupport(propList, handlers) {
var transferables = (function() {
var buffer = new ArrayBuffer(1024),
bytes = new Uint8Array(buffer);
try {
postMessage({
action: 'transferTest',
bytes: bytes
}, [buffer]);
if (buffer.byteLength) {
// No transferable support
return false;
} else {
return true;
}
} catch (e) {
return false;
}
})();
var self = this;
self.target = null;
var sentProps = {},
pendingEvents = [];
function copyObject(obj) {
var copy = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
copy[prop] = obj[prop];
}
}
return copy;
}
function copyAudioBuffer(data) {
if (data == null) {
return null;
} else {
// Array of Float32Arrays
var copy = [];
for (var i = 0; i < data.length; i++) {
copy[i] = new Float32Array(data[i]);
}
return copy;
}
}
function handleEvent(data) {
handlers[data.action].call(self, data.args, function(args) {
args = args || [];
// Collect and send any changed properties...
var props = {},
transfers = [];
propList.forEach(function(propName) {
var propVal = self.target[propName];
if (sentProps[propName] !== propVal) {
// Save this value for later reference...
sentProps[propName] = propVal;
if (propName == 'duration' && isNaN(propVal) && isNaN(sentProps[propName])) {
// NaN is not === itself. Nice!
// no need to update it here.
} else if (propName == 'audioBuffer') {
// Don't send the entire emscripten heap!
propVal = copyAudioBuffer(propVal);
props[propName] = propVal;
if (propVal) {
for (var i = 0; i < propVal.length; i++) {
transfers.push(propVal[i].buffer);
}
}
} else if (propName == 'frameBuffer') {
// We already extract ahead of time now,
// so transfer the small buffers.
props[propName] = propVal;
if (propVal) {
transfers.push(propVal.y.bytes.buffer);
transfers.push(propVal.u.bytes.buffer);
transfers.push(propVal.v.bytes.buffer);
}
} else {
props[propName] = propVal;
}
}
});
var out = {
action: 'callback',
callbackId: data.callbackId,
args: args,
props: props
};
if (transferables) {
postMessage(out, transfers);
} else {
postMessage(out);
}
});
}
handlers.construct = function(args, callback) {
var className = args[0],
options = args[1];
OGVLoader.loadClass(className, function(classObj) {
self.target = new classObj(options);
callback();
while (pendingEvents.length) {
handleEvent(pendingEvents.shift());
}
});
};
addEventListener('message', function workerOnMessage(event) {
var data = event.data;
if (!data || typeof data !== 'object') {
// invalid
return;
} else if (data.action == 'transferTest') {
// ignore
} else if (typeof data.action !== 'string' || typeof data.callbackId !== 'string' || typeof data.args !== 'object') {
console.log('invalid message data', data);
} else if (!(data.action in handlers)) {
console.log('invalid message action', data.action);
} else if (data.action == 'construct') {
// always handle constructor
handleEvent(data);
} else if (!self.target) {
// queue until constructed
pendingEvents.push(data);
} else {
handleEvent(data);
}
});
}
module.exports = OGVWorkerSupport;
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
var OGVVersion = ("1.4.2-20170425024925-504d7197");
(function() {
var global = this;
var scriptMap = {
OGVDemuxerOgg: 'ogv-demuxer-ogg.js',
OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js',
OGVDemuxerWebM: 'ogv-demuxer-webm.js',
OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js',
OGVDecoderAudioOpus: 'ogv-decoder-audio-opus.js',
OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js',
OGVDecoderAudioVorbis: 'ogv-decoder-audio-vorbis.js',
OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js',
OGVDecoderVideoTheora: 'ogv-decoder-video-theora.js',
OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js',
OGVDecoderVideoVP8: 'ogv-decoder-video-vp8.js',
OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js',
OGVDecoderVideoVP8MT: 'ogv-decoder-video-vp8-mt.js',
OGVDecoderVideoVP9: 'ogv-decoder-video-vp9.js',
OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js',
OGVDecoderVideoVP9MT: 'ogv-decoder-video-vp9-mt.js'
};
// @fixme make this less awful
var proxyTypes = {
OGVDecoderAudioOpus: 'audio',
OGVDecoderAudioOpusW: 'audio',
OGVDecoderAudioVorbis: 'audio',
OGVDecoderAudioVorbisW: 'audio',
OGVDecoderVideoTheora: 'video',
OGVDecoderVideoTheoraW: 'video',
OGVDecoderVideoVP8: 'video',
OGVDecoderVideoVP8W: 'video',
OGVDecoderVideoVP9: 'video',
OGVDecoderVideoVP9W: 'video'
};
var proxyInfo = {
audio: {
proxy: __webpack_require__(4),
worker: 'ogv-worker-audio.js',
},
video: {
proxy: __webpack_require__(6),
worker: 'ogv-worker-video.js'
}
}
function urlForClass(className) {
var scriptName = scriptMap[className];
if (scriptName) {
return urlForScript(scriptName);
} else {
throw new Error('asked for URL for unknown class ' + className);
}
};
function urlForScript(scriptName) {
if (scriptName) {
var base = OGVLoader.base;
if (base === undefined) {
base = '';
} else {
base += '/';
}
return base + scriptName + '?version=' + encodeURIComponent(OGVVersion);
} else {
throw new Error('asked for URL for unknown script ' + scriptName);
}
};
var scriptStatus = {},
scriptCallbacks = {};
function loadWebScript(src, callback) {
if (scriptStatus[src] == 'done') {
callback();
} else if (scriptStatus[src] == 'loading') {
scriptCallbacks[src].push(callback);
} else {
scriptStatus[src] = 'loading';
scriptCallbacks[src] = [callback];
var scriptNode = document.createElement('script');
function done(event) {
var callbacks = scriptCallbacks[src];
delete scriptCallbacks[src];
scriptStatus[src] = 'done';
callbacks.forEach(function(cb) {
cb();
});
}
scriptNode.addEventListener('load', done);
scriptNode.addEventListener('error', done);
scriptNode.src = src;
document.querySelector('head').appendChild(scriptNode);
}
}
function loadWebAssembly(src, callback) {
if (!src.match(/-wasm\.js/)) {
callback(null);
} else {
var wasmSrc = src.replace(/-wasm\.js/, '-wasm.wasm');
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
callback(xhr.response);
};
xhr.onerror = function() {
callback(null);
};
xhr.open('GET', wasmSrc);
xhr.send();
}
}
function defaultBase() {
if (typeof global.window === 'object') {
// for browser, try to autodetect
var scriptNodes = document.querySelectorAll('script'),
regex = /^(?:|(.*)\/)ogv(?:-support)?\.js(?:\?|#|$)/,
path,
matches;
for (var i = 0; i < scriptNodes.length; i++) {
path = scriptNodes[i].getAttribute('src');
if (path) {
matches = path.match(regex);
if (matches) {
return matches[1];
}
}
}
return undefined; // current dir
} else {
// for workers, assume current directory
// if not a worker, too bad.
return undefined;
}
}
var OGVLoader = {
base: defaultBase(),
loadClass: function(className, callback, options) {
options = options || {};
if (options.worker) {
this.workerProxy(className, callback);
return;
}
var url = urlForClass(className);
loadWebAssembly(url, function(wasmBinary) {
function wasmClassWrapper(options) {
options = options || {};
if (wasmBinary !== null) {
options.wasmBinary = wasmBinary;
}
return new global[className](options);
}
if (typeof global[className] === 'function') {
// already loaded!
callback(wasmClassWrapper);
} else if (typeof global.window === 'object') {
loadWebScript(url, function() {
callback(wasmClassWrapper);
});
} else if (typeof global.importScripts === 'function') {
// worker has convenient sync importScripts
global.importScripts(url);
callback(wasmClassWrapper);
}
});
},
workerProxy: function(className, callback) {
var proxyType = proxyTypes[className],
info = proxyInfo[proxyType];
if (!info) {
throw new Error('Requested worker for class with no proxy: ' + className);
}
var proxyClass = info.proxy,
workerScript = info.worker,
codecUrl = urlForScript(scriptMap[className]),
workerUrl = urlForScript(workerScript),
worker;
var construct = function(options) {
return new proxyClass(worker, className, options);
};
if (workerUrl.match(/^https?:|\/\//i)) {
// Can't load workers natively cross-domain, but if CORS
// is set up we can fetch the worker stub and the desired
// class and load them from a blob.
var getCodec,
getWorker,
codecResponse,
workerResponse,
codecLoaded = false,
workerLoaded = false,
blob;
function completionCheck() {
if ((codecLoaded == true) && (workerLoaded == true)) {
try {
blob = new Blob([codecResponse + " " + workerResponse], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(codecResponse + " " + workerResponse);
blob = blob.getBlob();
}
// Create the web worker
worker = new Worker(URL.createObjectURL(blob));
callback(construct);
}
}
// Load the codec
getCodec = new XMLHttpRequest();
getCodec.open("GET", codecUrl, true);
getCodec.onreadystatechange = function() {
if(getCodec.readyState == 4 && getCodec.status == 200) {
codecResponse = getCodec.responseText;
// Update the codec response loaded flag
codecLoaded = true;
completionCheck();
}
};
getCodec.send();
// Load the worker
getWorker = new XMLHttpRequest();
getWorker.open("GET", workerUrl, true);
getWorker.onreadystatechange = function() {
if(getWorker.readyState == 4 && getWorker.status == 200) {
workerResponse = getWorker.responseText;
// Update the worker response loaded flag
workerLoaded = true;
completionCheck();
}
};
getWorker.send();
} else {
// Local URL; load it directly for simplicity.
worker = new Worker(workerUrl);
callback(construct);
}
}
};
module.exports = OGVLoader;
})();
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
var OGVProxyClass = __webpack_require__(5);
var OGVDecoderAudioProxy = OGVProxyClass({
loadedMetadata: false,
audioFormat: null,
audioBuffer: null,
cpuTime: 0
}, {
init: function(callback) {
this.proxy('init', [], callback);
},
processHeader: function(data, callback) {
this.proxy('processHeader', [data], callback, [data]);
},
processAudio: function(data, callback) {
this.proxy('processAudio', [data], callback, [data]);
},
close: function() {
this.terminate();
}
});
module.exports = OGVDecoderAudioProxy;
/***/ }),
/* 5 */
/***/ (function(module, exports) {
/**
* Proxy object for web worker interface for codec classes.
*
* Used by the high-level player interface.
*
* @author Brion Vibber <brion@pobox.com>
* @copyright 2015
* @license MIT-style
*/
function OGVProxyClass(initialProps, methods) {
return function(worker, className, options) {
options = options || {};
var self = this;
var transferables = (function() {
var buffer = new ArrayBuffer(1024),
bytes = new Uint8Array(buffer);
try {
worker.postMessage({
action: 'transferTest',
bytes: bytes
}, [buffer]);
if (buffer.byteLength) {
// No transferable support
return false;
} else {
return true;
}
} catch (e) {
return false;
}
})();
// Set up proxied property getters
var props = {};
for (var iPropName in initialProps) {
if (initialProps.hasOwnProperty(iPropName)) {
(function(propName) {
props[propName] = initialProps[propName];
Object.defineProperty(self, propName, {
get: function getProperty() {
return props[propName];
}
});
})(iPropName);
}
}
// Current player wants to avoid async confusion.
var processingQueue = 0;
Object.defineProperty(self, 'processing', {
get: function() {
return (processingQueue > 0);
}
});
// Set up proxied methods
for (var method in methods) {
if (methods.hasOwnProperty(method)) {
self[method] = methods[method];
}
}
// And some infrastructure!
var messageCount = 0,
pendingCallbacks = {};
this.proxy = function(action, args, callback, transfers) {
if (!worker) {
throw 'Tried to call "' + action + '" method on closed proxy object';
}
var callbackId = 'callback-' + (++messageCount) + '-' + action;
if (callback) {
pendingCallbacks[callbackId] = callback;
}
var out = {
'action': action,
'callbackId': callbackId,
'args': args || []
};
processingQueue++;
if (transferables) {
worker.postMessage(out, transfers || []);
} else {
worker.postMessage(out);
}
};
this.terminate = function() {
if (worker) {
worker.terminate();
worker = null;
processingQueue = 0;
pendingCallbacks = {};
}
};
worker.addEventListener('message', function proxyOnMessage(event) {
processingQueue--;
if (event.data.action !== 'callback') {
// ignore
return;
}
var data = event.data,
callbackId = data.callbackId,
args = data.args,
callback = pendingCallbacks[callbackId];
// Save any updated properties returned to us...
if (data.props) {
for (var propName in data.props) {
if (data.props.hasOwnProperty(propName)) {
props[propName] = data.props[propName];
}
}
}
if (callback) {
delete pendingCallbacks[callbackId];
callback.apply(this, args);
}
});
// Tell the proxy to load and initialize the appropriate class
self.proxy('construct', [className, options], function() {});
return self;
};
}
module.exports = OGVProxyClass;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
var OGVProxyClass = __webpack_require__(5);
var OGVDecoderVideoProxy = OGVProxyClass({
loadedMetadata: false,
videoFormat: null,
frameBuffer: null,
cpuTime: 0
}, {
init: function(callback) {
this.proxy('init', [], callback);
},
processHeader: function(data, callback) {
this.proxy('processHeader', [data], callback, [data]);
},
processFrame: function(data, callback) {
this.proxy('processFrame', [data], callback, [data]);
},
close: function() {
this.terminate();
}
});
module.exports = OGVDecoderVideoProxy;
/***/ })
/******/ ]);

9663
app/vendor/ogv.js/ogv.js vendored

File diff suppressed because it is too large Load Diff

103
app/vendor/ogv.js/pthread-main.js vendored

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
// Pthread Web Worker startup routine:
// This is the entry point file that is loaded first by each Web Worker
// that executes pthreads on the Emscripten application.
// Thread-local, communicated via globals:
var threadInfoStruct = 0; // Info area for this thread in Emscripten HEAP (shared). If zero, this worker is not currently hosting an executing pthread.
var selfThreadId = 0; // The ID of this thread. 0 if not hosting a pthread.
var parentThreadId = 0; // The ID of the parent pthread that launched this thread.
// Send the pthreads mode and other params in through Module object settings
var Module = {
ENVIRONMENT: 'PTHREAD'
};
// Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091
// Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print.
function threadPrint() {
var text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
}
function threadPrintErr() {
var text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
}
function threadAlert() {
var text = Array.prototype.slice.call(arguments).join(' ');
postMessage({cmd: 'alert', text: text, threadId: selfThreadId});
}
Module['print'] = threadPrint;
Module['printErr'] = threadPrintErr;
this.alert = threadAlert;
// If modularized, we can't reuse the module's assert() function.
function assert(condition, text) {
if (!condition) {
abort('Assertion failed: ' + text);
}
}
this.onmessage = function(e) {
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
// Initialize the thread-local field(s):
Module['tempDoublePtr'] = e.data.tempDoublePtr;
// Initialize the global "process"-wide fields:
Module['buffer'] = e.data.buffer;
Module['TOTAL_MEMORY'] = e.data.TOTAL_MEMORY;
Module['STATICTOP'] = e.data.STATICTOP;
Module['DYNAMIC_BASE'] = e.data.DYNAMIC_BASE;
Module['DYNAMICTOP_PTR'] = e.data.DYNAMICTOP_PTR;
Module['pthreadWorkerInit'] = e.data.PthreadWorkerInit;
importScripts(e.data.url);
if (e.data.modularize) {
// Feed input options into the modularized constructor...
// 'this' is the Worker, which is also global scope.
Module = new this[e.data.moduleExportName](Module);
}
if (typeof FS !== 'undefined') FS.createStandardStreams();
postMessage({ cmd: 'loaded' });
} else if (e.data.cmd === 'objectTransfer') {
Module.PThread.receiveObjectTransfer(e.data);
} else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point.
threadInfoStruct = e.data.threadInfoStruct;
Module.PThread.registerPthreadPtr(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out.
assert(threadInfoStruct);
selfThreadId = e.data.selfThreadId;
parentThreadId = e.data.parentThreadId;
assert(selfThreadId);
assert(parentThreadId);
Module.PThread.setStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize);
var result = 0;
Module.PThread.receiveObjectTransfer(e.data);
Module.PThread.setThreadStatus(threadInfoStruct, 1/*EM_THREAD_STATUS_RUNNING*/);
try {
Module.PThread.runThreadFunc(e.data.start_routine, e.data.arg);
} catch(e) {
if (e === 'Canceled!') {
Module.PThread.threadCancel();
return;
} else {
Atomics.store(Module.HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
Atomics.store(Module.HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
Module.PThread.wakeAllThreads();
throw e;
}
}
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
// (This is a no-op if explicit pthread_exit() had been called prior.)
if (!Module['noExitRuntime']) Module.PThread.threadExit(result);
else console.log('pthread noExitRuntime: not quitting.');
} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
if (threadInfoStruct && Module.PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) {
Module.PThread.threadCancel();
}
} else {
// Module['printErr']('pthread-main.js received unknown command ' + e.data.cmd);
// console.error(e.data)
}
}

18
app/vendor/recorderjs/encoder_worker.js vendored

File diff suppressed because one or more lines are too long

245
app/vendor/recorderjs/recorder.js vendored

@ -0,0 +1,245 @@ @@ -0,0 +1,245 @@
"use strict";
var root = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this;
(function( global ) {
var Recorder = function( config ){
var that = this;
if ( !Recorder.isRecordingSupported() ) {
throw new Error("Recording is not supported in this browser");
}
this.state = "inactive";
this.eventTarget = global.document.createDocumentFragment();
this.audioContext = new global.AudioContext();
this.monitorNode = this.audioContext.createGain();
this.config = config = config || {};
this.config.command = "init";
this.config.bufferLength = config.bufferLength || 4096;
this.config.monitorGain = config.monitorGain || 0;
this.config.numberOfChannels = config.numberOfChannels || 1;
this.config.originalSampleRate = this.audioContext.sampleRate;
this.config.encoderSampleRate = config.encoderSampleRate || 48000;
this.config.encoderPath = config.encoderPath || 'encoderWorker.min.js';
this.config.streamPages = config.streamPages || false;
this.config.leaveStreamOpen = config.leaveStreamOpen || false;
this.config.maxBuffersPerPage = config.maxBuffersPerPage || 40;
this.config.encoderApplication = config.encoderApplication || 2049;
this.config.encoderFrameSize = config.encoderFrameSize || 20;
this.config.resampleQuality = config.resampleQuality || 3;
this.config.streamOptions = config.streamOptions || {
optional: [],
mandatory: {
googEchoCancellation: false,
googAutoGainControl: false,
googNoiseSuppression: false,
googHighpassFilter: false
}
};
this.setMonitorGain( this.config.monitorGain );
this.scriptProcessorNode = this.audioContext.createScriptProcessor( this.config.bufferLength, this.config.numberOfChannels, this.config.numberOfChannels );
this.scriptProcessorNode.onaudioprocess = function( e ){
that.encodeBuffers( e.inputBuffer );
};
};
Recorder.isRecordingSupported = function(){
return global.AudioContext && global.navigator && ( global.navigator.getUserMedia || ( global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia ) );
};
Recorder.prototype.addEventListener = function( type, listener, useCapture ){
this.eventTarget.addEventListener( type, listener, useCapture );
};
Recorder.prototype.clearStream = function() {
if ( this.stream ) {
if ( this.stream.getTracks ) {
this.stream.getTracks().forEach(function ( track ) {
track.stop();
});
}
else {
this.stream.stop();
}
delete this.stream;
}
};
Recorder.prototype.encodeBuffers = function( inputBuffer ){
if ( this.state === "recording" ) {
var buffers = [];
for ( var i = 0; i < inputBuffer.numberOfChannels; i++ ) {
buffers[i] = inputBuffer.getChannelData(i);
}
this.encoder.postMessage({
command: "encode",
buffers: buffers
});
}
};
Recorder.prototype.initStream = function(){
var that = this;
var onStreamInit = function( stream ){
that.stream = stream;
that.sourceNode = that.audioContext.createMediaStreamSource( stream );
that.sourceNode.connect( that.scriptProcessorNode );
that.sourceNode.connect( that.monitorNode );
that.eventTarget.dispatchEvent( new global.Event( "streamReady" ) );
return stream;
}
var onStreamError = function( e ){
that.eventTarget.dispatchEvent( new global.ErrorEvent( "streamError", { error: e } ) );
}
var constraints = { audio : this.config.streamOptions };
if ( this.stream ) {
this.eventTarget.dispatchEvent( new global.Event( "streamReady" ) );
return global.Promise.resolve( this.stream );
}
if ( global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia ) {
return global.navigator.mediaDevices.getUserMedia( constraints ).then( onStreamInit, onStreamError );
}
if ( global.navigator.getUserMedia ) {
return new global.Promise( function( resolve, reject ) {
global.navigator.getUserMedia( constraints, resolve, reject );
}).then( onStreamInit, onStreamError );
}
};
Recorder.prototype.pause = function(){
if ( this.state === "recording" ){
this.state = "paused";
this.eventTarget.dispatchEvent( new global.Event( 'pause' ) );
}
};
Recorder.prototype.removeEventListener = function( type, listener, useCapture ){
this.eventTarget.removeEventListener( type, listener, useCapture );
};
Recorder.prototype.resume = function() {
if ( this.state === "paused" ) {
this.state = "recording";
this.eventTarget.dispatchEvent( new global.Event( 'resume' ) );
}
};
Recorder.prototype.setMonitorGain = function( gain ){
this.monitorNode.gain.value = gain;
};
Recorder.prototype.start = function(){
if ( this.state === "inactive" && this.stream ) {
var that = this;
this.encoder = new global.Worker( this.config.encoderPath );
if (this.config.streamPages){
this.encoder.addEventListener( "message", function( e ) {
that.streamPage( e.data );
});
}
else {
this.recordedPages = [];
this.totalLength = 0;
this.encoder.addEventListener( "message", function( e ) {
that.storePage( e.data );
});
}
// First buffer can contain old data. Don't encode it.
this.encodeBuffers = function(){
delete this.encodeBuffers;
};
this.state = "recording";
this.monitorNode.connect( this.audioContext.destination );
this.scriptProcessorNode.connect( this.audioContext.destination );
this.eventTarget.dispatchEvent( new global.Event( 'start' ) );
this.encoder.postMessage( this.config );
}
};
Recorder.prototype.stop = function(){
if ( this.state !== "inactive" ) {
this.state = "inactive";
this.monitorNode.disconnect();
this.scriptProcessorNode.disconnect();
if ( !this.config.leaveStreamOpen ) {
this.clearStream();
}
this.audioContext.close();
this.audioContext = null;
this.encoder.postMessage({ command: "done" });
}
};
Recorder.prototype.storePage = function( page ) {
if ( page === null ) {
var outputData = new Uint8Array( this.totalLength );
var outputIndex = 0;
for ( var i = 0; i < this.recordedPages.length; i++ ) {
outputData.set( this.recordedPages[i], outputIndex );
outputIndex += this.recordedPages[i].length;
}
this.eventTarget.dispatchEvent( new global.CustomEvent( 'dataAvailable', {
detail: outputData
}));
this.recordedPages = [];
this.eventTarget.dispatchEvent( new global.Event( 'stop' ) );
}
else {
this.recordedPages.push( page );
this.totalLength += page.length;
}
};
Recorder.prototype.streamPage = function( page ) {
if ( page === null ) {
this.eventTarget.dispatchEvent( new global.Event( 'stop' ) );
}
else {
this.eventTarget.dispatchEvent( new global.CustomEvent( 'dataAvailable', {
detail: page
}));
}
};
// Exports
global.Recorder = Recorder;
if ( typeof define === 'function' && define.amd ) {
define( [], function() {
return Recorder;
});
}
else if ( typeof module == 'object' && module.exports ) {
module.exports = Recorder;
}
})(root);

1
app/vendor/recorderjs/recorder.min.js vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
"use strict";var root="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||this;!function(e){var t=function(n){var i=this;if(!t.isRecordingSupported())throw new Error("Recording is not supported in this browser");this.state="inactive",this.eventTarget=e.document.createDocumentFragment(),this.audioContext=new e.AudioContext,this.monitorNode=this.audioContext.createGain(),this.config=n=n||{},this.config.command="init",this.config.bufferLength=n.bufferLength||4096,this.config.monitorGain=n.monitorGain||0,this.config.numberOfChannels=n.numberOfChannels||1,this.config.originalSampleRate=this.audioContext.sampleRate,this.config.encoderSampleRate=n.encoderSampleRate||48e3,this.config.encoderPath=n.encoderPath||"encoderWorker.min.js",this.config.streamPages=n.streamPages||!1,this.config.leaveStreamOpen=n.leaveStreamOpen||!1,this.config.maxBuffersPerPage=n.maxBuffersPerPage||40,this.config.encoderApplication=n.encoderApplication||2049,this.config.encoderFrameSize=n.encoderFrameSize||20,this.config.resampleQuality=n.resampleQuality||3,this.config.streamOptions=n.streamOptions||{optional:[],mandatory:{googEchoCancellation:!1,googAutoGainControl:!1,googNoiseSuppression:!1,googHighpassFilter:!1}},this.setMonitorGain(this.config.monitorGain),this.scriptProcessorNode=this.audioContext.createScriptProcessor(this.config.bufferLength,this.config.numberOfChannels,this.config.numberOfChannels),this.scriptProcessorNode.onaudioprocess=function(e){i.encodeBuffers(e.inputBuffer)}};t.isRecordingSupported=function(){return e.AudioContext&&e.navigator&&(e.navigator.getUserMedia||e.navigator.mediaDevices&&e.navigator.mediaDevices.getUserMedia)},t.prototype.addEventListener=function(e,t,n){this.eventTarget.addEventListener(e,t,n)},t.prototype.clearStream=function(){this.stream&&(this.stream.getTracks?this.stream.getTracks().forEach(function(e){e.stop()}):this.stream.stop(),delete this.stream)},t.prototype.encodeBuffers=function(e){if("recording"===this.state){for(var t=[],n=0;n<e.numberOfChannels;n++)t[n]=e.getChannelData(n);this.encoder.postMessage({command:"encode",buffers:t})}},t.prototype.initStream=function(){var t=this,n=function(n){return t.stream=n,t.sourceNode=t.audioContext.createMediaStreamSource(n),t.sourceNode.connect(t.scriptProcessorNode),t.sourceNode.connect(t.monitorNode),t.eventTarget.dispatchEvent(new e.Event("streamReady")),n},i=function(n){t.eventTarget.dispatchEvent(new e.ErrorEvent("streamError",{error:n}))},o={audio:this.config.streamOptions};return this.stream?(this.eventTarget.dispatchEvent(new e.Event("streamReady")),e.Promise.resolve(this.stream)):e.navigator.mediaDevices&&e.navigator.mediaDevices.getUserMedia?e.navigator.mediaDevices.getUserMedia(o).then(n,i):e.navigator.getUserMedia?new e.Promise(function(t,n){e.navigator.getUserMedia(o,t,n)}).then(n,i):void 0},t.prototype.pause=function(){"recording"===this.state&&(this.state="paused",this.eventTarget.dispatchEvent(new e.Event("pause")))},t.prototype.removeEventListener=function(e,t,n){this.eventTarget.removeEventListener(e,t,n)},t.prototype.resume=function(){"paused"===this.state&&(this.state="recording",this.eventTarget.dispatchEvent(new e.Event("resume")))},t.prototype.setMonitorGain=function(e){this.monitorNode.gain.value=e},t.prototype.start=function(){if("inactive"===this.state&&this.stream){var t=this;this.encoder=new e.Worker(this.config.encoderPath),this.config.streamPages?this.encoder.addEventListener("message",function(e){t.streamPage(e.data)}):(this.recordedPages=[],this.totalLength=0,this.encoder.addEventListener("message",function(e){t.storePage(e.data)})),this.encodeBuffers=function(){delete this.encodeBuffers},this.state="recording",this.monitorNode.connect(this.audioContext.destination),this.scriptProcessorNode.connect(this.audioContext.destination),this.eventTarget.dispatchEvent(new e.Event("start")),this.encoder.postMessage(this.config)}},t.prototype.stop=function(){"inactive"!==this.state&&(this.state="inactive",this.monitorNode.disconnect(),this.scriptProcessorNode.disconnect(),this.config.leaveStreamOpen||this.clearStream(),this.encoder.postMessage({command:"done"}))},t.prototype.storePage=function(t){if(null===t){for(var n=new Uint8Array(this.totalLength),i=0,o=0;o<this.recordedPages.length;o++)n.set(this.recordedPages[o],i),i+=this.recordedPages[o].length;this.eventTarget.dispatchEvent(new e.CustomEvent("dataAvailable",{detail:n})),this.recordedPages=[],this.eventTarget.dispatchEvent(new e.Event("stop"))}else this.recordedPages.push(t),this.totalLength+=t.length},t.prototype.streamPage=function(t){null===t?this.eventTarget.dispatchEvent(new e.Event("stop")):this.eventTarget.dispatchEvent(new e.CustomEvent("dataAvailable",{detail:t}))},e.Recorder=t,"function"==typeof define&&define.amd?define([],function(){return t}):"object"==typeof module&&module.exports&&(module.exports=t)}(root);

2
app/webogram.appcache

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
CACHE MANIFEST
# 67
# 77
NETWORK:
*

7
gulpfile.js

@ -190,7 +190,9 @@ function writeServiceWorkerFile (rootDir, handleFetch, callback) { @@ -190,7 +190,9 @@ function writeServiceWorkerFile (rootDir, handleFetch, callback) {
staticFileGlobs: fileGlobs,
stripPrefix: './' + rootDir + '/',
importScripts: ['js/lib/push_worker.js'],
verbose: true
verbose: true,
maximumFileSizeToCacheInBytes: 3004152, // about 3MB, default is "2097152" 2MB,
navigateFallback: "index.html",
}
swPrecache.write(path.join(rootDir, 'service_worker.js'), config, callback)
}
@ -202,7 +204,8 @@ gulp.task('generate-service-worker', ['build'], function (callback) { @@ -202,7 +204,8 @@ gulp.task('generate-service-worker', ['build'], function (callback) {
gulp.task('add-appcache-manifest', ['build'], function () {
return gulp.src(fileGlobs)
.pipe($.manifest({
timestamp: true,
timestamp: false,
hash: true,
network: ['http://*', 'https://*', '*'],
filename: 'webogram.appcache',
exclude: ['webogram.appcache', 'app.manifest']

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

Loading…
Cancel
Save