Browse Source
Closing by escape Avatar & new media are now popups Fix deleting message Edit single item from album Fix message text in album Fix overflow in pinned subtitlemaster
morethanwords
4 years ago
16 changed files with 502 additions and 449 deletions
@ -0,0 +1,50 @@ |
|||||||
|
import { formatNumber } from "../../lib/utils"; |
||||||
|
|
||||||
|
type Message = any; |
||||||
|
|
||||||
|
export namespace MessageRender { |
||||||
|
/* export const setText = () => { |
||||||
|
|
||||||
|
}; */ |
||||||
|
|
||||||
|
export const setTime = (message: Message, bubble: HTMLElement, bubbleContainer: HTMLElement, messageDiv: HTMLElement) => { |
||||||
|
let date = new Date(message.date * 1000); |
||||||
|
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2); |
||||||
|
|
||||||
|
if(message.views) { |
||||||
|
bubble.classList.add('channel-post'); |
||||||
|
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time; |
||||||
|
|
||||||
|
if(!message.savedFrom) { |
||||||
|
let forward = document.createElement('div'); |
||||||
|
forward.classList.add('bubble-beside-button', 'forward'); |
||||||
|
forward.innerHTML = ` |
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"> |
||||||
|
<defs> |
||||||
|
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path> |
||||||
|
</defs> |
||||||
|
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use> |
||||||
|
</svg>`;
|
||||||
|
bubbleContainer.append(forward); |
||||||
|
bubble.classList.add('with-beside-button'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(message.edit_date) { |
||||||
|
bubble.classList.add('is-edited'); |
||||||
|
time = '<i class="edited">edited</i> ' + time; |
||||||
|
} |
||||||
|
|
||||||
|
let timeSpan = document.createElement('span'); |
||||||
|
timeSpan.classList.add('time'); |
||||||
|
|
||||||
|
let timeInner = document.createElement('div'); |
||||||
|
timeInner.classList.add('inner', 'tgico'); |
||||||
|
timeInner.innerHTML = time; |
||||||
|
|
||||||
|
timeSpan.appendChild(timeInner); |
||||||
|
messageDiv.append(timeSpan); |
||||||
|
|
||||||
|
return timeSpan; |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
const InputField = (placeholder: string, label: string, name: string) => { |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.classList.add('input-field'); |
||||||
|
|
||||||
|
div.innerHTML = ` |
||||||
|
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required=""> |
||||||
|
<label for="input-${name}">${label}</label> |
||||||
|
`;
|
||||||
|
|
||||||
|
return div; |
||||||
|
}; |
||||||
|
|
||||||
|
export default InputField; |
@ -0,0 +1,285 @@ |
|||||||
|
import { isTouchSupported } from "../helpers/touchSupport"; |
||||||
|
import appImManager from "../lib/appManagers/appImManager"; |
||||||
|
import appMessagesManager from "../lib/appManagers/appMessagesManager"; |
||||||
|
import { calcImageInBox } from "../lib/utils"; |
||||||
|
import { Layouter, RectPart } from "./groupedLayout"; |
||||||
|
import InputField from "./inputField"; |
||||||
|
import { PopupElement } from "./popup"; |
||||||
|
import { ripple } from "./ripple"; |
||||||
|
import { wrapDocument } from "./wrappers"; |
||||||
|
|
||||||
|
type SendFileParams = Partial<{ |
||||||
|
file: File, |
||||||
|
objectURL: string, |
||||||
|
width: number, |
||||||
|
height: number, |
||||||
|
duration: number |
||||||
|
}>; |
||||||
|
|
||||||
|
export default class PopupNewMedia extends PopupElement { |
||||||
|
private btnSend: HTMLElement; |
||||||
|
private input: HTMLInputElement; |
||||||
|
private mediaContainer: HTMLElement; |
||||||
|
|
||||||
|
private willAttach: Partial<{ |
||||||
|
type: 'media' | 'document', |
||||||
|
isMedia: boolean, |
||||||
|
sendFileDetails: SendFileParams[] |
||||||
|
}> = { |
||||||
|
sendFileDetails: [] |
||||||
|
}; |
||||||
|
|
||||||
|
constructor(files: File[], willAttachType: PopupNewMedia['willAttach']['type']) { |
||||||
|
super('popup-send-photo popup-new-media', null, {closable: true}); |
||||||
|
|
||||||
|
this.willAttach.type = willAttachType; |
||||||
|
|
||||||
|
this.btnSend = document.createElement('button'); |
||||||
|
this.btnSend.className = 'btn-primary'; |
||||||
|
this.btnSend.innerText = 'SEND'; |
||||||
|
ripple(this.btnSend); |
||||||
|
this.btnSend.addEventListener('click', this.send, {once: true}); |
||||||
|
|
||||||
|
this.header.append(this.btnSend); |
||||||
|
|
||||||
|
this.mediaContainer = document.createElement('div'); |
||||||
|
this.mediaContainer.classList.add('popup-photo'); |
||||||
|
|
||||||
|
const inputField = InputField('Add a caption...', 'Caption', 'photo-caption'); |
||||||
|
this.input = inputField.firstElementChild as HTMLInputElement; |
||||||
|
this.container.append(this.mediaContainer, inputField); |
||||||
|
|
||||||
|
this.attachFiles(files); |
||||||
|
} |
||||||
|
|
||||||
|
private onKeyDown = (e: KeyboardEvent) => { |
||||||
|
const target = e.target as HTMLElement; |
||||||
|
if(target.tagName != 'INPUT') { |
||||||
|
this.input.focus(); |
||||||
|
} |
||||||
|
|
||||||
|
if(e.key == 'Enter' && !isTouchSupported) { |
||||||
|
this.btnSend.click(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public send = () => { |
||||||
|
this.destroy(); |
||||||
|
let caption = this.input.value; |
||||||
|
const willAttach = this.willAttach; |
||||||
|
willAttach.isMedia = willAttach.type == 'media'; |
||||||
|
|
||||||
|
//console.log('will send files with options:', willAttach);
|
||||||
|
|
||||||
|
const peerID = appImManager.peerID; |
||||||
|
const chatInputC = appImManager.chatInputC; |
||||||
|
|
||||||
|
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) { |
||||||
|
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({ |
||||||
|
caption, |
||||||
|
replyToMsgID: chatInputC.replyToMsgID |
||||||
|
}, willAttach)); |
||||||
|
} else { |
||||||
|
if(caption) { |
||||||
|
if(willAttach.sendFileDetails.length > 1) { |
||||||
|
appMessagesManager.sendText(peerID, caption, {replyToMsgID: chatInputC.replyToMsgID}); |
||||||
|
caption = ''; |
||||||
|
chatInputC.replyToMsgID = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const promises = willAttach.sendFileDetails.map(params => { |
||||||
|
const promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({ |
||||||
|
//isMedia: willAttach.isMedia,
|
||||||
|
isMedia: params.file.type.includes('audio/') || willAttach.isMedia, |
||||||
|
caption, |
||||||
|
replyToMsgID: chatInputC.replyToMsgID |
||||||
|
}, params)); |
||||||
|
|
||||||
|
caption = ''; |
||||||
|
chatInputC.replyToMsgID = 0; |
||||||
|
return promise; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
//Promise.all(promises);
|
||||||
|
|
||||||
|
//appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach);
|
||||||
|
|
||||||
|
chatInputC.onMessageSent(); |
||||||
|
}; |
||||||
|
|
||||||
|
public attachFile = (file: File) => { |
||||||
|
const willAttach = this.willAttach; |
||||||
|
return new Promise<HTMLDivElement>((resolve) => { |
||||||
|
const params: SendFileParams = {}; |
||||||
|
params.file = file; |
||||||
|
//console.log('selected file:', file, typeof(file), willAttach);
|
||||||
|
const itemDiv = document.createElement('div'); |
||||||
|
switch(willAttach.type) { |
||||||
|
case 'media': { |
||||||
|
const isVideo = file.type.indexOf('video/') === 0; |
||||||
|
|
||||||
|
itemDiv.classList.add('popup-item-media'); |
||||||
|
|
||||||
|
if(isVideo) { |
||||||
|
const video = document.createElement('video'); |
||||||
|
const source = document.createElement('source'); |
||||||
|
source.src = params.objectURL = URL.createObjectURL(file); |
||||||
|
video.autoplay = false; |
||||||
|
video.controls = false; |
||||||
|
video.muted = true; |
||||||
|
video.setAttribute('playsinline', ''); |
||||||
|
|
||||||
|
video.onloadeddata = () => { |
||||||
|
params.width = video.videoWidth; |
||||||
|
params.height = video.videoHeight; |
||||||
|
params.duration = Math.floor(video.duration); |
||||||
|
|
||||||
|
itemDiv.append(video); |
||||||
|
resolve(itemDiv); |
||||||
|
}; |
||||||
|
|
||||||
|
video.append(source); |
||||||
|
} else { |
||||||
|
const img = new Image(); |
||||||
|
img.src = params.objectURL = URL.createObjectURL(file); |
||||||
|
img.onload = () => { |
||||||
|
params.width = img.naturalWidth; |
||||||
|
params.height = img.naturalHeight; |
||||||
|
|
||||||
|
itemDiv.append(img); |
||||||
|
resolve(itemDiv); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case 'document': { |
||||||
|
const isPhoto = file.type.indexOf('image/') !== -1; |
||||||
|
if(isPhoto) { |
||||||
|
params.objectURL = URL.createObjectURL(file); |
||||||
|
} |
||||||
|
|
||||||
|
const docDiv = wrapDocument({ |
||||||
|
file: file, |
||||||
|
file_name: file.name || '', |
||||||
|
size: file.size, |
||||||
|
type: isPhoto ? 'photo' : 'doc', |
||||||
|
url: params.objectURL |
||||||
|
} as any, false, true); |
||||||
|
|
||||||
|
const finish = () => { |
||||||
|
itemDiv.append(docDiv); |
||||||
|
resolve(itemDiv); |
||||||
|
}; |
||||||
|
|
||||||
|
if(isPhoto) { |
||||||
|
const img = new Image(); |
||||||
|
img.src = params.objectURL; |
||||||
|
img.onload = () => { |
||||||
|
params.width = img.naturalWidth; |
||||||
|
params.height = img.naturalHeight; |
||||||
|
|
||||||
|
finish(); |
||||||
|
}; |
||||||
|
|
||||||
|
img.onerror = finish; |
||||||
|
} else { |
||||||
|
finish(); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
willAttach.sendFileDetails.push(params); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
public attachFiles(files: File[]) { |
||||||
|
const container = this.container; |
||||||
|
const willAttach = this.willAttach; |
||||||
|
|
||||||
|
files = files.filter(file => { |
||||||
|
if(willAttach.type == 'media') { |
||||||
|
return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0); |
||||||
|
} else { |
||||||
|
return true; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if(files.length) { |
||||||
|
if(willAttach.type == 'document') { |
||||||
|
this.title.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File'); |
||||||
|
container.classList.add('is-document'); |
||||||
|
} else { |
||||||
|
container.classList.add('is-media'); |
||||||
|
|
||||||
|
let foundPhotos = 0; |
||||||
|
let foundVideos = 0; |
||||||
|
files.forEach(file => { |
||||||
|
if(file.type.indexOf('image/') === 0) ++foundPhotos; |
||||||
|
else if(file.type.indexOf('video/') === 0) ++foundVideos; |
||||||
|
}); |
||||||
|
|
||||||
|
if(foundPhotos && foundVideos) { |
||||||
|
this.title.innerText = 'Send Album'; |
||||||
|
} else if(foundPhotos) { |
||||||
|
this.title.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo'); |
||||||
|
} else if(foundVideos) { |
||||||
|
this.title.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Promise.all(files.map(this.attachFile)).then(results => { |
||||||
|
if(willAttach.type == 'media') { |
||||||
|
if(willAttach.sendFileDetails.length > 1) { |
||||||
|
container.classList.add('is-album'); |
||||||
|
|
||||||
|
const layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4); |
||||||
|
const layout = layouter.layout(); |
||||||
|
|
||||||
|
for(const {geometry, sides} of layout) { |
||||||
|
const div = results.shift(); |
||||||
|
|
||||||
|
div.style.width = geometry.width + 'px'; |
||||||
|
div.style.height = geometry.height + 'px'; |
||||||
|
div.style.top = geometry.y + 'px'; |
||||||
|
div.style.left = geometry.x + 'px'; |
||||||
|
|
||||||
|
if(sides & RectPart.Right) { |
||||||
|
this.mediaContainer.style.width = geometry.width + geometry.x + 'px'; |
||||||
|
} |
||||||
|
|
||||||
|
if(sides & RectPart.Bottom) { |
||||||
|
this.mediaContainer.style.height = geometry.height + geometry.y + 'px'; |
||||||
|
} |
||||||
|
|
||||||
|
this.mediaContainer.append(div); |
||||||
|
} |
||||||
|
|
||||||
|
//console.log('chatInput album layout:', layout);
|
||||||
|
} else { |
||||||
|
const params = willAttach.sendFileDetails[0]; |
||||||
|
const div = results[0]; |
||||||
|
const {w, h} = calcImageInBox(params.width, params.height, 380, 320); |
||||||
|
div.style.width = w + 'px'; |
||||||
|
div.style.height = h + 'px'; |
||||||
|
this.mediaContainer.append(div); |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.mediaContainer.append(...results); |
||||||
|
} |
||||||
|
|
||||||
|
// show now
|
||||||
|
document.body.addEventListener('keydown', this.onKeyDown); |
||||||
|
this.onClose = () => { |
||||||
|
document.body.removeEventListener('keydown', this.onKeyDown); |
||||||
|
}; |
||||||
|
this.show(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue