From 9a5ffc216249fc6a2beb252c5cfb2c697212acc5 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 19 Nov 2020 01:51:39 +0200 Subject: [PATCH] Handle webp sticker with message (animated webp) Fix some types of messages for edit Up arrow to edit Maybe support Safari iOS 14 WebP Fix title abbreviation again --- src/components/chat/input.ts | 6 +++- src/components/wrappers.ts | 6 +++- src/lib/appManagers/appDocsManager.ts | 5 ++- src/lib/appManagers/appImManager.ts | 39 ++++++++++++++++++++--- src/lib/appManagers/appMessagesManager.ts | 4 ++- src/lib/mtproto/apiFileManager.ts | 4 +-- src/lib/mtproto/mtproto.worker.ts | 8 +++++ src/lib/mtproto/mtprotoworker.ts | 4 +++ src/lib/richtextprocessor.ts | 14 +++++++- src/lib/webp/webpWorkerController.ts | 9 ++++++ src/scss/partials/_chatBubble.scss | 4 +++ 11 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 92a577bf..3c730376 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; import opusDecodeController from "../../lib/opusDecodeController"; import { RichTextProcessor } from "../../lib/richtextprocessor"; import rootScope from '../../lib/rootScope'; -import { cancelEvent, findUpClassName, getRichValue, isInputEmpty, serializeNodes } from "../../helpers/dom"; +import { cancelEvent, findUpClassName, getRichValue, isInputEmpty, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import emoticonsDropdown from "../emoticonsDropdown"; import PopupCreatePoll from "../popupCreatePoll"; @@ -719,6 +719,10 @@ export class ChatInput { if(input !== undefined) { this.messageInput.innerHTML = input || ''; + window.requestAnimationFrame(() => { + placeCaretAtEnd(this.messageInput); + this.inputScroll.scrollTop = this.inputScroll.scrollHeight; + }); } setTimeout(() => { diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index a69c6f07..f60ae6d3 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -581,7 +581,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o } else if('bytes' in thumb) { img = new Image(); - if((!isSafari || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { + if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); } else { webpWorkerController.convert(doc.id, thumb.bytes as Uint8Array).then(bytes => { @@ -692,6 +692,10 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o img.classList.add('fade-in-transition'); img.style.opacity = '0'; + if(!div.firstElementChild) { + div.append(img); + } + img.addEventListener('load', () => { doc.downloaded = true; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 3530f7df..99c47bfa 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -1,10 +1,12 @@ import { FileURLType, getFileNameByLocation, getFileURL } from '../../helpers/fileName'; import { safeReplaceArrayInObject, defineNotNumerableProperties, isObject } from '../../helpers/object'; +import { isSafari } from '../../helpers/userAgent'; import { Document, InputFileLocation, PhotoSize } from '../../layer'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import referenceDatabase, { ReferenceContext } from '../mtproto/referenceDatabase'; import opusDecodeController from '../opusDecodeController'; import { RichTextProcessor } from '../richtextprocessor'; +import webpWorkerController from '../webp/webpWorkerController'; import appDownloadManager, { DownloadBlob } from './appDownloadManager'; import appPhotosManager from './appPhotosManager'; @@ -99,7 +101,8 @@ class AppDocsManager { } } - if(/* apiDoc.thumbs && */doc.mime_type == 'image/webp') { + // * there can be no thumbs, then it is a document + if(/* apiDoc.thumbs && */doc.mime_type == 'image/webp' && (doc.thumbs || webpWorkerController.isWebpSupported())) { doc.type = 'sticker'; doc.sticker = 1; } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 33b4c2ee..a4bfab5d 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -732,7 +732,7 @@ export class AppImManager { //if(target.tagName == 'INPUT') return; - //this.log('onkeydown', e); + this.log('onkeydown', e); if(e.key == 'Escape') { /* if(AppMediaViewer.wholeDiv.classList.contains('active')) { @@ -750,6 +750,31 @@ export class AppImManager { return; } else if(e.code == "KeyC" && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') { return; + } else if(e.code == 'ArrowUp') { + if(!this.chatInputC.editMsgID) { + const history = appMessagesManager.historiesStorage[this.peerID]; + if(history?.history) { + let goodMid: number; + for(const mid of history.history) { + const message = appMessagesManager.getMessage(mid); + const good = this.myID == this.peerID ? message.fromID == this.myID : message.pFlags.out; + + if(good) { + if(appMessagesManager.canEditMessage(mid, 'text')) { + goodMid = mid; + } + + break; + } + } + + if(goodMid) { + this.chatInputC.initMessageEditing(goodMid); + } + } + }/* else { + this.scrollable.scrollTop -= 20; + } */ } if(e.target != this.chatInputC.messageInput && target.tagName != 'INPUT' && !target.hasAttribute('contenteditable')) { @@ -1394,7 +1419,11 @@ export class AppImManager { this.chatInput.style.display = ''; this.chatInput.classList.toggle('is-hidden', !canWrite); this.bubblesContainer.classList.toggle('is-chat-input-hidden', !canWrite); - this.chatInputC.messageInput.toggleAttribute('contenteditable', canWrite); + if(!canWrite) { + this.chatInputC.messageInput.removeAttribute('contenteditable'); + } else { + this.chatInputC.messageInput.setAttribute('contenteditable', 'true'); + } // const noTransition = [this.columnEl/* appSidebarRight.sidebarEl, this.backgroundEl, // this.bubblesContainer, this.chatInput, this.chatInner, @@ -1765,12 +1794,14 @@ export class AppImManager { return bubble; } + let messageMedia = message.media; + let messageMessage: string, totalEntities: any[]; if(message.grouped_id) { const t = appMessagesManager.getAlbumText(message.grouped_id); messageMessage = t.message; totalEntities = t.totalEntities; - } else { + } else if(messageMedia?.document?.type != 'sticker') { messageMessage = message.message; totalEntities = message.totalEntities; } @@ -1779,8 +1810,6 @@ export class AppImManager { entities: totalEntities }); - let messageMedia = message.media; - if(totalEntities && !messageMedia) { let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); let strLength = messageMessage.length; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 092c1b86..966ff3a0 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -2142,6 +2142,7 @@ export class AppMessagesManager { messageText = 'Video message' + (message.message ? ', ' : '') + ''; } else if(document.type == 'sticker') { messageText = (document.stickerEmoji || '') + 'Sticker'; + text = ''; } else { messageText = '' + document.file_name + (message.message ? ', ' : '') + ''; } @@ -2379,7 +2380,8 @@ export class AppMessagesManager { return false; } - if(this.getMessagePeer(message) == appUsersManager.getSelf().id) { + // * second rule for saved messages, because there is no 'out' flag + if(message.pFlags.out || this.getMessagePeer(message) == appUsersManager.getSelf().id) { return true; } diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 03525d63..f5425ea8 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -2,13 +2,13 @@ import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePr import { notifyAll, notifySomeone } from "../../helpers/context"; import { getFileNameByLocation } from "../../helpers/fileName"; import { nextRandomInt } from "../../helpers/random"; -import { isSafari } from "../../helpers/userAgent"; import { FileLocation, InputFile, InputFileLocation, UploadFile } from "../../layer"; import cacheStorage from "../cacheStorage"; import cryptoWorker from "../crypto/cryptoworker"; import FileManager from "../filemanager"; import { logger, LogLevels } from "../logger"; import apiManager from "./apiManager"; +import { isWebpSupported } from "./mtproto.worker"; import { MOUNT_CLASS_TO } from "./mtproto_config"; @@ -182,7 +182,7 @@ export class ApiFileManager { let process: ApiFileManager['uncompressTGS'] | ApiFileManager['convertWebp']; - if(options.mimeType == 'image/webp' && isSafari) { + if(options.mimeType == 'image/webp' && !isWebpSupported()) { process = this.convertWebp; options.mimeType = 'image/png'; } else if(options.mimeType == 'application/x-tgsticker') { diff --git a/src/lib/mtproto/mtproto.worker.ts b/src/lib/mtproto/mtproto.worker.ts index 2bed4f5e..36d37132 100644 --- a/src/lib/mtproto/mtproto.worker.ts +++ b/src/lib/mtproto/mtproto.worker.ts @@ -52,6 +52,11 @@ function respond(...args: any[]) { } */ } +let webpSupported = false; +export const isWebpSupported = () => { + return webpSupported; +}; + networkerFactory.setUpdatesProcessor((obj, bool) => { respond({update: {obj, bool}}); }); @@ -98,6 +103,9 @@ ctx.addEventListener('message', async(e) => { respond(responseTask); return; + } else if(task.type == 'webpSupport') { + webpSupported = task.payload; + return; } switch(task.task) { diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 55cdd048..0ec014f5 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -106,6 +106,10 @@ export class ApiManagerProxy extends CryptoWorkerMethods { this.postMessage = this.worker.postMessage.bind(this.worker); } + const isWebpSupported = webpWorkerController.isWebpSupported(); + this.log('WebP supported:', isWebpSupported); + this.postMessage({type: 'webpSupport', payload: isWebpSupported}); + this.releasePending(); } diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts index 242ad1d1..54f9870f 100644 --- a/src/lib/richtextprocessor.ts +++ b/src/lib/richtextprocessor.ts @@ -870,7 +870,7 @@ namespace RichTextProcessor { return text.match(urlRegExp); } - const el = document.createElement('span'); + /* const el = document.createElement('span'); export function getAbbreviation(str: string, onlyFirst = false) { const wrapped = wrapEmojiText(str); el.innerHTML = wrapped; @@ -892,6 +892,18 @@ namespace RichTextProcessor { } return first + last; + } */ + export function getAbbreviation(str: string, onlyFirst = false) { + const splitted = str.trim().split(' '); + if(!splitted[0]) return ''; + + const first = [...splitted[0]][0]; + + if(onlyFirst || splitted.length == 1) return wrapEmojiText(first); + + const last = [...splitted[splitted.length - 1]][0]; + + return wrapEmojiText(first + last); } } diff --git a/src/lib/webp/webpWorkerController.ts b/src/lib/webp/webpWorkerController.ts index b23e86a3..db7cedf6 100644 --- a/src/lib/webp/webpWorkerController.ts +++ b/src/lib/webp/webpWorkerController.ts @@ -14,6 +14,7 @@ export type WebpConvertTask = { export class WebpWorkerController { private worker: Worker; private convertPromises: {[fileName: string]: CancellablePromise} = {}; + private isWebpSupportedCache: boolean; init() { this.worker = new WebpWorker(); @@ -41,6 +42,14 @@ export class WebpWorkerController { this.worker.postMessage(data); } + isWebpSupported() { + if(this.isWebpSupportedCache === undefined) { + this.isWebpSupportedCache = document.createElement('canvas').toDataURL('image/webp').startsWith('data:image/webp'); + } + + return this.isWebpSupportedCache; + } + convert(fileName: string, bytes: Uint8Array) { fileName = 'main-' + fileName; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index ac42943d..adb49656 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1242,6 +1242,10 @@ $bubble-margin: .25rem; &__media-container { cursor: pointer; } + + audio-element, poll-element { + white-space: nowrap; // * fix due to .message white-space prewrap + } } .bubble.service {