From a74684b493f99ed625c636a1de9d9173f7ad0014 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sun, 16 Feb 2020 01:08:26 +0700 Subject: [PATCH] new reply --- src/components/wrappers.ts | 137 ++++++++++++++++++++-------- src/lib/appManagers/appImManager.ts | 74 +++------------ src/scss/partials/_chat.scss | 99 +++++++++++++------- src/scss/style.scss | 8 +- 4 files changed, 186 insertions(+), 132 deletions(-) diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 87358c0e..492dfecc 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -10,6 +10,7 @@ import LazyLoadQueue from './lazyLoadQueue'; import apiFileManager, { CancellablePromise } from '../lib/mtproto/apiFileManager'; import appWebpManager from '../lib/appManagers/appWebpManager'; import {wrapPlayer} from '../lib/ckin'; +import { RichTextProcessor } from '../lib/richtextprocessor'; export type MTDocument = { _: 'document', @@ -159,7 +160,7 @@ export function wrapDocument(doc: MTDocument, withTime = false): HTMLDivElement if(doc.type == 'voice') { return wrapAudio(doc, withTime); } - + let docDiv = document.createElement('div'); docDiv.classList.add('document'); @@ -210,27 +211,27 @@ export function wrapDocument(doc: MTDocument, withTime = false): HTMLDivElement appDocsManager.saveDocFile(doc.id).then(res => { promise = res.promise; - + preloader.attach(downloadDiv, true, promise); - + promise.then(() => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); }); }) - + downloadDiv.classList.add('downloading'); } else { downloadDiv.classList.remove('downloading'); promise = null; } }); - + /* apiFileManager.getDownloadedFile(Object.assign({}, doc, {_: 'inputDocumentFileLocation'})).then(() => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); }, () => { - + }); */ return docDiv; @@ -241,51 +242,51 @@ let lastAudioToggle: HTMLDivElement = null; export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { let div = document.createElement('div'); div.classList.add('audio'); - + let duration = doc.duration; - + // @ts-ignore let durationStr = String(duration | 0).toHHMMSS(true); - + div.innerHTML = `
${durationStr}
`; - + console.log('wrapping audio', doc, doc.attributes[0].waveform); - + let timeDiv = div.lastElementChild as HTMLDivElement; let downloadDiv = div.querySelector('.audio-download') as HTMLDivElement; let preloader: ProgressivePreloader; let promise: CancellablePromise; - + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add('audio-waveform'); svg.setAttributeNS(null, 'width', '250'); svg.setAttributeNS(null, 'height', '23'); svg.setAttributeNS(null, 'viewBox', '0 0 250 23'); - + div.insertBefore(svg, div.lastElementChild); let wave = doc.attributes[0].waveform as Uint8Array; - + let index = 0; for(let uint8 of wave) { let percents = uint8 / 255; - + let height = 23 * percents; if(/* !height || */height < 2) { height = 2; } - + svg.insertAdjacentHTML('beforeend', ` - + `); ++index; } - + let onClick = () => { if(!promise) { if(downloadDiv.classList.contains('downloading')) { @@ -298,47 +299,47 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { let promise = appDocsManager.downloadDoc(doc.id); preloader.attach(downloadDiv, true, promise); - + promise.then(blob => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); - + let audio = document.createElement('audio'); let source = document.createElement('source'); source.src = URL.createObjectURL(blob); source.type = doc.mime_type; - + div.removeEventListener('click', onClick); let toggle = div.querySelector('.audio-toggle') as HTMLDivElement; - + let interval = 0; - + toggle.addEventListener('click', () => { if(audio.paused) { if(lastAudioToggle && lastAudioToggle.classList.contains('tgico-largepause')) { lastAudioToggle.click(); } - + audio.currentTime = 0; audio.play(); - + lastAudioToggle = toggle; - + toggle.classList.remove('tgico-largeplay'); toggle.classList.add('tgico-largepause'); - + (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); - + let lastIndex = 0; interval = setInterval(() => { if(lastIndex >= svg.childElementCount) { clearInterval(interval); return; } - + // @ts-ignore timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); - + //svg.children[lastIndex].setAttributeNS(null, 'fill', '#000'); svg.children[lastIndex].classList.add('active'); ++lastIndex; @@ -348,23 +349,23 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { audio.pause(); toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); - + clearInterval(interval); } }); - + audio.addEventListener('ended', () => { toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); clearInterval(interval); - + // @ts-ignore timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); }); - + audio.append(source); }); - + downloadDiv.classList.add('downloading'); } else { downloadDiv.classList.remove('downloading'); @@ -373,7 +374,7 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { }; div.addEventListener('click', onClick); - + div.click(); return div; @@ -397,9 +398,9 @@ export function wrapPhoto(this: AppImManager, photo: any, message: any, containe let load = () => { let promise = appPhotosManager.preloadPhoto(photo.id, size); - + preloader.attach(container, true, promise); - + return promise.then((blob) => { if(this.peerID != peerID) { this.log.warn('peer changed'); @@ -527,3 +528,63 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); } + +export function wrapReply(title: string, subtitle: string, media?: any) { + let div = document.createElement('div'); + div.classList.add('reply'); + + let replyBorder = document.createElement('div'); + replyBorder.classList.add('reply-border'); + + let replyContent = document.createElement('div'); + replyContent.classList.add('reply-content'); + + let replyTitle = document.createElement('div'); + replyTitle.classList.add('reply-title'); + + let replySubtitle = document.createElement('div'); + replySubtitle.classList.add('reply-subtitle'); + + replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; + + if(media) { + if(media.photo) { + replySubtitle.innerHTML = 'Photo'; + } else if(media.document && media.document.type) { + replySubtitle.innerHTML = media.document.type; + } else if(media.webpage) { + replySubtitle.innerHTML = RichTextProcessor.wrapPlainText(media.webpage.url); + } else { + replySubtitle.innerHTML = media._; + } + + if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { + let replyMedia = document.createElement('div'); + replyMedia.classList.add('reply-media'); + + let photo = media.photo || media.document; + + let sizes = photo.sizes || photo.thumbs; + if(sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, replyMedia, false, true); + } + + appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) + .then((blob) => { + replyMedia.style.backgroundImage = 'url(' + URL.createObjectURL(blob) + ')'; + }); + + replyContent.append(replyMedia); + div.classList.add('is-reply-media'); + } + } else { + replySubtitle.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; + } + + replyContent.append(replyTitle, replySubtitle); + div.append(replyBorder, replyContent); + + console.log('wrapReply', title, subtitle, media); + + return div; +} diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 22cf00de..469666d5 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -20,7 +20,7 @@ import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from '../../components/emoticonsDropdown'; import LazyLoadQueue from '../../components/lazyLoadQueue'; -import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker } from '../../components/wrappers'; +import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; import { openBtnMenu } from '../../components/misc'; import appWebPagesManager from './appWebPagesManager'; @@ -490,11 +490,15 @@ class ChatInput { this.btnSend.classList.add('tgico-microphone2'); }; - public setTopInfo(title: string, subtitle: string, input?: string) { + public setTopInfo(title: string, subtitle: string, input?: string, media?: any) { //appImManager.scrollPosition.prepareFor('down'); - this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; - this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; + if(this.replyElements.container.lastElementChild.tagName == 'DIV') { + this.replyElements.container.lastElementChild.remove(); + this.replyElements.container.append(wrapReply(title, subtitle, media)); + } + //this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; + //this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; this.replyElements.container.classList.add('active'); if(input !== undefined) { @@ -796,7 +800,7 @@ export class AppImManager { let isReplyClick = false; try { - isReplyClick = !!findUpClassName(e.target, 'box'); + isReplyClick = !!findUpClassName(e.target, 'reply'); } catch(err) {} if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) { @@ -1000,14 +1004,14 @@ export class AppImManager { this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message); + this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message.media); this.chatInputC.replyToMsgID = this.contextMenuMsgID; this.chatInputC.editMsgID = 0; }); this.contextMenuEdit.addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo('Editing', message.message, message.message); + this.chatInputC.setTopInfo('Editing', message.message, message.message, message.media); this.chatInputC.replyToMsgID = 0; this.chatInputC.editMsgID = this.contextMenuMsgID; }); @@ -1563,7 +1567,7 @@ export class AppImManager { if(this.peerID == peerID) { this.setPeerPromise = null; } - + this.log.error('setPeer promises error:', err); return false; }); @@ -1992,20 +1996,8 @@ export class AppImManager { } } else { if(message.reply_to_mid) { - let box = document.createElement('div'); - box.classList.add('box'); - - let quote = document.createElement('div'); - quote.classList.add('quote'); - - let nameEl = document.createElement('a'); - nameEl.classList.add('name'); - - let textDiv = document.createElement('div'); - textDiv.classList.add('text'); - let originalMessage = appMessagesManager.getMessage(message.reply_to_mid); - let originalPeerTitle = appPeersManager.getPeerTitle(originalMessage.fromID) || ''; + let originalPeerTitle = appPeersManager.getPeerTitle(originalMessage.fromID, true) || ''; this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message); @@ -2018,54 +2010,16 @@ export class AppImManager { originalPeerTitle = 'Loading...'; } - let originalText = ''; - if(originalMessage.message) { - originalText = RichTextProcessor.wrapRichText(originalMessage.message, { - entities: originalMessage.totalEntities, - noLinebreaks: true - }); - } - - if(originalMessage.media) { - switch(originalMessage.media._) { - case 'messageMediaPhoto': - if(!originalText) originalText = 'Photo'; - break; - - default: - if(!originalText) originalText = originalMessage.media._; - break; - } - } - - nameEl.innerHTML = originalPeerTitle; - textDiv.innerHTML = originalText; - - quote.append(nameEl, textDiv); - box.append(quote); - if(originalMessage.mid) { bubble.setAttribute('data-original-mid', originalMessage.mid); } else { bubble.setAttribute('data-original-mid', message.reply_to_mid); } - bubble.append(box); + bubble.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage.media)); bubble.classList.add('is-reply'); } - /* if(message.media) { - switch(message.media._) { - case 'messageMediaWebPage': { - let nameDiv = document.createElement('div'); - nameDiv.classList.add('name'); - nameDiv.innerText = title; - bubble.append(nameDiv); - break; - } - } - } */ - if(!bubble.classList.contains('sticker') && (peerID < 0 && peerID != message.fromID)) { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 349dacc5..d7b6dea3 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -250,6 +250,14 @@ opacity: 1; } } + + .reply { + width: auto; + + .reply-content { + height: auto; + } + } &.photo, &.video { width: min-content; @@ -426,7 +434,7 @@ } } - .box { + .box, .reply { font-size: .95rem; // margin: .25rem; margin: 4px 4px 4px 6px; @@ -498,43 +506,45 @@ max-width: 100%; overflow: hidden; width: 100%; + } - .text { - line-height: 1.2; - } - + .text, .reply-subtitle { + line-height: 1.2; } - .name { + .name, .reply-title { font-weight: 500; display: inline!important; } - - &:not(.web) { - margin-bottom: 0; - margin-top: 0; - cursor: pointer; - } + } + + .reply { + margin-bottom: 6px; + margin-top: 0; + cursor: pointer; } &.is-reply { &.emoji-big, &.sticker { - .box { + .reply { padding: 10px; border-radius: 12px; border: 1px solid #ccc; max-width: 300px; + height: 54px; + max-height: 54px; white-space: nowrap; position: absolute; top: 0; + margin-bottom: 0; - .quote { + .reply-content { margin-top: 0; } } } - .quote .text { + .reply-content { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -688,12 +698,12 @@ } } - &.hide-name .message:not(.message-empty) { + &.hide-name:not(.is-reply) .message:not(.message-empty) { //padding-top: .2675rem; padding-top: 6px; } - &.hide-name:not(.sticker):not(.emoji-big) .box:not(.web) .quote { + &.hide-name:not(.sticker):not(.emoji-big) .reply { margin-top: 6px; } @@ -726,13 +736,13 @@ color: $darkblue; } - .quote:hover { + .quote:hover, .reply:hover { background-color: $light; } .bubble.is-reply { &.emoji-big, &.sticker { - .box { + .box, .reply { left: calc(100% + 10px); background-color: #fff; } @@ -741,17 +751,16 @@ .quote { border-left: 2px $darkblue solid; - //margin-top: 6px; //MOJET VREMENNO - - .name { - color: $darkblue; - } * { overflow: hidden; text-overflow: ellipsis; } } + + .quote .name, .reply-title { + color: $darkblue; + } .time { color: #a3adb6; @@ -798,13 +807,13 @@ .out { align-items: flex-end; - .quote:hover { + .quote:hover, .reply:hover { background-color: rgba($green, 0.12); } .bubble.is-reply { &.emoji-big, &.sticker { - .box { + .box, .reply { background-color: #eeffde; right: calc(100% + 10px); border-color: rgba($green, .12); @@ -814,10 +823,14 @@ .quote { border-left: 2px $darkgreen solid; - - .name { - color: $darkgreen; - } + } + + .reply-border { + background-color: $darkgreen; + } + + .quote .name, .reply-title { + color: $darkgreen; } .time { @@ -920,7 +933,7 @@ } &-toggle, &-download { - background-color: #68AB5A; + background-color: #4FAE4E; } } } @@ -1089,7 +1102,14 @@ width: 187px; margin-right: 1rem; max-height: 35px; + position: relative; /* padding: .25rem; */ + + &.is-reply-media { + .pinned-message-content, .reply-content { + padding-left: 40px; + } + } &:hover { background-color: rgba(112, 117, 121, 0.08); @@ -1108,6 +1128,10 @@ flex-shrink: 1; overflow: hidden; pointer-events: none; + position: relative; + height: 32px; + display: flex; + flex-direction: column; } &-title { @@ -1127,6 +1151,19 @@ color: #111; } + &-media { + height: 32px; + width: 32px; + border-radius: 8px; + overflow: hidden; + position: absolute; + left: 0; + top: 0; + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + } + img.emoji { height: 16px; width: 16px; diff --git a/src/scss/style.scss b/src/scss/style.scss index 919d79ed..e85e72c5 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -479,14 +479,14 @@ input { .audio { position: relative; padding-left: 67px; - min-height: 54px; + min-height: 58px; max-width: 286px; overflow: visible!important; &-toggle, &-download { border-radius: 50%; background-color: $blue; - font-size: 2.5rem; + font-size: 2.2rem; align-items: center; } @@ -512,7 +512,7 @@ input { &-time { font-size: 14px; color: $color-gray; - margin-top: 4px; + margin-top: 3px; margin-left: -1px; } } @@ -1317,10 +1317,12 @@ div.scrollable::-webkit-scrollbar-thumb { &.scrollable-x { overflow-x: auto; + scrollbar-width: none; } &.scrollable-y { overflow-y: auto; + scrollbar-width: none; } &.scrollable-x ~ .scrollbar-thumb {