Merge branch 'TitanNano-voice_recorder'
This commit is contained in:
commit
4e0ad438d3
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -78,6 +78,8 @@
|
||||
<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>
|
||||
<script type="text/javascript" src="js/lib/tl_utils.js"></script>
|
||||
|
@ -2321,6 +2321,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
|
||||
send: submitMessage,
|
||||
replyClear: replyClear,
|
||||
fwdsClear: fwdsClear,
|
||||
toggleSlash: toggleSlash,
|
||||
replyKeyboardToggle: replyKeyboardToggle,
|
||||
type: 'new'
|
||||
}
|
||||
$scope.mentions = {}
|
||||
@ -2347,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
|
||||
|
@ -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,9 +1550,11 @@ 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: '=',
|
||||
mentions: '=',
|
||||
@ -1558,19 +1563,31 @@ angular.module('myApp.directives', ['myApp.filters'])
|
||||
}
|
||||
|
||||
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
|
||||
var voiceRecorderPromise = 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,126 @@ 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' : 'mouseup'
|
||||
}
|
||||
$(voiceRecordBtn).on(voiceRecordEvents.start, function(event) {
|
||||
if ($scope.voiceRecorder.processing) {
|
||||
return
|
||||
}
|
||||
|
||||
voiceRecorderPromise = null
|
||||
|
||||
voiceRecorder = new Recorder({
|
||||
monitorGain: 0,
|
||||
numberOfChannels: 1,
|
||||
bitRate: 64000,
|
||||
encoderSampleRate: 48000,
|
||||
encoderPath: 'vendor/recorderjs/encoder_worker.js'
|
||||
})
|
||||
|
||||
voiceRecorder.addEventListener('start', function(e) {
|
||||
var startTime = tsNow(true)
|
||||
|
||||
voiceRecordDurationInterval = $interval(function() {
|
||||
$scope.voiceRecorder.duration = tsNow(true) - startTime
|
||||
}, 1000)
|
||||
|
||||
$scope.$apply(function() {
|
||||
$scope.voiceRecorder.recording = true
|
||||
})
|
||||
|
||||
console.warn(dT(), 'recording now!')
|
||||
})
|
||||
|
||||
voiceRecorder.addEventListener('streamReady', function(e) {
|
||||
voiceRecorder.start()
|
||||
})
|
||||
|
||||
voiceRecorder.initStream()
|
||||
|
||||
var curHover = false
|
||||
var curBoundaries = {}
|
||||
|
||||
var updateVoiceHoverBoundaries = function () {
|
||||
var boundElement = $('.im_bottom_panel_wrap')
|
||||
// console.warn(dT(), 'bound', boundElement[0])
|
||||
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
|
||||
// console.log('event', voiceRecordTouch, 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) {
|
||||
console.warn(dT(), 'change hover', isHover)
|
||||
element.toggleClass('im_send_form_hover', isHover)
|
||||
curHover = isHover
|
||||
}
|
||||
return returnHover && isHover
|
||||
}
|
||||
|
||||
updateVoiceHoverBoundaries()
|
||||
updateVoiceHoveredClass(event)
|
||||
|
||||
if (!Config.Mobile) {
|
||||
$(voiceRecorderWrap).css({
|
||||
height: messageFieldWrap.offsetHeight,
|
||||
width: messageFieldWrap.offsetWidth
|
||||
})
|
||||
}
|
||||
|
||||
$($window).on(voiceRecordEvents.move, updateVoiceHoveredClass)
|
||||
|
||||
$($window).one(voiceRecordEvents.stop, function(event) {
|
||||
console.warn(111)
|
||||
$($window).off(voiceRecordEvents.move, updateVoiceHoveredClass)
|
||||
|
||||
var isHover = 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.draftMessage.files = [blob]
|
||||
$scope.draftMessage.isMedia = true
|
||||
|
||||
$scope.voiceRecorder.processing = false
|
||||
})
|
||||
}
|
||||
voiceRecorder.stop()
|
||||
console.warn(dT(), 'stop audio')
|
||||
|
||||
$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,14 +2004,14 @@ 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 {
|
||||
return cancelEvent(e)
|
||||
if (e.type == 'drop') {
|
||||
$scope.$apply(function () {
|
||||
$scope.draftMessage.files = Array.prototype.slice.call(e.originalEvent.dataTransfer.files)
|
||||
@ -1882,10 +2019,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)
|
||||
@ -3900,4 +4034,4 @@ angular.module('myApp.directives', ['myApp.filters'])
|
||||
return {
|
||||
link: link
|
||||
}
|
||||
})
|
||||
})
|
@ -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 @@
|
||||
"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",
|
||||
@ -528,7 +530,8 @@
|
||||
"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_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",
|
||||
|
@ -1610,7 +1610,8 @@ MessageComposer.prototype.resetTyping = function () {
|
||||
}
|
||||
|
||||
MessageComposer.prototype.setPlaceholder = function (newPlaceholder) {
|
||||
(this.richTextareaEl || this.textareaEl).attr('placeholder', newPlaceholder)
|
||||
console.warn(dT(), 'set placeholder', this.richTextareaEl)
|
||||
;(this.richTextareaEl || this.textareaEl).attr('placeholder', newPlaceholder)
|
||||
}
|
||||
|
||||
function Scroller (content, options) {
|
||||
|
@ -2551,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;
|
||||
}
|
||||
@ -3505,6 +3520,61 @@ 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_voice_recording,
|
||||
.im_processing_recording {
|
||||
color: #AAA;
|
||||
|
||||
.im_voice_recorder_wrap {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.error_modal_window {
|
||||
.modal-dialog {
|
||||
max-width: 350px;
|
||||
|
@ -1290,6 +1290,75 @@ a.im_panel_peer_photo .peer_initials {
|
||||
}
|
||||
}
|
||||
|
||||
.im_record {
|
||||
display: none;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: -8px 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: #bfd9ed;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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,22 @@ a.im_panel_peer_photo .peer_initials {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.im_record {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -56px;
|
||||
|
||||
.im_record_supported .im_send_form_empty & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.im_voice_recorder_wrap {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.im_edit_panel {
|
||||
|
@ -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 {
|
||||
&_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 {
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
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,13 +1470,73 @@ a.im_message_fwd_author {
|
||||
}
|
||||
}
|
||||
|
||||
.im_send_form_empty {
|
||||
.im_submit {
|
||||
.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: #bfd9ed;
|
||||
}
|
||||
|
||||
.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_attach {
|
||||
display: block;
|
||||
.im_record {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
.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 {
|
||||
}
|
||||
.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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,9 @@
|
||||
"device-storage:videos": {
|
||||
"description": "Required for videos download",
|
||||
"access": "createonly"
|
||||
},
|
||||
"audio-capture" : {
|
||||
"description" : "Required to record voice messages"
|
||||
}
|
||||
},
|
||||
"activities": {
|
||||
|
@ -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"></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>
|
||||
|
64
app/partials/desktop/send_form.html
Normal file
64
app/partials/desktop/send_form.html
Normal file
@ -0,0 +1,64 @@
|
||||
<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="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="draftMessage.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="draftMessage.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>
|
||||
<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" my-i18n>
|
||||
<span ng-switch-when="true" my-i18n-format="im_voice_processing_label"></span>
|
||||
<span ng-switch-default my-i18n-format="im_voice_recording_label"></span>
|
||||
<my-i18n-param name="dots"></my-i18n-param>
|
||||
</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="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!historyState.replyKeyboard.pFlags.hidden">
|
||||
<div my-reply-markup="historyState.replyKeyboard"></div>
|
||||
</div>
|
||||
|
||||
</form>
|
152
app/partials/mobile/channel_modal.html
Normal file
152
app/partials/mobile/channel_modal.html
Normal file
@ -0,0 +1,152 @@
|
||||
<div class="chat_modal_wrap">
|
||||
|
||||
<div class="tg_page_head tg_modal_head">
|
||||
<div class="navbar navbar-static-top navbar-inverse">
|
||||
<div class="container">
|
||||
|
||||
<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="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()" 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>
|
||||
|
||||
<div class="navbar-header">
|
||||
|
||||
<ul class="nav navbar-nav navbar-quick-nav">
|
||||
<li>
|
||||
<a ng-click="$close()" class="navbar-quick-media-back">
|
||||
<i class="icon icon-back"></i>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body mobile_modal_body">
|
||||
|
||||
<div class="mobile_user_modal_photo_profile_wrap">
|
||||
|
||||
<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"></h4>
|
||||
<p class="mobile_user_modal_status" ng-if="chatFull.participants_count > 0">
|
||||
<ng-pluralize count="chatFull.participants_count"
|
||||
when="group_modal_pluralize_participants">
|
||||
</ng-pluralize>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</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">
|
||||
<h4 class="mobile_modal_section_header" my-i18n="channel_modal_share_link"></h4>
|
||||
<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_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="hasRights('edit_photo') && !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" />
|
||||
<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>
|
||||
<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>
|
||||
<span class="tg_checkbox_label" my-i18n="group_modal_notifications"></span>
|
||||
</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 | 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>
|
||||
|
||||
<div class="chat_modal_participant_name">
|
||||
<a my-peer-link="participant.user_id"></a>
|
||||
</div>
|
||||
<div class="chat_modal_participant_status" my-user-status="::participant.user_id" bot-chat-privacy="true"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
@ -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 @@
|
||||
<div class="mobile_modal_action_wrap" ng-if="hasRights('edit_photo') && !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>
|
||||
|
@ -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"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -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>
|
||||
|
55
app/partials/mobile/send_form.html
Normal file
55
app/partials/mobile/send_form.html
Normal file
@ -0,0 +1,55 @@
|
||||
<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" my-i18n>
|
||||
<span ng-switch-when="true" my-i18n-format="im_voice_processing_label"></span>
|
||||
<span ng-switch-default my-i18n-format="im_voice_recording_label"></span>
|
||||
<my-i18n-param name="dots"></my-i18n-param>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="im_send_field_wrap" ng-class="historyState.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="!historyState.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="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="draftMessage.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-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="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!historyState.replyKeyboard.pFlags.hidden">
|
||||
<div my-reply-markup="historyState.replyKeyboard"></div>
|
||||
</div>
|
||||
|
||||
</form>
|
18
app/vendor/recorderjs/encoder_worker.js
vendored
Executable file
18
app/vendor/recorderjs/encoder_worker.js
vendored
Executable file
File diff suppressed because one or more lines are too long
245
app/vendor/recorderjs/recorder.js
vendored
Executable file
245
app/vendor/recorderjs/recorder.js
vendored
Executable file
@ -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
Executable file
1
app/vendor/recorderjs/recorder.min.js
vendored
Executable file
@ -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);
|
@ -1,6 +1,6 @@
|
||||
CACHE MANIFEST
|
||||
|
||||
# 75
|
||||
# 76
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user