From 1e4b34aca3d1b9094d23dcbeb94b940d4d086270 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sun, 26 Sep 2021 20:03:45 +0400 Subject: [PATCH] Added video messages to voice tab Some fixes --- src/components/appMediaPlaybackController.ts | 34 ++++---- src/components/appMediaViewer.ts | 43 +---------- src/components/appSearchSuper..ts | 57 ++++++++++---- src/components/audio.ts | 77 ++++++++++++------- src/components/chat/audio.ts | 34 +++++--- src/components/chat/pinnedContainer.ts | 11 ++- src/components/sidebarLeft/index.ts | 2 +- .../sidebarLeft/tabs/activeSessions.ts | 4 +- .../sidebarRight/tabs/sharedMedia.ts | 2 +- src/components/wrappers.ts | 51 ++++++------ src/helpers/date.ts | 60 ++++++++++----- src/lang.ts | 2 + src/lib/appManagers/appMessagesManager.ts | 33 +++++--- src/lib/langPack.ts | 9 ++- src/scss/partials/_audio.scss | 29 +++++-- src/scss/partials/_chatBubble.scss | 4 + src/scss/partials/_document.scss | 4 + src/scss/partials/_rightSidebar.scss | 9 ++- 18 files changed, 275 insertions(+), 190 deletions(-) diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index f79a53b9..cb94ac38 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -267,22 +267,24 @@ class AppMediaPlaybackController { } else if(isVoice) { const peerId = message.fromId || message.peerId; const peerPhoto = appPeersManager.getPeerPhoto(peerId); - const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small'); - if(result.cached) { - const url = await result.loadPromise; - artwork.push({ - src: url, - sizes: '160x160', - type: 'image/jpeg' - }); - } else { - result.loadPromise.then((url) => { - if(this.playingMedia !== playingMedia || !url) { - return; - } - - this.setNewMediadata(message); - }); + if(peerPhoto) { + const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small'); + if(result.cached) { + const url = await result.loadPromise; + artwork.push({ + src: url, + sizes: '160x160', + type: 'image/jpeg' + }); + } else { + result.loadPromise.then((url) => { + if(this.playingMedia !== playingMedia || !url) { + return; + } + + this.setNewMediadata(message); + }); + } } title = appPeersManager.getPeerTitle(peerId, true, false); diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index db3500b6..782de057 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -28,7 +28,7 @@ import ProgressivePreloader from "./preloader"; import Scrollable from "./scrollable"; import appSidebarRight from "./sidebarRight"; import SwipeHandler from "./swipeHandler"; -import { ONE_DAY } from "../helpers/date"; +import { formatFullSentTime, formatTime, ONE_DAY } from "../helpers/date"; import { SearchSuperContext } from "./appSearchSuper."; import appNavigationController from "./appNavigationController"; import { Message } from "../layer"; @@ -1185,46 +1185,7 @@ class AppMediaViewerBase${appMessagesManager.getSenderToPeerText(message)}` : ''; - - let titleAdditionHTML = ''; - if(this.showSender) { - titleAdditionHTML = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; - } - const row = new Row({ title, - titleRight: titleAdditionHTML, + titleRight: appMessagesManager.wrapSentTime(message), subtitle: subtitleFragment, havePadding: true, clickable: true, @@ -1257,7 +1268,7 @@ export default class AppSearchSuper { } // ! Фикс случая, когда не загружаются документы при открытой панели разработчиков (происходит из-за того, что не совпадают критерии отбора документов в getSearch) - if(value.history.length < loadCount) { + if(value.history.length < loadCount || (this.searchContext.folderId !== undefined && !value.next_rate) || value.history.length === value.count) { //if((value.count || history.length === value.count) && history.length >= value.count) { //this.log(logStr + 'loaded all media', value, loadCount); this.loaded[type] = true; @@ -1401,14 +1412,26 @@ export default class AppSearchSuper { const dateTimestamp = date.getTime(); const containers = this.monthContainers[type] ?? (this.monthContainers[type] = {}); if(!(dateTimestamp in containers)) { - const str = months[date.getMonth()] + ' ' + date.getFullYear(); - const container = document.createElement('div'); container.className = 'search-super-month'; const name = document.createElement('div'); name.classList.add('search-super-month-name'); - name.innerText = str; + + const options: Intl.DateTimeFormatOptions = { + month: 'long' + }; + + if(date.getFullYear() !== new Date().getFullYear()) { + options.year = 'numeric'; + } + + const dateElement = new I18n.IntlDateElement({ + date, + options + }).element; + name.append(dateElement); + container.append(name); const items = document.createElement('div'); diff --git a/src/components/audio.ts b/src/components/audio.ts index bc773f13..ca94e32c 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -5,8 +5,7 @@ */ import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager"; -import { RichTextProcessor } from "../lib/richtextprocessor"; -import { formatDate, wrapPhoto } from "./wrappers"; +import { wrapPhoto } from "./wrappers"; import ProgressivePreloader from "./preloader"; import { MediaProgressLine } from "../lib/mediaPlayer"; import appMediaPlaybackController, { MediaItem } from "./appMediaPlaybackController"; @@ -17,7 +16,6 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager"; import rootScope from "../lib/rootScope"; import './middleEllipsis'; import { SearchSuperContext } from "./appSearchSuper."; -import { formatDateAccordingToToday } from "../helpers/date"; import { cancelEvent } from "../helpers/dom/cancelEvent"; import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent"; import LazyLoadQueue from "./lazyLoadQueue"; @@ -25,6 +23,11 @@ import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromi import ListenerSetter, { Listener } from "../helpers/listenerSetter"; import noop from "../helpers/noop"; import findUpClassName from "../helpers/dom/findUpClassName"; +import { joinElementsWith } from "../lib/langPack"; +import { MiddleEllipsisElement } from "./middleEllipsis"; +import htmlToSpan from "../helpers/dom/htmlToSpan"; +import { formatFullSentTime } from "../helpers/date"; +import { formatBytes } from "../helpers/number"; rootScope.addEventListener('messages_media_read', ({mids, peerId}) => { mids.forEach(mid => { @@ -273,43 +276,56 @@ function wrapAudio(audioEl: AudioElement) { const message = audioEl.message; const doc: MyDocument = message.media.document || message.media.webpage.document; - const senderTitle = audioEl.showSender ? appMessagesManager.getSenderToPeerText(message) : ''; - - let title = doc.type === 'voice' ? senderTitle : (doc.audioTitle || doc.fileName); - let subtitle: string; + const isVoice = doc.type === 'voice' || doc.type === 'round'; + const descriptionEl = document.createElement('div'); + descriptionEl.classList.add('audio-description'); - if(doc.type === 'voice') { - subtitle = ''; - } else { - subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : ''; + if(!isVoice) { + const parts: (Node | string)[] = []; + if(doc.audioPerformer) { + parts.push(htmlToSpan(doc.audioPerformer)); + } + if(withTime) { - subtitle += (subtitle ? ' • ' : '') + formatDate(doc.date); - } else if(!subtitle) { - subtitle = 'Unknown Artist'; + parts.push(formatFullSentTime(doc.date)); + } else if(!parts.length) { + parts.push(formatBytes(doc.size)); } if(audioEl.showSender) { - subtitle += ' • ' + senderTitle; - } else { - subtitle = ' • ' + subtitle; + parts.push(appMessagesManager.wrapSenderToPeer(message)); } - } - let titleAdditionHTML = ''; - if(audioEl.showSender) { - titleAdditionHTML = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; + descriptionEl.append(...joinElementsWith(parts, ' • ')); } const html = `
-
${title}${titleAdditionHTML}
-
${subtitle || '
'}
+
+
`; - audioEl.insertAdjacentHTML('beforeend', html); + const titleEl = audioEl.querySelector('.audio-title') as HTMLElement; + + const middleEllipsisEl = new MiddleEllipsisElement(); + middleEllipsisEl.dataset.fontWeight = audioEl.dataset.fontWeight; + if(isVoice) { + middleEllipsisEl.append(appMessagesManager.wrapSenderToPeer(message)); + } else { + middleEllipsisEl.innerHTML = doc.audioTitle || doc.fileName; + } + + titleEl.append(middleEllipsisEl); + + if(audioEl.showSender) { + titleEl.append(appMessagesManager.wrapSentTime(message)); + } + + const subtitleDiv = audioEl.querySelector('.audio-subtitle') as HTMLDivElement; + subtitleDiv.append(descriptionEl); + const onLoad = () => { - const subtitleDiv = audioEl.querySelector('.audio-subtitle') as HTMLDivElement; let launched = false; let progressLine = new MediaProgressLine(audioEl.audio, doc.supportsStreaming); @@ -317,7 +333,7 @@ function wrapAudio(audioEl: AudioElement) { audioEl.addAudioListener('ended', () => { audioEl.classList.remove('audio-show-progress'); // Reset subtitle - subtitleDiv.lastChild.replaceWith(subtitle); + subtitleDiv.lastChild.replaceWith(descriptionEl); launched = false; }); @@ -519,9 +535,9 @@ export default class AudioElement extends HTMLElement { onClick(); } } else { - if(doc.supportsStreaming) { + // if(doc.supportsStreaming) { onLoad(false); - } + // } if(doc.thumbs) { const imgs: HTMLImageElement[] = []; @@ -606,6 +622,8 @@ export default class AudioElement extends HTMLElement { this.listenerSetter.remove(pauseListener); }); } else { + preloader = constructDownloadPreloader(); + const load = () => { const download = getDownloadPromise(); preloader.attach(downloadDiv, false, download); @@ -619,7 +637,10 @@ export default class AudioElement extends HTMLElement { this.append(downloadDiv); + this.classList.add('downloading'); + this.readyPromise.then(() => { + this.classList.remove('downloading'); downloadDiv.classList.add('downloaded'); setTimeout(() => { downloadDiv.remove(); diff --git a/src/components/chat/audio.ts b/src/components/chat/audio.ts index faef4323..c2b1201b 100644 --- a/src/components/chat/audio.ts +++ b/src/components/chat/audio.ts @@ -6,30 +6,40 @@ import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; import type ChatTopbar from "./topbar"; -import { RichTextProcessor } from "../../lib/richtextprocessor"; import rootScope from "../../lib/rootScope"; import appMediaPlaybackController from "../appMediaPlaybackController"; import DivAndCaption from "../divAndCaption"; -import { formatDate } from "../wrappers"; import PinnedContainer from "./pinnedContainer"; import Chat from "./chat"; import { cancelEvent } from "../../helpers/dom/cancelEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent"; import replaceContent from "../../helpers/dom/replaceContent"; import PeerTitle from "../peerTitle"; +import { i18n } from "../../lib/langPack"; +import { formatFullSentTime } from "../../helpers/date"; export default class ChatAudio extends PinnedContainer { private toggleEl: HTMLElement; constructor(protected topbar: ChatTopbar, protected chat: Chat, protected appMessagesManager: AppMessagesManager) { - super(topbar, chat, topbar.listenerSetter, 'audio', new DivAndCaption('pinned-audio', (title: string | HTMLElement, subtitle: string | HTMLElement) => { - replaceContent(this.divAndCaption.title, title); - replaceContent(this.divAndCaption.subtitle, subtitle); - }), () => { - if(this.toggleEl.classList.contains('flip-icon')) { - appMediaPlaybackController.toggle(); + super( + topbar, + chat, + topbar.listenerSetter, + 'audio', + new DivAndCaption( + 'pinned-audio', + (title: string | HTMLElement | DocumentFragment, subtitle: string | HTMLElement | DocumentFragment) => { + replaceContent(this.divAndCaption.title, title); + replaceContent(this.divAndCaption.subtitle, subtitle); + } + ), + () => { + if(this.toggleEl.classList.contains('flip-icon')) { + appMediaPlaybackController.toggle(); + } } - }); + ); this.divAndCaption.border.remove(); @@ -45,16 +55,16 @@ export default class ChatAudio extends PinnedContainer { this.topbar.listenerSetter.add(rootScope)('audio_play', (e) => { const {doc, mid, peerId} = e; - let title: string | HTMLElement, subtitle: string; + let title: string | HTMLElement, subtitle: string | HTMLElement | DocumentFragment; const message = appMessagesManager.getMessageByPeer(peerId, mid); if(doc.type === 'voice' || doc.type === 'round') { title = new PeerTitle({peerId: message.fromId}).element; //subtitle = 'Voice message'; - subtitle = formatDate(message.date, false, false); + subtitle = formatFullSentTime(message.date); } else { title = doc.audioTitle || doc.fileName; - subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; + subtitle = doc.audioPerformer || i18n('AudioUnknownArtist'); } this.fill(title, subtitle, message); diff --git a/src/components/chat/pinnedContainer.ts b/src/components/chat/pinnedContainer.ts index 3349bc6f..0b6d9fa6 100644 --- a/src/components/chat/pinnedContainer.ts +++ b/src/components/chat/pinnedContainer.ts @@ -22,7 +22,14 @@ export default class PinnedContainer { private close: HTMLElement; protected wrapper: HTMLElement; - constructor(protected topbar: ChatTopbar, protected chat: Chat, public listenerSetter: ListenerSetter, protected className: string, public divAndCaption: DivAndCaption<(title: string | HTMLElement, subtitle: string | HTMLElement, message?: any) => void>, onClose?: () => void | Promise) { + constructor( + protected topbar: ChatTopbar, + protected chat: Chat, + public listenerSetter: ListenerSetter, + protected className: string, + public divAndCaption: DivAndCaption<(title: string | HTMLElement | DocumentFragment, subtitle: string | HTMLElement | DocumentFragment, message?: any) => void>, + onClose?: () => void | Promise + ) { /* const prev = this.divAndCaption.fill; this.divAndCaption.fill = (mid, title, subtitle) => { this.divAndCaption.container.dataset.mid = '' + mid; @@ -87,7 +94,7 @@ export default class PinnedContainer { this.topbar.setUtilsWidth(); } - public fill(title: string | HTMLElement, subtitle: string | HTMLElement, message: any) { + public fill(title: string | HTMLElement | DocumentFragment, subtitle: string | HTMLElement | DocumentFragment, message: any) { this.divAndCaption.container.dataset.peerId = '' + message.peerId; this.divAndCaption.container.dataset.mid = '' + message.mid; this.divAndCaption.fill(title, subtitle, message); diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 247a3aa2..6198e218 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -297,7 +297,7 @@ export class AppSidebarLeft extends SidebarSlider { name: 'SharedMusicTab2', type: 'music' }, { - inputFilter: 'inputMessagesFilterVoice', + inputFilter: 'inputMessagesFilterRoundVoice', name: 'SharedVoiceTab2', type: 'voice' }], diff --git a/src/components/sidebarLeft/tabs/activeSessions.ts b/src/components/sidebarLeft/tabs/activeSessions.ts index 1e1225d6..3372459d 100644 --- a/src/components/sidebarLeft/tabs/activeSessions.ts +++ b/src/components/sidebarLeft/tabs/activeSessions.ts @@ -9,7 +9,7 @@ import { SettingSection } from ".."; import Button from "../../button"; import Row from "../../row"; import { Authorization } from "../../../layer"; -import { formatDateAccordingToToday } from "../../../helpers/date"; +import { formatDateAccordingToTodayNew } from "../../../helpers/date"; import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc"; import ButtonMenu from "../../buttonMenu"; import apiManager from "../../../lib/mtproto/mtprotoworker"; @@ -35,7 +35,7 @@ export default class AppActiveSessionsTab extends SliderSuperTab { title: [auth.app_name, auth.app_version].join(' '), subtitle: [auth.ip, auth.country].join(' - '), clickable: true, - titleRight: auth.pFlags.current ? undefined : formatDateAccordingToToday(new Date(Math.max(auth.date_active, auth.date_created) * 1000)) + titleRight: auth.pFlags.current ? undefined : formatDateAccordingToTodayNew(new Date(Math.max(auth.date_active, auth.date_created) * 1000)) }); row.container.dataset.hash = auth.hash; diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 2d7957b9..3803d18a 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -862,7 +862,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { name: 'SharedMusicTab2', type: 'music' }, { - inputFilter: 'inputMessagesFilterVoice', + inputFilter: 'inputMessagesFilterRoundVoice', name: 'SharedVoiceTab2', type: 'voice' }], diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 6127fa34..d9a3b327 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -8,7 +8,7 @@ import type Chat from './chat/chat'; import { getEmojiToneIndex } from '../vendor/emoji'; import { readBlobAsText } from '../helpers/blob'; import { deferredPromise } from '../helpers/cancellablePromise'; -import { formatDateAccordingToToday, months } from '../helpers/date'; +import { formatFullSentTime } from '../helpers/date'; import mediaSizes, { ScreenSize } from '../helpers/mediaSizes'; import { formatBytes } from '../helpers/number'; import { IS_SAFARI } from '../environment/userAgent'; @@ -46,6 +46,8 @@ import { clearBadCharsAndTrim } from '../helpers/cleanSearchText'; import blur from '../helpers/blur'; import IS_WEBP_SUPPORTED from '../environment/webpSupport'; import MEDIA_MIME_TYPES_SUPPORTED from '../environment/mediaMimeTypesSupport'; +import { MiddleEllipsisElement } from './middleEllipsis'; +import { joinElementsWith } from '../lib/langPack'; const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB @@ -487,20 +489,6 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai return res; } -export const formatDate = (timestamp: number, monthShort = false, withYear = true) => { - const date = new Date(timestamp * 1000); - - let month = months[date.getMonth()]; - if(monthShort) month = month.slice(0, 3); - - let str = month + ' ' + date.getDate(); - if(withYear) { - str += ', ' + date.getFullYear(); - } - - return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); -}; - rootScope.addEventListener('download_start', (docId) => { const elements = Array.from(document.querySelectorAll(`.document[data-doc-id="${docId}"]`)) as HTMLElement[]; elements.forEach(element => { @@ -525,7 +513,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS const doc = (message.media.document || message.media.webpage.document) as MyDocument; const uploading = message.pFlags.is_outgoing && message.media?.preloader; - if(doc.type === 'audio' || doc.type === 'voice') { + if(doc.type === 'audio' || doc.type === 'voice' || doc.type === 'round') { const audioElement = new AudioElement(); audioElement.dataset.mid = '' + message.mid; audioElement.dataset.peerId = '' + message.peerId; @@ -589,27 +577,38 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS //let fileName = stringMiddleOverflow(doc.file_name || 'Unknown.file', 26); let fileName = doc.fileName || 'Unknown.file'; - let size = formatBytes(doc.size); + const descriptionEl = document.createElement('div'); + descriptionEl.classList.add('document-description'); + const descriptionParts: (HTMLElement | string | DocumentFragment)[] = [formatBytes(doc.size)]; if(withTime) { - size += ' · ' + formatDate(doc.date); + descriptionParts.push(formatFullSentTime(doc.date)); } if(showSender) { - size += ' · ' + appMessagesManager.getSenderToPeerText(message); + descriptionParts.push(appMessagesManager.wrapSenderToPeer(message)); } - let titleAdditionHTML = ''; - if(showSender) { - titleAdditionHTML = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; - } - docDiv.innerHTML = ` ${cacheContext.downloaded && !uploading ? '' : `
`} -
${fileName}${titleAdditionHTML}
-
${size}
+
+
`; + const nameDiv = docDiv.querySelector('.document-name') as HTMLElement; + const middleEllipsisEl = new MiddleEllipsisElement(); + middleEllipsisEl.dataset.fontWeight = '' + fontWeight; + middleEllipsisEl.innerHTML = fileName; + + nameDiv.append(middleEllipsisEl); + + if(showSender) { + nameDiv.append(appMessagesManager.wrapSentTime(message)); + } + + const sizeDiv = docDiv.querySelector('.document-size') as HTMLElement; + sizeDiv.append(...joinElementsWith(descriptionParts, ' · ')); + docDiv.prepend(icoDiv); if(!uploading && message.pFlags.is_outgoing) { diff --git a/src/helpers/date.ts b/src/helpers/date.ts index 890f172c..1fd0583c 100644 --- a/src/helpers/date.ts +++ b/src/helpers/date.ts @@ -5,7 +5,8 @@ */ import { MOUNT_CLASS_TO } from "../config/debug"; -import I18n from "../lib/langPack"; +import I18n, { i18n } from "../lib/langPack"; +import { capitalizeFirstLetter } from "./string"; export const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; export const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; @@ -21,25 +22,6 @@ export const getWeekNumber = (date: Date) => { return Math.ceil((((d.getTime() - yearStart.getTime()) / ONE_DAY) + 1) / 7); }; -export const formatDateAccordingToToday = (time: Date) => { - const date = new Date(); - const now = date.getTime() / 1000 | 0; - const timestamp = time.getTime() / 1000 | 0; - - let timeStr: string; - if((now - timestamp) < ONE_DAY && date.getDate() === time.getDate()) { // if the same day - timeStr = ('0' + time.getHours()).slice(-2) + ':' + ('0' + time.getMinutes()).slice(-2); - } else if(date.getFullYear() !== time.getFullYear()) { // different year - timeStr = time.getDate() + '.' + ('0' + (time.getMonth() + 1)).slice(-2) + '.' + ('' + time.getFullYear()).slice(-2); - } else if((now - timestamp) < (ONE_DAY * 7) && getWeekNumber(date) === getWeekNumber(time)) { // current week - timeStr = days[time.getDay()].slice(0, 3); - } else { // same year - timeStr = months[time.getMonth()].slice(0, 3) + ' ' + ('0' + time.getDate()).slice(-2); - } - - return timeStr; -}; - export function formatDateAccordingToTodayNew(time: Date) { const today = new Date(); const now = today.getTime() / 1000 | 0; @@ -64,6 +46,44 @@ export function formatDateAccordingToTodayNew(time: Date) { }).element; } +export function formatFullSentTime(timestamp: number) { + const date = new Date(); + const time = new Date(timestamp * 1000); + const now = date.getTime() / 1000; + + const timeEl = formatTime(time); + + let dateEl: Node | string; + if((now - timestamp) < ONE_DAY && date.getDate() === time.getDate()) { // if the same day + dateEl = i18n('Date.Today'); + } else if((now - timestamp) < (ONE_DAY * 2) && (date.getDate() - 1) === time.getDate()) { // yesterday + dateEl = capitalizeFirstLetter(I18n.format('Yesterday', true)); + } else if(date.getFullYear() !== time.getFullYear()) { // different year + dateEl = new I18n.IntlDateElement({ + date: time, + options: { + month: 'short', + day: 'numeric', + year: 'numeric' + } + }).element; + // dateStr = months[time.getMonth()].slice(0, 3) + ' ' + time.getDate() + ', ' + time.getFullYear(); + } else { + dateEl = new I18n.IntlDateElement({ + date: time, + options: { + month: 'short', + day: 'numeric' + } + }).element; + // dateStr = months[time.getMonth()].slice(0, 3) + ' ' + time.getDate(); + } + + const fragment = document.createDocumentFragment(); + fragment.append(dateEl, ' ', i18n('ScheduleController.at'), ' ', timeEl); + return fragment; +} + export function formatTime(date: Date) { return new I18n.IntlDateElement({ date, diff --git a/src/lang.ts b/src/lang.ts index ea814e22..2787b72c 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -584,6 +584,8 @@ const lang = { "AreYouSureBlockContact2": "Are you sure you want to block **%1$s**?", "UserBlocked": "User blocked", "UserUnblocked": "User unblocked", + "AudioUnknownArtist": "Unknown artist", + "AudioUnknownTitle": "Unknown title", // * macos "AccountSettings.Filters": "Chat Folders", diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 8a6030c4..eb89201b 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -12,7 +12,7 @@ import { LazyLoadQueueBase } from "../../components/lazyLoadQueue"; import ProgressivePreloader from "../../components/preloader"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; -import { formatTime, tsNow } from "../../helpers/date"; +import { formatDateAccordingToTodayNew, formatTime, tsNow } from "../../helpers/date"; import { createPosterForVideo } from "../../helpers/files"; import { copy, getObjectKeysAndSort } from "../../helpers/object"; import { randomLong } from "../../helpers/random"; @@ -2721,21 +2721,36 @@ export class AppMessagesManager { } } - public getSenderToPeerText(message: MyMessage) { - let senderTitle = '', peerTitle: string; + public wrapSenderToPeer(message: MyMessage) { + const senderTitle: HTMLElement = document.createElement('span'); + senderTitle.classList.add('sender-title'); - senderTitle = message.pFlags.out ? 'You' : appPeersManager.getPeerTitle(message.fromId, false, false); - peerTitle = appPeersManager.isAnyGroup(message.peerId) || (message.pFlags.out && message.peerId !== rootScope.myId) ? - appPeersManager.getPeerTitle(message.peerId, false, false) : - ''; + const fromMe = message.fromId === rootScope.myId && message.peerId !== rootScope.myId; + senderTitle.append( + fromMe ? + i18n('FromYou') : + new PeerTitle({ + peerId: message.fromId, + dialog: message.peerId === rootScope.myId + }).element + ); - if(peerTitle) { - senderTitle += ' ➝ ' + peerTitle; + if(appPeersManager.isAnyGroup(message.peerId) || fromMe) { + const peerTitle = new PeerTitle({peerId: message.peerId}).element; + senderTitle.append(' ➝ ', peerTitle); } return senderTitle; } + public wrapSentTime(message: MyMessage) { + const el: HTMLElement = document.createElement('span'); + el.classList.add('sent-time'); + el.append(formatDateAccordingToTodayNew(new Date(message.date * 1000))); + + return el; + } + public wrapMessageActionTextNew(message: any, plain: true): string; public wrapMessageActionTextNew(message: any, plain?: false): HTMLElement; public wrapMessageActionTextNew(message: any, plain: boolean): HTMLElement | string; diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 926b6b43..2e7c0eaa 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -448,16 +448,19 @@ export {i18n_}; const _i18n = I18n._i18n; export {_i18n}; -export function join(elements: (Node | string)[], useLast = true) { +export function joinElementsWith(elements: (Node | string)[], joiner: typeof elements[0] | ((isLast: boolean) => typeof elements[0])) { const arr = elements.slice(0, 1); for(let i = 1; i < elements.length; ++i) { const isLast = (elements.length - 1) === i; - const delimiterKey: LangPackKey = isLast && useLast ? 'WordDelimiterLast' : 'WordDelimiter'; - arr.push(i18n(delimiterKey)); + arr.push(typeof(joiner) === 'function' ? joiner(isLast) : joiner); arr.push(elements[i]); } return arr; } +export function join(elements: (Node | string)[], useLast = true) { + return joinElementsWith(elements, (isLast) => i18n(isLast && useLast ? 'WordDelimiterLast' : 'WordDelimiter')); +} + MOUNT_CLASS_TO.I18n = I18n; diff --git a/src/scss/partials/_audio.scss b/src/scss/partials/_audio.scss index e05398b4..0f853c02 100644 --- a/src/scss/partials/_audio.scss +++ b/src/scss/partials/_audio.scss @@ -454,9 +454,6 @@ &-subtitle { font-size: .875rem; color: var(--secondary-text-color); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; display: flex; @include respond-to(handhelds) { @@ -464,6 +461,12 @@ } } + &-title, + &-time, + &-subtitle { + @include text-overflow(); + } + // * for audio &-subtitle { align-items: center; @@ -483,12 +486,9 @@ // * for audio &-title, &:not(.audio-show-progress) &-subtitle { - white-space: nowrap; - overflow: hidden; max-width: 100%; - text-overflow: ellipsis; } - + &.is-voice { .audio-time { line-height: 1; @@ -535,9 +535,10 @@ .audio-play-icon { z-index: 1; background-color: transparent; + opacity: 1; @include animation-level(2) { - transition: transform .25 ease-in-out, background-color .2s ease-in-out; + transition: transform .25s ease-in-out, background-color .2s ease-in-out, opacity .2s ease-in-out; } .part { @@ -555,5 +556,17 @@ width: inherit; height: inherit; } + + &:not(.corner-download) { + .audio-download { + background-color: rgba(0, 0, 0, .3); + } + + &.downloading { + .audio-play-icon { + opacity: 0; + } + } + } } } diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 05dda5b1..c7c8d56b 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1691,6 +1691,10 @@ $bubble-margin: .25rem; .replies { user-select: none; + .c-ripple__circle { + background-color: var(--light-primary-color); + } + .rp { width: 100%; height: 100%; diff --git a/src/scss/partials/_document.scss b/src/scss/partials/_document.scss index 72930656..c1167d77 100644 --- a/src/scss/partials/_document.scss +++ b/src/scss/partials/_document.scss @@ -189,6 +189,10 @@ //transform: scale(0); } } + + &-description { + @include text-overflow(); + } &:not(.corner-download) .preloader-container:not(.preloader-streamable) { transform: scale(1) !important; diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index b779aff7..55f09dcf 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -514,16 +514,17 @@ white-space: pre-wrap; text-overflow: ellipsis; word-break: break-word; - - &.sender { - margin-top: .125rem; - } } .sent-time { margin: 1px 0 0; } + .sender-title { + display: block; + margin-top: .25rem; + } + .checkbox-field { padding: 0 !important; margin: 2rem 0 0 -1.75rem !important;