Browse Source

Added video messages to voice tab

Some fixes
master
morethanwords 3 years ago
parent
commit
1e4b34aca3
  1. 2
      src/components/appMediaPlaybackController.ts
  2. 43
      src/components/appMediaViewer.ts
  3. 57
      src/components/appSearchSuper..ts
  4. 75
      src/components/audio.ts
  5. 26
      src/components/chat/audio.ts
  6. 11
      src/components/chat/pinnedContainer.ts
  7. 2
      src/components/sidebarLeft/index.ts
  8. 4
      src/components/sidebarLeft/tabs/activeSessions.ts
  9. 2
      src/components/sidebarRight/tabs/sharedMedia.ts
  10. 51
      src/components/wrappers.ts
  11. 60
      src/helpers/date.ts
  12. 2
      src/lang.ts
  13. 33
      src/lib/appManagers/appMessagesManager.ts
  14. 9
      src/lib/langPack.ts
  15. 27
      src/scss/partials/_audio.scss
  16. 4
      src/scss/partials/_chatBubble.scss
  17. 4
      src/scss/partials/_document.scss
  18. 9
      src/scss/partials/_rightSidebar.scss

2
src/components/appMediaPlaybackController.ts

@ -267,6 +267,7 @@ class AppMediaPlaybackController {
} else if(isVoice) { } else if(isVoice) {
const peerId = message.fromId || message.peerId; const peerId = message.fromId || message.peerId;
const peerPhoto = appPeersManager.getPeerPhoto(peerId); const peerPhoto = appPeersManager.getPeerPhoto(peerId);
if(peerPhoto) {
const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small'); const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small');
if(result.cached) { if(result.cached) {
const url = await result.loadPromise; const url = await result.loadPromise;
@ -284,6 +285,7 @@ class AppMediaPlaybackController {
this.setNewMediadata(message); this.setNewMediadata(message);
}); });
} }
}
title = appPeersManager.getPeerTitle(peerId, true, false); title = appPeersManager.getPeerTitle(peerId, true, false);
artist = I18n.format(doc.type === 'voice' ? 'AttachAudio' : 'AttachRound', true); artist = I18n.format(doc.type === 'voice' ? 'AttachAudio' : 'AttachRound', true);

43
src/components/appMediaViewer.ts

