Browse Source

new reply

master
Eduard Kuzmenko 4 years ago
parent
commit
a74684b493
  1. 137
      src/components/wrappers.ts
  2. 74
      src/lib/appManagers/appImManager.ts
  3. 99
      src/scss/partials/_chat.scss
  4. 8
      src/scss/style.scss

137
src/components/wrappers.ts

@ -10,6 +10,7 @@ import LazyLoadQueue from './lazyLoadQueue'; @@ -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 @@ -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 @@ -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; @@ -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 = `
<div class="audio-toggle audio-ico tgico-largeplay"></div>
<div class="audio-download"><div class="tgico-download"></div></div>
<div class="audio-time">${durationStr}</div>
`;
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<Blob>;
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', `
<rect x="${index * 4}" y="${23 - height}" width="2" height="${height}" rx="1" ry="1"></rect>
<rect x="${index * 4}" y="${23 - height}" width="2" height="${height}" rx="1" ry="1"></rect>
`);
++index;
}
let onClick = () => {
if(!promise) {
if(downloadDiv.classList.contains('downloading')) {
@ -298,47 +299,47 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -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 { @@ -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 { @@ -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 @@ -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?: ( @@ -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;
}

74
src/lib/appManagers/appImManager.ts

@ -20,7 +20,7 @@ import appMessagesIDsManager from "./appMessagesIDsManager"; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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');

99
src/scss/partials/_chat.scss

@ -250,6 +250,14 @@ @@ -250,6 +250,14 @@
opacity: 1;
}
}
.reply {
width: auto;
.reply-content {
height: auto;
}
}
&.photo, &.video {
width: min-content;
@ -426,7 +434,7 @@ @@ -426,7 +434,7 @@
}
}
.box {
.box, .reply {
font-size: .95rem;
// margin: .25rem;
margin: 4px 4px 4px 6px;
@ -498,43 +506,45 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -920,7 +933,7 @@
}
&-toggle, &-download {
background-color: #68AB5A;
background-color: #4FAE4E;
}
}
}
@ -1089,7 +1102,14 @@ @@ -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 @@ @@ -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 @@ @@ -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;

8
src/scss/style.scss

@ -479,14 +479,14 @@ input { @@ -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 { @@ -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 { @@ -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 {

Loading…
Cancel
Save