From 04123d0ba692016fa46c9f3bdc151a5c61669d5a Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Tue, 11 Feb 2020 13:22:12 +0700 Subject: [PATCH] send media popup & menu & from clipboard & autofocus input --- src/lib/appManagers/appImManager.ts | 201 +++++++++++++++++++--- src/lib/appManagers/appMessagesManager.ts | 6 +- src/lib/appManagers/appSidebarLeft.ts | 2 +- src/lib/richtextprocessor.js | 2 +- src/lib/utils.js | 18 ++ src/scss/partials/_chat.scss | 11 +- src/scss/style.scss | 119 +++++++++---- 7 files changed, 294 insertions(+), 65 deletions(-) diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 4ed72c23..e085cdcf 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1,5 +1,5 @@ import apiManager from '../mtproto/apiManager'; -import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber } from "../utils"; +import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, calcImageInBox } from "../utils"; import appUsersManager from "./appUsersManager"; import appMessagesManager from "./appMessagesManager"; import appPeersManager from "./appPeersManager"; @@ -102,9 +102,35 @@ class ChatInput { public lastUrl = ''; public lastTimeType = 0; + public attachMenu: { + container?: HTMLButtonElement, + media?: HTMLDivElement, + document?: HTMLDivElement, + poll?: HTMLDivElement + } = {}; + + public attachMediaPopUp: { + container?: HTMLDivElement, + titleEl?: HTMLDivElement, + sendBtn?: HTMLButtonElement, + mediaContainer?: HTMLDivElement, + captionInput?: HTMLInputElement + } = {}; + constructor() { this.toggleEmoticons = this.pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement; + this.attachMenu.container = document.getElementById('attach-file') as HTMLButtonElement; + this.attachMenu.media = this.attachMenu.container.querySelector('.menu-media') as HTMLDivElement; + this.attachMenu.document = this.attachMenu.container.querySelector('.menu-document') as HTMLDivElement; + this.attachMenu.poll = this.attachMenu.container.querySelector('.menu-poll') as HTMLDivElement; + + this.attachMediaPopUp.container = this.pageEl.querySelector('.popup-send-photo') as HTMLDivElement; + this.attachMediaPopUp.titleEl = this.attachMediaPopUp.container.querySelector('.popup-title') as HTMLDivElement; + this.attachMediaPopUp.sendBtn = this.attachMediaPopUp.container.querySelector('.btn-primary') as HTMLButtonElement; + this.attachMediaPopUp.mediaContainer = this.attachMediaPopUp.container.querySelector('.popup-photo') as HTMLDivElement; + this.attachMediaPopUp.captionInput = this.attachMediaPopUp.container.querySelector('input') as HTMLInputElement; + this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => { if(e.key == 'Enter') { if(e.shiftKey) { @@ -116,18 +142,18 @@ class ChatInput { }); this.messageInput.addEventListener('input', (e) => { - console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); + //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); let value = this.messageInput.innerText; let entities = RichTextProcessor.parseEntities(value); - console.log('messageInput entities', entities); + //console.log('messageInput entities', entities); let entityUrl = entities.find(e => e._ == 'messageEntityUrl'); if(entityUrl) { // need to get webpage let url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length); - console.log('messageInput url:', url); + //console.log('messageInput url:', url); if(this.lastUrl != url) { this.lastUrl = url; @@ -136,7 +162,7 @@ class ChatInput { hash: 0 }).then((webpage: any) => { if(this.lastUrl != url) return; - console.log(webpage); + //console.log(webpage); appImManager.replyElements.titleEl.innerHTML = RichTextProcessor.wrapEmojiText(webpage.site_name || webpage.title || ''); appImManager.replyElements.subtitleEl.innerHTML = RichTextProcessor.wrapEmojiText(webpage.description || webpage.url || ''); @@ -187,14 +213,12 @@ class ChatInput { event.preventDefault(); }); - this.messageInput.addEventListener('paste', (e) => { + /* this.messageInput.addEventListener('paste', (e) => { e.preventDefault(); // @ts-ignore let text = (e.originalEvent || e).clipboardData.getData('text/plain'); // console.log('messageInput paste', text); - let entities = RichTextProcessor.parseEntities(text); - text = RichTextProcessor.wrapEmojiText(text); // console.log('messageInput paste after', text); @@ -205,30 +229,114 @@ class ChatInput { // @ts-ignore //console.log('paste text', text, ); window.document.execCommand('insertHTML', false, text); - }); + }); */ + + let attachFile = (file: File) => { + console.log('selected file:', file, typeof(file)); + + willAttachFile = file; + + this.fileInput.value = ''; + + this.attachMediaPopUp.captionInput.value = ''; + this.attachMediaPopUp.mediaContainer.innerHTML = ''; + + switch(willAttach) { + case 'media': { + let img = new Image(); + img.src = URL.createObjectURL(file); + img.onload = () => { + willAttachWidth = img.naturalWidth; + willAttachHeight = img.naturalHeight; + }; + this.attachMediaPopUp.titleEl.innerText = 'Send Photo'; + + this.attachMediaPopUp.mediaContainer.append(img); + this.attachMediaPopUp.container.classList.add('active'); + + break; + } + + case 'document': { + let docDiv = wrapDocument({ + file: file, + file_name: file.name || '', + size: file.size, + type: ['image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/bmp'].indexOf(file.type) !== -1 ? 'photo' : 'doc' + } as any, false); + + this.attachMediaPopUp.titleEl.innerText = 'Send File'; + + this.attachMediaPopUp.mediaContainer.append(docDiv); + this.attachMediaPopUp.container.classList.add('active'); + break; + } + } + }; + + let willAttach = ''; + let willAttachFile: File = null; + let willAttachWidth = 0, willAttachHeight = 0; this.fileInput.addEventListener('change', (e) => { var file = (e.target as HTMLInputElement & EventTarget).files[0]; if(!file) { return; } - console.log('selected file:', file, typeof(file)); - - this.fileInput.value = ''; - - appMessagesManager.sendFile(appImManager.peerID, file, {isMedia: true}); - appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; - - /* MTProto.apiFileManager.uploadFile(file).then((inputFile) => { - console.log('uploaded smthn', inputFile); - }); */ + attachFile(file); }, false); - - this.pageEl.querySelector('#attach-file').addEventListener('click', () => { + + this.attachMenu.media.addEventListener('click', () => { + willAttach = 'media'; this.fileInput.click(); }); + this.attachMenu.document.addEventListener('click', () => { + willAttach = 'document'; + this.fileInput.click(); + }); + + document.addEventListener('paste', (event) => { + if(!appImManager.peerID || this.attachMediaPopUp.container.classList.contains('active')) { + return; + } + + // @ts-ignore + var items = (event.clipboardData || event.originalEvent.clipboardData).items; + //console.log(items); // will give you the mime types + for(let i = 0; i < items.length; ++i) { + if(items[i].kind == 'file') { + event.cancelBubble = true; + event.stopPropagation(); + + let file = items[i].getAsFile(); + //console.log(items[i], file); + if(!file) continue; + + willAttach = file.type.indexOf('image/') === 0 ? 'media' : "document"; + attachFile(file); + } + } + }, true); + + this.attachMediaPopUp.sendBtn.addEventListener('click', () => { + this.attachMediaPopUp.container.classList.remove('active'); + let caption = this.attachMediaPopUp.captionInput.value; + + appMessagesManager.sendFile(appImManager.peerID, willAttachFile, { + isMedia: true, + caption, + width: willAttachWidth, + height: willAttachHeight + }); + appImManager.scroll.scrollTop = appImManager.scroll.scrollHeight; + }); + this.btnSend.addEventListener('click', () => { if(this.btnSend.classList.contains('tgico-send')) { this.sendMessage(); @@ -497,7 +605,7 @@ export class AppImManager { return; } - if(target.tagName == 'IMG' || target.tagName == 'VIDEO') { + if((target.tagName == 'IMG' && !target.classList.contains('emoji')) || target.tagName == 'VIDEO') { let messageID = +target.getAttribute('message-id'); let message = appMessagesManager.getMessage(messageID); @@ -540,6 +648,45 @@ export class AppImManager { this.btnMenuMute.addEventListener('click', () => this.mutePeer()); this.btnMute.addEventListener('click', () => this.mutePeer()); + let onKeyDown = (e: KeyboardEvent) => { + let target = e.target as HTMLElement; + + //if(target.tagName == 'INPUT') return; + + this.log('onkeydown', e); + + if(this.chatInputC.attachMediaPopUp.container.classList.contains('active')) { + if(target.tagName != 'INPUT') { + this.chatInputC.attachMediaPopUp.captionInput.focus(); + } + + if(e.key == 'Enter') { + this.chatInputC.attachMediaPopUp.sendBtn.click(); + } else if(e.key == 'Escape') { + this.chatInputC.attachMediaPopUp.container.classList.remove('active'); + } + + return; + } + + if(e.target != this.chatInputC.messageInput && target.tagName != 'INPUT') { + this.chatInputC.messageInput.focus(); + placeCaretAtEnd(this.chatInputC.messageInput); + } + }; + + document.body.addEventListener('keydown', onKeyDown); + + /* this.chatInner.addEventListener('mouseover', () => { + document.body.addEventListener('keydown', onKeyDown); + + this.log('mouseover'); + + this.chatInner.addEventListener('mouseout', () => { + document.body.removeEventListener('keydown', onKeyDown); + }, {once: true}); + }); */ + this.chatInner.addEventListener('contextmenu', e => { let bubble: HTMLDivElement = null; @@ -1272,9 +1419,15 @@ export class AppImManager { if(pending.size < 1e6) { let img = new Image(); img.src = URL.createObjectURL(pending.file); + + let {w, h} = calcImageInBox(pending.w, pending.h, 380, 380); + + attachmentDiv.style.width = w + 'px'; + attachmentDiv.style.height = h + 'px'; attachmentDiv.append(img); preloader.attach(attachmentDiv, false); + bubble.classList.add('hide-name', 'photo'); break; } @@ -1478,7 +1631,7 @@ export class AppImManager { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); nameDiv.innerHTML = 'Forwarded from ' + title; - nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID); + nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false); bubble.append(nameDiv); } } else { @@ -1549,7 +1702,7 @@ export class AppImManager { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); nameDiv.innerHTML = title; - nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID); + nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false); bubble.append(nameDiv); } else if(!message.reply_to_mid) { bubble.classList.add('hide-name'); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 35b0a7b0..6d1aa427 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -371,7 +371,9 @@ export class AppMessagesManager { isMedia?: boolean, replyToMsgID?: number, caption?: string, - entities?: any[] + entities?: any[], + width?: number, + height?: number } = {}) { peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; var messageID = this.tempID--; @@ -459,6 +461,8 @@ export class AppMessagesManager { size: file.size, file: file, preloader: preloader, + w: options.width, + h: options.height, progress: { percent: 1, total: file.size, diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index eceb5195..3b184b9b 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -188,7 +188,7 @@ class AppSidebarLeft { } public onChatsScroll() { - this.log(this.scroll); + //this.log(this.scroll); if(this.scroll.hiddenElements.down.length > 0/* || 1 == 1 */) return; if(!this.loadDialogsPromise) { diff --git a/src/lib/richtextprocessor.js b/src/lib/richtextprocessor.js index decab819..bba32d4f 100644 --- a/src/lib/richtextprocessor.js +++ b/src/lib/richtextprocessor.js @@ -8,7 +8,7 @@ var EmojiHelper = { var emojiData = Config.Emoji; var emojiIconSize = emojiData.img_size; -var emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1 /* && false */, +var emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1 && false, emojiCode; //var emojiRegExp = '\\u0023\\u20E3|\\u00a9|\\u00ae|\\u203c|\\u2049|\\u2139|[\\u2194-\\u2199]|\\u21a9|\\u21aa|\\u231a|\\u231b|\\u23e9|[\\u23ea-\\u23ec]|\\u23f0|\\u24c2|\\u25aa|\\u25ab|\\u25b6|\\u2611|\\u2614|\\u26fd|\\u2705|\\u2709|[\\u2795-\\u2797]|\\u27a1|\\u27b0|\\u27bf|\\u2934|\\u2935|[\\u2b05-\\u2b07]|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u3030|\\u303d|\\u3297|\\u3299|[\\uE000-\\uF8FF\\u270A-\\u2764\\u2122\\u25C0\\u25FB-\\u25FE\\u2615\\u263a\\u2648-\\u2653\\u2660-\\u2668\\u267B\\u267F\\u2693\\u261d\\u26A0-\\u26FA\\u2708\\u2702\\u2601\\u260E]|[\\u2600\\u26C4\\u26BE\\u23F3\\u2764]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83C[\\uDDE8-\\uDDFA\uDDEC]\\uD83C[\\uDDEA-\\uDDFA\uDDE7]|[0-9]\\u20e3|\\uD83C[\\uDC00-\\uDFFF]'; //var emojiRegExp = '\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff]'; diff --git a/src/lib/utils.js b/src/lib/utils.js index a07acc76..22014f21 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -142,6 +142,24 @@ export function getRichValue (field) { return value } +export function placeCaretAtEnd(el) { + el.focus(); + if (typeof window.getSelection != "undefined" + && typeof document.createRange != "undefined") { + var range = document.createRange(); + range.selectNodeContents(el); + range.collapse(false); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } else if (typeof document.body.createTextRange != "undefined") { + var textRange = document.body.createTextRange(); + textRange.moveToElementText(el); + textRange.collapse(false); + textRange.select(); + } +} + export function getRichValueWithCaret (field) { if (!field) { return [] diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 1da12e28..e89b837d 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -271,7 +271,7 @@ } .message:not(.message-empty) + .attachment, - &.is-reply .attachment { + &.is-reply .message:not(.message-empty) + .attachment { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } @@ -305,7 +305,7 @@ width: max-content; } - img, video { + img:not(.emoji), video { /* object-fit: contain; */ object-fit: cover; width: 100%; @@ -493,7 +493,8 @@ } > .name { - padding: .2675rem .6rem 0 .6rem; + /* padding: .2675rem .6rem 0 .6rem; */ + padding: .32rem .6rem 0 .6rem; font-weight: 500; /* padding-bottom: 4px; */ color: $darkblue; @@ -603,6 +604,7 @@ } &.forwarded .attachment, + &.is-reply .attachment, &:not(.hide-name):not(.sticker) .attachment { border-top-left-radius: 0; border-top-right-radius: 0; @@ -688,7 +690,8 @@ border-radius: 12px 12px 0px 12px; } - &.forwarded .attachment { + &.forwarded .attachment, + &.is-reply .attachment { border-top-left-radius: 0; border-top-right-radius: 0; } diff --git a/src/scss/style.scss b/src/scss/style.scss index 045bee97..252137f4 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -1,5 +1,7 @@ $placeholder-color: #9e9e9e; $border-radius: 8px; +$border-radius-medium: 10px; +$border-radius-big: 12px; $button-primary-background: #4EA4F6; $success-color: #4DCD5E; $color-error: #E53935; @@ -770,6 +772,7 @@ input:focus, button:focus { cursor: pointer; overflow: hidden; position: relative; + padding: 0; // new &:hover { background: darken($button-primary-background, 8%); @@ -994,47 +997,17 @@ $width: 100px; } span.popup-close { - /* position: absolute; - left: 20px; - top: 12.5px; */ - /* width: 100%; */ - height: 18px; cursor: pointer; + color: $color-gray; z-index: 3; text-align: center; justify-self: center; line-height: 1; + transition: .2s; - svg { - max-width: 100%; - max-height: 100%; - } - - path { - fill: $color-gray; - transition: .2s all; - } - - &:hover path { - fill: #000; - } - - /* &:before, &:after { - position: absolute; - left: 15px; - content: ' '; - height: 15px; - width: 1px; - background-color: #707579; - } - - &:before { - transform: rotate(45deg); + &:hover { + color: #000; } - - &:after { - transform: rotate(-45deg); - } */ } .popup.active .popup-container { @@ -1334,6 +1307,84 @@ div.scrollable::-webkit-scrollbar-thumb { justify-content: flex-start; } +.popup-send-photo { + .popup-container { + max-width: 420px; + max-height: 425px; + overflow: hidden; + /* padding: 12px 20px 50px; */ + padding: 12px 20px 32.5px; + border-radius: $border-radius-medium; + } + + .popup-header { + justify-content: space-between; + align-items: center; + margin-bottom: 12.5px; + + .popup-close { + font-size: 1.5rem; + margin-left: .5rem; + } + + .popup-title { + flex: 1; + padding: 0 2rem; + margin: 0; + font-size: 1.35rem; + font-weight: 500; + } + + .btn-primary { + width: 80px; + height: 35px; + font-size: 1rem; + padding: 0; + border-radius: $border-radius-medium; + } + } + + .popup-photo { + max-width: 378px; + max-height: 256px; + display: flex; + overflow: hidden; + justify-content: center; + width: fit-content; + border-radius: $border-radius-medium; + /* align-items: center; */ + + .document { + max-width: 100%; + overflow: hidden; + cursor: default; + + .document-name { + font-weight: normal; + width: 100%; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } + } + + img { + object-fit: contain; + } + } + + .input-field { + margin-top: 25px; + + input { + height: 55px; + font-size: 1.15rem; + padding: 0 15px; + border-radius: $border-radius-medium; + } + } +} + .page-chats { /* display: grid; */ /* grid-template-columns: 25% 50%; */