@ -28,7 +28,7 @@ import ProgressivePreloader from "./preloader";
import Scrollable from "./scrollable"; import Scrollable from "./scrollable";
import appSidebarRight from "./sidebarRight"; import appSidebarRight from "./sidebarRight";
import SwipeHandler from "./swipeHandler"; import SwipeHandler from "./swipeHandler";
import { ONE_DAY } from "../helpers/date"; import { formatFullSentTime, formatTime, ONE_DAY } from "../helpers/date";
import { SearchSuperContext } from "./appSearchSuper."; import { SearchSuperContext } from "./appSearchSuper.";
import appNavigationController from "./appNavigationController"; import appNavigationController from "./appNavigationController";
import { Message } from "../layer"; import { Message } from "../layer";
@ -1185,46 +1185,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
} }
protected setAuthorInfo(fromId: number, timestamp: number) { protected setAuthorInfo(fromId: number, timestamp: number) {
const date = new Date(); this.author.date.append(formatFullSentTime(timestamp));
const time = new Date(timestamp * 1000);
const now = date.getTime() / 1000;
const timeEl = new I18n.IntlDateElement({
date: time,
options: {
hour: '2-digit',
minute: '2-digit'
}
}).element;
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();
}
this.author.date.innerHTML = '';
this.author.date.append(dateEl, ' ', i18n('ScheduleController.at'), ' ', timeEl);
replaceContent(this.author.nameEl, new PeerTitle({ replaceContent(this.author.nameEl, new PeerTitle({
peerId: fromId, peerId: fromId,

57
src/components/appSearchSuper..ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { formatDateAccordingToToday, months } from "../helpers/date"; import { months } from "../helpers/date";
import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object"; import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object";
import { escapeRegExp, limitSymbols } from "../helpers/string"; import { escapeRegExp, limitSymbols } from "../helpers/string";
import appChatsManager from "../lib/appManagers/appChatsManager"; import appChatsManager from "../lib/appManagers/appChatsManager";
@ -26,7 +26,7 @@ import { ripple } from "./ripple";
import Scrollable, { ScrollableX } from "./scrollable"; import Scrollable, { ScrollableX } from "./scrollable";
import { wrapDocument, wrapPhoto, wrapVideo } from "./wrappers"; import { wrapDocument, wrapPhoto, wrapVideo } from "./wrappers";
import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck"; import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
import { LangPackKey, i18n } from "../lib/langPack"; import I18n, { LangPackKey, i18n } from "../lib/langPack";
import findUpClassName from "../helpers/dom/findUpClassName"; import findUpClassName from "../helpers/dom/findUpClassName";
import { getMiddleware } from "../helpers/middleware"; import { getMiddleware } from "../helpers/middleware";
import appProfileManager from "../lib/appManagers/appProfileManager"; import appProfileManager from "../lib/appManagers/appProfileManager";
@ -50,6 +50,7 @@ import htmlToDocumentFragment from "../helpers/dom/htmlToDocumentFragment";
import { SearchSelection } from "./chat/selection"; import { SearchSelection } from "./chat/selection";
import { cancelEvent } from "../helpers/dom/cancelEvent"; import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent"; import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent";
import { MyDocument } from "../lib/appManagers/appDocsManager";
//const testScroll = false; //const testScroll = false;
@ -599,6 +600,18 @@ export default class AppSearchSuper {
break; break;
} }
case 'inputMessagesFilterRoundVoice': {
for(let message of messages) {
if(!message.media.document || !(['voice', 'round'] as MyDocument['type'][]).includes(message.media.document.type)) {
continue;
}
filtered.push(message);
}
break;
}
default: default:
break; break;
} }
@ -695,21 +708,22 @@ export default class AppSearchSuper {
} }
case 'inputMessagesFilterVoice': case 'inputMessagesFilterVoice':
case 'inputMessagesFilterRoundVoice':
case 'inputMessagesFilterMusic': case 'inputMessagesFilterMusic':
case 'inputMessagesFilterDocument': { case 'inputMessagesFilterDocument': {
for(const message of messages) { for(const message of messages) {
const showSender = this.showSender || message.media.document.type === 'voice'; const showSender = this.showSender || (['voice', 'round'] as MyDocument['type'][]).includes(message.media.document.type);
const div = wrapDocument({ const div = wrapDocument({
message, message,
withTime: !showSender, withTime: !showSender,
fontWeight: 400, fontWeight: 400,
voiceAsMusic: true, voiceAsMusic: true,
showSender: showSender, showSender,
searchContext: this.copySearchContext(inputFilter), searchContext: this.copySearchContext(inputFilter),
lazyLoadQueue: this.lazyLoadQueue lazyLoadQueue: this.lazyLoadQueue
}); });
if(['audio', 'voice'].includes(message.media.document.type)) { if((['audio', 'voice', 'round'] as MyDocument['type'][]).includes(message.media.document.type)) {
div.classList.add('audio-48'); div.classList.add('audio-48');
} }
@ -811,21 +825,18 @@ export default class AppSearchSuper {
subtitleFragment.append(a); subtitleFragment.append(a);
if(this.showSender) {
subtitleFragment.append('\n', appMessagesManager.wrapSenderToPeer(message));
}
if(!title) { if(!title) {
//title = new URL(webpage.url).hostname; //title = new URL(webpage.url).hostname;
title = RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]); title = RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]);
} }
let sender = this.showSender ? `<div class="subtitle sender">${appMessagesManager.getSenderToPeerText(message)}</div>` : '';
let titleAdditionHTML = '';
if(this.showSender) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
}
const row = new Row({ const row = new Row({
title, title,
titleRight: titleAdditionHTML, titleRight: appMessagesManager.wrapSentTime(message),
subtitle: subtitleFragment, subtitle: subtitleFragment,
havePadding: true, havePadding: true,
clickable: true, clickable: true,
@ -1257,7 +1268,7 @@ export default class AppSearchSuper {
} }
// ! Фикс случая, когда не загружаются документы при открытой панели разработчиков (происходит из-за того, что не совпадают критерии отбора документов в getSearch) // ! Фикс случая, когда не загружаются документы при открытой панели разработчиков (происходит из-за того, что не совпадают критерии отбора документов в 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) { //if((value.count || history.length === value.count) && history.length >= value.count) {
//this.log(logStr + 'loaded all media', value, loadCount); //this.log(logStr + 'loaded all media', value, loadCount);
this.loaded[type] = true; this.loaded[type] = true;
@ -1401,14 +1412,26 @@ export default class AppSearchSuper {
const dateTimestamp = date.getTime(); const dateTimestamp = date.getTime();
const containers = this.monthContainers[type] ?? (this.monthContainers[type] = {}); const containers = this.monthContainers[type] ?? (this.monthContainers[type] = {});
if(!(dateTimestamp in containers)) { if(!(dateTimestamp in containers)) {
const str = months[date.getMonth()] + ' ' + date.getFullYear();
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'search-super-month'; container.className = 'search-super-month';
const name = document.createElement('div'); const name = document.createElement('div');
name.classList.add('search-super-month-name'); 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); container.append(name);
const items = document.createElement('div'); const items = document.createElement('div');

75
src/components/audio.ts

@ -5,8 +5,7 @@
*/ */
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager"; import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { RichTextProcessor } from "../lib/richtextprocessor"; import { wrapPhoto } from "./wrappers";
import { formatDate, wrapPhoto } from "./wrappers";
import ProgressivePreloader from "./preloader"; import ProgressivePreloader from "./preloader";
import { MediaProgressLine } from "../lib/mediaPlayer"; import { MediaProgressLine } from "../lib/mediaPlayer";
import appMediaPlaybackController, { MediaItem } from "./appMediaPlaybackController"; import appMediaPlaybackController, { MediaItem } from "./appMediaPlaybackController";
@ -17,7 +16,6 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager";
import rootScope from "../lib/rootScope"; import rootScope from "../lib/rootScope";
import './middleEllipsis'; import './middleEllipsis';
import { SearchSuperContext } from "./appSearchSuper."; import { SearchSuperContext } from "./appSearchSuper.";
import { formatDateAccordingToToday } from "../helpers/date";
import { cancelEvent } from "../helpers/dom/cancelEvent"; import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent"; import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
import LazyLoadQueue from "./lazyLoadQueue"; import LazyLoadQueue from "./lazyLoadQueue";
@ -25,6 +23,11 @@ import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromi
import ListenerSetter, { Listener } from "../helpers/listenerSetter"; import ListenerSetter, { Listener } from "../helpers/listenerSetter";
import noop from "../helpers/noop"; import noop from "../helpers/noop";
import findUpClassName from "../helpers/dom/findUpClassName"; 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}) => { rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
mids.forEach(mid => { mids.forEach(mid => {
@ -273,43 +276,56 @@ function wrapAudio(audioEl: AudioElement) {
const message = audioEl.message; const message = audioEl.message;
const doc: MyDocument = message.media.document || message.media.webpage.document; const doc: MyDocument = message.media.document || message.media.webpage.document;
const senderTitle = audioEl.showSender ? appMessagesManager.getSenderToPeerText(message) : ''; const isVoice = doc.type === 'voice' || doc.type === 'round';
const descriptionEl = document.createElement('div');
descriptionEl.classList.add('audio-description');
let title = doc.type === 'voice' ? senderTitle : (doc.audioTitle || doc.fileName); if(!isVoice) {
let subtitle: string; const parts: (Node | string)[] = [];
if(doc.audioPerformer) {
parts.push(htmlToSpan(doc.audioPerformer));
}
if(doc.type === 'voice') {
subtitle = '';
} else {
subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : '';
if(withTime) { if(withTime) {
subtitle += (subtitle ? ' • ' : '') + formatDate(doc.date); parts.push(formatFullSentTime(doc.date));
} else if(!subtitle) { } else if(!parts.length) {
subtitle = 'Unknown Artist'; parts.push(formatBytes(doc.size));
} }
if(audioEl.showSender) { if(audioEl.showSender) {
subtitle += ' • ' + senderTitle; parts.push(appMessagesManager.wrapSenderToPeer(message));
} else {
subtitle = ' • ' + subtitle;
}
} }
let titleAdditionHTML = ''; descriptionEl.append(...joinElementsWith(parts, ' • '));
if(audioEl.showSender) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
} }
const html = ` const html = `
<div class="audio-details"> <div class="audio-details">
<div class="audio-title"><middle-ellipsis-element data-font-weight="${audioEl.dataset.fontWeight}">${title}</middle-ellipsis-element>${titleAdditionHTML}</div> <div class="audio-title"></div>
<div class="audio-subtitle"><div class="audio-time"></div>${subtitle || '<div></div>'}</div> <div class="audio-subtitle"><div class="audio-time"></div></div>
</div>`; </div>`;
audioEl.insertAdjacentHTML('beforeend', html); audioEl.insertAdjacentHTML('beforeend', html);
const onLoad = () => { 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; const subtitleDiv = audioEl.querySelector('.audio-subtitle') as HTMLDivElement;
subtitleDiv.append(descriptionEl);
const onLoad = () => {
let launched = false; let launched = false;
let progressLine = new MediaProgressLine(audioEl.audio, doc.supportsStreaming); let progressLine = new MediaProgressLine(audioEl.audio, doc.supportsStreaming);
@ -317,7 +333,7 @@ function wrapAudio(audioEl: AudioElement) {
audioEl.addAudioListener('ended', () => { audioEl.addAudioListener('ended', () => {
audioEl.classList.remove('audio-show-progress'); audioEl.classList.remove('audio-show-progress');
// Reset subtitle // Reset subtitle
subtitleDiv.lastChild.replaceWith(subtitle); subtitleDiv.lastChild.replaceWith(descriptionEl);
launched = false; launched = false;
}); });
@ -519,9 +535,9 @@ export default class AudioElement extends HTMLElement {
onClick(); onClick();
} }
} else { } else {
if(doc.supportsStreaming) { // if(doc.supportsStreaming) {
onLoad(false); onLoad(false);
} // }
if(doc.thumbs) { if(doc.thumbs) {
const imgs: HTMLImageElement[] = []; const imgs: HTMLImageElement[] = [];
@ -606,6 +622,8 @@ export default class AudioElement extends HTMLElement {
this.listenerSetter.remove(pauseListener); this.listenerSetter.remove(pauseListener);
}); });
} else { } else {
preloader = constructDownloadPreloader();
const load = () => { const load = () => {
const download = getDownloadPromise(); const download = getDownloadPromise();
preloader.attach(downloadDiv, false, download); preloader.attach(downloadDiv, false, download);
@ -619,7 +637,10 @@ export default class AudioElement extends HTMLElement {
this.append(downloadDiv); this.append(downloadDiv);
this.classList.add('downloading');
this.readyPromise.then(() => { this.readyPromise.then(() => {
this.classList.remove('downloading');
downloadDiv.classList.add('downloaded'); downloadDiv.classList.add('downloaded');
setTimeout(() => { setTimeout(() => {
downloadDiv.remove(); downloadDiv.remove();

26
src/components/chat/audio.ts

@ -6,30 +6,40 @@
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import type ChatTopbar from "./topbar"; import type ChatTopbar from "./topbar";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import appMediaPlaybackController from "../appMediaPlaybackController"; import appMediaPlaybackController from "../appMediaPlaybackController";
import DivAndCaption from "../divAndCaption"; import DivAndCaption from "../divAndCaption";
import { formatDate } from "../wrappers";
import PinnedContainer from "./pinnedContainer"; import PinnedContainer from "./pinnedContainer";
import Chat from "./chat"; import Chat from "./chat";
import { cancelEvent } from "../../helpers/dom/cancelEvent"; import { cancelEvent } from "../../helpers/dom/cancelEvent";
import { attachClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent";
import replaceContent from "../../helpers/dom/replaceContent"; import replaceContent from "../../helpers/dom/replaceContent";
import PeerTitle from "../peerTitle"; import PeerTitle from "../peerTitle";
import { i18n } from "../../lib/langPack";
import { formatFullSentTime } from "../../helpers/date";
export default class ChatAudio extends PinnedContainer { export default class ChatAudio extends PinnedContainer {
private toggleEl: HTMLElement; private toggleEl: HTMLElement;
constructor(protected topbar: ChatTopbar, protected chat: Chat, protected appMessagesManager: AppMessagesManager) { 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) => { 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.title, title);
replaceContent(this.divAndCaption.subtitle, subtitle); replaceContent(this.divAndCaption.subtitle, subtitle);
}), () => { }
),
() => {
if(this.toggleEl.classList.contains('flip-icon')) { if(this.toggleEl.classList.contains('flip-icon')) {
appMediaPlaybackController.toggle(); appMediaPlaybackController.toggle();
} }
}); }
);
this.divAndCaption.border.remove(); this.divAndCaption.border.remove();
@ -45,16 +55,16 @@ export default class ChatAudio extends PinnedContainer {
this.topbar.listenerSetter.add(rootScope)('audio_play', (e) => { this.topbar.listenerSetter.add(rootScope)('audio_play', (e) => {
const {doc, mid, peerId} = 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); const message = appMessagesManager.getMessageByPeer(peerId, mid);
if(doc.type === 'voice' || doc.type === 'round') { if(doc.type === 'voice' || doc.type === 'round') {
title = new PeerTitle({peerId: message.fromId}).element; title = new PeerTitle({peerId: message.fromId}).element;
//subtitle = 'Voice message'; //subtitle = 'Voice message';
subtitle = formatDate(message.date, false, false); subtitle = formatFullSentTime(message.date);
} else { } else {
title = doc.audioTitle || doc.fileName; title = doc.audioTitle || doc.fileName;
subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; subtitle = doc.audioPerformer || i18n('AudioUnknownArtist');
} }
this.fill(title, subtitle, message); this.fill(title, subtitle, message);

11
src/components/chat/pinnedContainer.ts

@ -22,7 +22,14 @@ export default class PinnedContainer {
private close: HTMLElement; private close: HTMLElement;
protected wrapper: 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<boolean>) { 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<boolean>
) {
/* const prev = this.divAndCaption.fill; /* const prev = this.divAndCaption.fill;
this.divAndCaption.fill = (mid, title, subtitle) => { this.divAndCaption.fill = (mid, title, subtitle) => {
this.divAndCaption.container.dataset.mid = '' + mid; this.divAndCaption.container.dataset.mid = '' + mid;
@ -87,7 +94,7 @@ export default class PinnedContainer {
this.topbar.setUtilsWidth(); 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.peerId = '' + message.peerId;
this.divAndCaption.container.dataset.mid = '' + message.mid; this.divAndCaption.container.dataset.mid = '' + message.mid;
this.divAndCaption.fill(title, subtitle, message); this.divAndCaption.fill(title, subtitle, message);

2
src/components/sidebarLeft/index.ts

@ -297,7 +297,7 @@ export class AppSidebarLeft extends SidebarSlider {
name: 'SharedMusicTab2', name: 'SharedMusicTab2',
type: 'music' type: 'music'
}, { }, {
inputFilter: 'inputMessagesFilterVoice', inputFilter: 'inputMessagesFilterRoundVoice',
name: 'SharedVoiceTab2', name: 'SharedVoiceTab2',
type: 'voice' type: 'voice'
}], }],

4
src/components/sidebarLeft/tabs/activeSessions.ts

@ -9,7 +9,7 @@ import { SettingSection } from "..";
import Button from "../../button"; import Button from "../../button";
import Row from "../../row"; import Row from "../../row";
import { Authorization } from "../../../layer"; import { Authorization } from "../../../layer";
import { formatDateAccordingToToday } from "../../../helpers/date"; import { formatDateAccordingToTodayNew } from "../../../helpers/date";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc"; import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc";
import ButtonMenu from "../../buttonMenu"; import ButtonMenu from "../../buttonMenu";
import apiManager from "../../../lib/mtproto/mtprotoworker"; import apiManager from "../../../lib/mtproto/mtprotoworker";
@ -35,7 +35,7 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
title: [auth.app_name, auth.app_version].join(' '), title: [auth.app_name, auth.app_version].join(' '),
subtitle: [auth.ip, auth.country].join(' - '), subtitle: [auth.ip, auth.country].join(' - '),
clickable: true, 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; row.container.dataset.hash = auth.hash;

2
src/components/sidebarRight/tabs/sharedMedia.ts

@ -862,7 +862,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
name: 'SharedMusicTab2', name: 'SharedMusicTab2',
type: 'music' type: 'music'
}, { }, {
inputFilter: 'inputMessagesFilterVoice', inputFilter: 'inputMessagesFilterRoundVoice',
name: 'SharedVoiceTab2', name: 'SharedVoiceTab2',
type: 'voice' type: 'voice'
}], }],

51
src/components/wrappers.ts

@ -8,7 +8,7 @@ import type Chat from './chat/chat';
import { getEmojiToneIndex } from '../vendor/emoji'; import { getEmojiToneIndex } from '../vendor/emoji';
import { readBlobAsText } from '../helpers/blob'; import { readBlobAsText } from '../helpers/blob';
import { deferredPromise } from '../helpers/cancellablePromise'; import { deferredPromise } from '../helpers/cancellablePromise';
import { formatDateAccordingToToday, months } from '../helpers/date'; import { formatFullSentTime } from '../helpers/date';
import mediaSizes, { ScreenSize } from '../helpers/mediaSizes'; import mediaSizes, { ScreenSize } from '../helpers/mediaSizes';
import { formatBytes } from '../helpers/number'; import { formatBytes } from '../helpers/number';
import { IS_SAFARI } from '../environment/userAgent'; import { IS_SAFARI } from '../environment/userAgent';
@ -46,6 +46,8 @@ import { clearBadCharsAndTrim } from '../helpers/cleanSearchText';
import blur from '../helpers/blur'; import blur from '../helpers/blur';
import IS_WEBP_SUPPORTED from '../environment/webpSupport'; import IS_WEBP_SUPPORTED from '../environment/webpSupport';
import MEDIA_MIME_TYPES_SUPPORTED from '../environment/mediaMimeTypesSupport'; 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 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; 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) => { rootScope.addEventListener('download_start', (docId) => {
const elements = Array.from(document.querySelectorAll(`.document[data-doc-id="${docId}"]`)) as HTMLElement[]; const elements = Array.from(document.querySelectorAll(`.document[data-doc-id="${docId}"]`)) as HTMLElement[];
elements.forEach(element => { 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 doc = (message.media.document || message.media.webpage.document) as MyDocument;
const uploading = message.pFlags.is_outgoing && message.media?.preloader; 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(); const audioElement = new AudioElement();
audioElement.dataset.mid = '' + message.mid; audioElement.dataset.mid = '' + message.mid;
audioElement.dataset.peerId = '' + message.peerId; 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 = stringMiddleOverflow(doc.file_name || 'Unknown.file', 26);
let fileName = doc.fileName || 'Unknown.file'; 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) { if(withTime) {
size += ' · ' + formatDate(doc.date); descriptionParts.push(formatFullSentTime(doc.date));
}
if(showSender) {
size += ' · ' + appMessagesManager.getSenderToPeerText(message);
} }
let titleAdditionHTML = '';
if(showSender) { if(showSender) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`; descriptionParts.push(appMessagesManager.wrapSenderToPeer(message));
} }
docDiv.innerHTML = ` docDiv.innerHTML = `
${cacheContext.downloaded && !uploading ? '' : `<div class="document-download"></div>`} ${cacheContext.downloaded && !uploading ? '' : `<div class="document-download"></div>`}
<div class="document-name"><middle-ellipsis-element data-font-weight="${fontWeight}">${fileName}</middle-ellipsis-element>${titleAdditionHTML}</div> <div class="document-name"></div>
<div class="document-size">${size}</div> <div class="document-size"></div>
`; `;
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); docDiv.prepend(icoDiv);
if(!uploading && message.pFlags.is_outgoing) { if(!uploading && message.pFlags.is_outgoing) {

60
src/helpers/date.ts

@ -5,7 +5,8 @@
*/ */
import { MOUNT_CLASS_TO } from "../config/debug"; 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 months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
export const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 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); 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) { export function formatDateAccordingToTodayNew(time: Date) {
const today = new Date(); const today = new Date();
const now = today.getTime() / 1000 | 0; const now = today.getTime() / 1000 | 0;
@ -64,6 +46,44 @@ export function formatDateAccordingToTodayNew(time: Date) {
}).element; }).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) { export function formatTime(date: Date) {
return new I18n.IntlDateElement({ return new I18n.IntlDateElement({
date, date,

2
src/lang.ts

@ -584,6 +584,8 @@ const lang = {
"AreYouSureBlockContact2": "Are you sure you want to block **%1$s**?", "AreYouSureBlockContact2": "Are you sure you want to block **%1$s**?",
"UserBlocked": "User blocked", "UserBlocked": "User blocked",
"UserUnblocked": "User unblocked", "UserUnblocked": "User unblocked",
"AudioUnknownArtist": "Unknown artist",
"AudioUnknownTitle": "Unknown title",
// * macos // * macos
"AccountSettings.Filters": "Chat Folders", "AccountSettings.Filters": "Chat Folders",

33
src/lib/appManagers/appMessagesManager.ts

@ -12,7 +12,7 @@
import { LazyLoadQueueBase } from "../../components/lazyLoadQueue"; import { LazyLoadQueueBase } from "../../components/lazyLoadQueue";
import ProgressivePreloader from "../../components/preloader"; import ProgressivePreloader from "../../components/preloader";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; 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 { createPosterForVideo } from "../../helpers/files";
import { copy, getObjectKeysAndSort } from "../../helpers/object"; import { copy, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random"; import { randomLong } from "../../helpers/random";
@ -2721,21 +2721,36 @@ export class AppMessagesManager {
} }
} }
public getSenderToPeerText(message: MyMessage) { public wrapSenderToPeer(message: MyMessage) {
let senderTitle = '', peerTitle: string; const senderTitle: HTMLElement = document.createElement('span');
senderTitle.classList.add('sender-title');
senderTitle = message.pFlags.out ? 'You' : appPeersManager.getPeerTitle(message.fromId, false, false); const fromMe = message.fromId === rootScope.myId && message.peerId !== rootScope.myId;
peerTitle = appPeersManager.isAnyGroup(message.peerId) || (message.pFlags.out && message.peerId !== rootScope.myId) ? senderTitle.append(
appPeersManager.getPeerTitle(message.peerId, false, false) : fromMe ?
''; i18n('FromYou') :
new PeerTitle({
peerId: message.fromId,
dialog: message.peerId === rootScope.myId
}).element
);
if(peerTitle) { if(appPeersManager.isAnyGroup(message.peerId) || fromMe) {
senderTitle += ' ➝ ' + peerTitle; const peerTitle = new PeerTitle({peerId: message.peerId}).element;
senderTitle.append(' ➝ ', peerTitle);
} }
return senderTitle; 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: true): string;
public wrapMessageActionTextNew(message: any, plain?: false): HTMLElement; public wrapMessageActionTextNew(message: any, plain?: false): HTMLElement;
public wrapMessageActionTextNew(message: any, plain: boolean): HTMLElement | string; public wrapMessageActionTextNew(message: any, plain: boolean): HTMLElement | string;

9
src/lib/langPack.ts

@ -448,16 +448,19 @@ export {i18n_};
const _i18n = I18n._i18n; const _i18n = I18n._i18n;
export {_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); const arr = elements.slice(0, 1);
for(let i = 1; i < elements.length; ++i) { for(let i = 1; i < elements.length; ++i) {
const isLast = (elements.length - 1) === i; const isLast = (elements.length - 1) === i;
const delimiterKey: LangPackKey = isLast && useLast ? 'WordDelimiterLast' : 'WordDelimiter'; arr.push(typeof(joiner) === 'function' ? joiner(isLast) : joiner);
arr.push(i18n(delimiterKey));
arr.push(elements[i]); arr.push(elements[i]);
} }
return arr; return arr;
} }
export function join(elements: (Node | string)[], useLast = true) {
return joinElementsWith(elements, (isLast) => i18n(isLast && useLast ? 'WordDelimiterLast' : 'WordDelimiter'));
}
MOUNT_CLASS_TO.I18n = I18n; MOUNT_CLASS_TO.I18n = I18n;

27
src/scss/partials/_audio.scss

@ -454,9 +454,6 @@
&-subtitle { &-subtitle {
font-size: .875rem; font-size: .875rem;
color: var(--secondary-text-color); color: var(--secondary-text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex; display: flex;
@include respond-to(handhelds) { @include respond-to(handhelds) {
@ -464,6 +461,12 @@
} }
} }
&-title,
&-time,
&-subtitle {
@include text-overflow();
}
// * for audio // * for audio
&-subtitle { &-subtitle {
align-items: center; align-items: center;
@ -483,10 +486,7 @@
// * for audio // * for audio
&-title, &-title,
&:not(.audio-show-progress) &-subtitle { &:not(.audio-show-progress) &-subtitle {
white-space: nowrap;
overflow: hidden;
max-width: 100%; max-width: 100%;
text-overflow: ellipsis;
} }
&.is-voice { &.is-voice {
@ -535,9 +535,10 @@
.audio-play-icon { .audio-play-icon {
z-index: 1; z-index: 1;
background-color: transparent; background-color: transparent;
opacity: 1;
@include animation-level(2) { @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 { .part {
@ -555,5 +556,17 @@
width: inherit; width: inherit;
height: inherit; height: inherit;
} }
&:not(.corner-download) {
.audio-download {
background-color: rgba(0, 0, 0, .3);
}
&.downloading {
.audio-play-icon {
opacity: 0;
}
}
}
} }
} }

4
src/scss/partials/_chatBubble.scss

@ -1691,6 +1691,10 @@ $bubble-margin: .25rem;
.replies { .replies {
user-select: none; user-select: none;
.c-ripple__circle {
background-color: var(--light-primary-color);
}
.rp { .rp {
width: 100%; width: 100%;
height: 100%; height: 100%;

4
src/scss/partials/_document.scss

@ -190,6 +190,10 @@
} }
} }
&-description {
@include text-overflow();
}
&:not(.corner-download) .preloader-container:not(.preloader-streamable) { &:not(.corner-download) .preloader-container:not(.preloader-streamable) {
transform: scale(1) !important; transform: scale(1) !important;
} }

9
src/scss/partials/_rightSidebar.scss

@ -514,16 +514,17 @@
white-space: pre-wrap; white-space: pre-wrap;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-word; word-break: break-word;
&.sender {
margin-top: .125rem;
}
} }
.sent-time { .sent-time {
margin: 1px 0 0; margin: 1px 0 0;
} }
.sender-title {
display: block;
margin-top: .25rem;
}
.checkbox-field { .checkbox-field {
padding: 0 !important; padding: 0 !important;
margin: 2rem 0 0 -1.75rem !important; margin: 2rem 0 0 -1.75rem !important;

Loading…
Cancel
Save