Media timestamps
Fix hiding audio bar on stop Fix showing audio bar on waveform click
This commit is contained in:
parent
cf9d4e11f1
commit
7cd845c210
|
@ -640,7 +640,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
};
|
||||
|
||||
private onEnded = (e?: Event) => {
|
||||
if(!e.isTrusted) {
|
||||
if(e && !e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -697,7 +697,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
return this.toggle(false);
|
||||
};
|
||||
|
||||
public stop = (media = this.playingMedia) => {
|
||||
public stop = (media = this.playingMedia, force?: boolean) => {
|
||||
if(!media) {
|
||||
return false;
|
||||
}
|
||||
|
@ -707,7 +707,6 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
}
|
||||
|
||||
media.currentTime = 0;
|
||||
simulateEvent(media, 'ended');
|
||||
|
||||
if(media === this.playingMedia) {
|
||||
const details = this.mediaDetails.get(media);
|
||||
|
@ -733,6 +732,10 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
this.playingMediaType = undefined;
|
||||
}
|
||||
|
||||
if(force) {
|
||||
this.dispatchEvent('stop');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -134,6 +134,10 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
|
||||
const onCaptionClick = (e: MouseEvent) => {
|
||||
const a = findUpTag(e.target, 'A');
|
||||
if(a.classList.contains('timestamp')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spoiler = findUpClassName(e.target, 'spoiler');
|
||||
if(a instanceof HTMLAnchorElement && (!spoiler || this.content.caption.classList.contains('is-spoiler-visible'))) { // close viewer if it's t.me/ redirect
|
||||
const onclick = a.getAttribute('onclick');
|
||||
|
@ -172,11 +176,19 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
}
|
||||
|
||||
onPrevClick = async(target: AppMediaViewerTargetType) => {
|
||||
this.openMedia(await this.getMessageByPeer(target.peerId, target.mid), target.element, -1);
|
||||
this.openMedia({
|
||||
message: await this.getMessageByPeer(target.peerId, target.mid),
|
||||
target: target.element,
|
||||
fromRight: -1
|
||||
});
|
||||
};
|
||||
|
||||
onNextClick = async(target: AppMediaViewerTargetType) => {
|
||||
this.openMedia(await this.getMessageByPeer(target.peerId, target.mid), target.element, 1);
|
||||
this.openMedia({
|
||||
message: await this.getMessageByPeer(target.peerId, target.mid),
|
||||
target: target.element,
|
||||
fromRight: 1
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteClick = () => {
|
||||
|
@ -235,8 +247,11 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
const caption = (message as Message.message).message;
|
||||
let html: Parameters<typeof setInnerHTML>[1] = '';
|
||||
if(caption) {
|
||||
const media = getMediaFromMessage(message, true);
|
||||
|
||||
html = wrapRichText(caption, {
|
||||
entities: (message as Message.message).totalEntities
|
||||
entities: (message as Message.message).totalEntities,
|
||||
maxMediaTimestamp: ((media as MyDocument)?.type === 'video' && (media as MyDocument).duration) || undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -252,8 +267,24 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
return this;
|
||||
}
|
||||
|
||||
public async openMedia(message: MyMessage, target?: HTMLElement, fromRight = 0, reverse = false,
|
||||
prevTargets: AppMediaViewerTargetType[] = [], nextTargets: AppMediaViewerTargetType[] = []/* , needLoadMore = true */) {
|
||||
public async openMedia({
|
||||
message,
|
||||
target,
|
||||
fromRight = 0,
|
||||
reverse = false,
|
||||
prevTargets = [],
|
||||
nextTargets = [],
|
||||
mediaTimestamp
|
||||
}: {
|
||||
message: MyMessage,
|
||||
target?: HTMLElement,
|
||||
fromRight?: number,
|
||||
reverse?: boolean,
|
||||
prevTargets?: AppMediaViewerTargetType[],
|
||||
nextTargets?: AppMediaViewerTargetType[],
|
||||
mediaTimestamp?: number
|
||||
/* , needLoadMore = true */
|
||||
}) {
|
||||
if(this.setMoverPromise) return this.setMoverPromise;
|
||||
|
||||
const mid = message.mid;
|
||||
|
@ -283,7 +314,19 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage);
|
||||
|
||||
this.setCaption(message);
|
||||
const promise = super._openMedia(media as MyPhoto | MyDocument, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, message/* , needLoadMore */);
|
||||
const promise = super._openMedia({
|
||||
media: media as MyPhoto | MyDocument,
|
||||
timestamp: message.date,
|
||||
fromId,
|
||||
fromRight,
|
||||
target,
|
||||
reverse,
|
||||
prevTargets,
|
||||
nextTargets,
|
||||
message,
|
||||
mediaTimestamp
|
||||
/* , needLoadMore */
|
||||
});
|
||||
this.target.mid = mid;
|
||||
this.target.peerId = message.peerId;
|
||||
this.target.message = message;
|
||||
|
|
|
@ -36,11 +36,19 @@ export default class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete
|
|||
}
|
||||
|
||||
onPrevClick = (target: AppMediaViewerAvatarTargetType) => {
|
||||
this.openMedia(target.photoId, target.element, -1);
|
||||
this.openMedia({
|
||||
photoId: target.photoId,
|
||||
target: target.element,
|
||||
fromRight: -1
|
||||
});
|
||||
};
|
||||
|
||||
onNextClick = (target: AppMediaViewerAvatarTargetType) => {
|
||||
this.openMedia(target.photoId, target.element, 1);
|
||||
this.openMedia({
|
||||
photoId: target.photoId,
|
||||
target: target.element,
|
||||
fromRight: 1
|
||||
});
|
||||
};
|
||||
|
||||
onDownloadClick = () => {
|
||||
|
@ -50,11 +58,32 @@ export default class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete
|
|||
});
|
||||
};
|
||||
|
||||
public async openMedia(photoId: Photo.photo['id'], target?: HTMLElement, fromRight = 0, prevTargets?: AppMediaViewerAvatarTargetType[], nextTargets?: AppMediaViewerAvatarTargetType[]) {
|
||||
public async openMedia({
|
||||
photoId,
|
||||
target,
|
||||
fromRight = 0,
|
||||
prevTargets,
|
||||
nextTargets
|
||||
}: {
|
||||
photoId: Photo.photo['id'],
|
||||
target?: HTMLElement,
|
||||
fromRight?: number,
|
||||
prevTargets?: AppMediaViewerAvatarTargetType[],
|
||||
nextTargets?: AppMediaViewerAvatarTargetType[]
|
||||
}) {
|
||||
if(this.setMoverPromise) return this.setMoverPromise;
|
||||
|
||||
const photo = await this.managers.appPhotosManager.getPhoto(photoId);
|
||||
const ret = super._openMedia(photo, photo.date, this.peerId, fromRight, target, false, prevTargets, nextTargets);
|
||||
const ret = super._openMedia({
|
||||
media: photo,
|
||||
timestamp: photo.date,
|
||||
fromId: this.peerId,
|
||||
fromRight,
|
||||
target,
|
||||
reverse: false,
|
||||
prevTargets,
|
||||
nextTargets
|
||||
});
|
||||
this.target.photoId = photo.id;
|
||||
this.target.photo = photo;
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ export default class AppMediaViewerBase<
|
|||
protected lastDragDelta: {x: number, y: number} = this.transform;
|
||||
protected lastGestureTime: number;
|
||||
protected clampZoomDebounced: ReturnType<typeof debounce<() => void>>;
|
||||
ignoreNextClick: boolean;
|
||||
protected ignoreNextClick: boolean;
|
||||
|
||||
get target() {
|
||||
return this.listLoader.current;
|
||||
|
@ -758,6 +758,10 @@ export default class AppMediaViewerBase<
|
|||
window.addEventListener('keyup', this.onKeyUp);
|
||||
}
|
||||
|
||||
public setMediaTimestamp(timestamp: number) {
|
||||
this.videoPlayer?.setTimestamp(timestamp);
|
||||
}
|
||||
|
||||
onClick = (e: MouseEvent) => {
|
||||
if(this.ignoreNextClick) {
|
||||
this.ignoreNextClick = undefined;
|
||||
|
@ -1471,18 +1475,30 @@ export default class AppMediaViewerBase<
|
|||
});
|
||||
}
|
||||
|
||||
protected async _openMedia(
|
||||
protected async _openMedia({
|
||||
media,
|
||||
timestamp,
|
||||
fromId,
|
||||
fromRight,
|
||||
target,
|
||||
reverse = false,
|
||||
prevTargets = [],
|
||||
nextTargets = [],
|
||||
message,
|
||||
mediaTimestamp
|
||||
}: {
|
||||
media: MyDocument | MyPhoto,
|
||||
timestamp: number,
|
||||
fromId: PeerId | string,
|
||||
fromRight: number,
|
||||
target?: HTMLElement,
|
||||
reverse = false,
|
||||
prevTargets: TargetType[] = [],
|
||||
nextTargets: TargetType[] = [],
|
||||
message?: MyMessage
|
||||
reverse?: boolean,
|
||||
prevTargets?: TargetType[],
|
||||
nextTargets?: TargetType[],
|
||||
message?: MyMessage,
|
||||
mediaTimestamp?: number
|
||||
/* , needLoadMore = true */
|
||||
) {
|
||||
}) {
|
||||
if(this.setMoverPromise) return this.setMoverPromise;
|
||||
|
||||
/* if(DEBUG) {
|
||||
|
@ -1528,7 +1544,7 @@ export default class AppMediaViewerBase<
|
|||
const tempId = ++this.tempId;
|
||||
|
||||
if(container.firstElementChild) {
|
||||
container.innerHTML = '';
|
||||
container.replaceChildren();
|
||||
}
|
||||
|
||||
// ok set
|
||||
|
@ -1672,6 +1688,10 @@ export default class AppMediaViewerBase<
|
|||
video.loop = true;
|
||||
}
|
||||
|
||||
if(mediaTimestamp !== undefined) {
|
||||
video.currentTime = mediaTimestamp;
|
||||
}
|
||||
|
||||
// if(!video.parentElement) {
|
||||
div.append(video);
|
||||
// }
|
||||
|
|
|
@ -586,7 +586,14 @@ export default class AppSearchSuper {
|
|||
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId, mid);
|
||||
new AppMediaViewer()
|
||||
.setSearchContext(this.copySearchContext(inputFilter))
|
||||
.openMedia(message, targets[idx].element, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
|
||||
.openMedia({
|
||||
message,
|
||||
target: targets[idx].element,
|
||||
fromRight: 0,
|
||||
reverse: false,
|
||||
prevTargets: targets.slice(0, idx),
|
||||
nextTargets: targets.slice(idx + 1)
|
||||
});
|
||||
};
|
||||
|
||||
attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'), {listenerSetter: this.listenerSetter});
|
||||
|
|
|
@ -262,7 +262,7 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
|
|||
let mousedown = false, mousemove = false;
|
||||
progress.addEventListener('mouseleave', (e) => {
|
||||
if(mousedown) {
|
||||
audio.play();
|
||||
audioEl.togglePlay(undefined, true);
|
||||
mousedown = false;
|
||||
}
|
||||
mousemove = false;
|
||||
|
@ -275,7 +275,7 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
|
|||
e.preventDefault();
|
||||
if(e.button !== 0) return;
|
||||
if(!audio.paused) {
|
||||
audio.pause();
|
||||
audioEl.togglePlay(undefined, false);
|
||||
}
|
||||
|
||||
scrub(e);
|
||||
|
@ -283,7 +283,7 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
|
|||
});
|
||||
progress.addEventListener('mouseup', (e) => {
|
||||
if(mousemove && mousedown) {
|
||||
audio.play();
|
||||
audioEl.togglePlay(undefined, true);
|
||||
mousedown = false;
|
||||
}
|
||||
});
|
||||
|
@ -491,6 +491,7 @@ export default class AudioElement extends HTMLElement {
|
|||
private onTypeDisconnect: () => void;
|
||||
public onLoad: (autoload?: boolean) => void;
|
||||
public readyPromise: CancellablePromise<void>;
|
||||
public load: (shouldPlay: boolean, controlledAutoplay?: boolean) => void;
|
||||
|
||||
public async render() {
|
||||
this.classList.add('audio');
|
||||
|
@ -562,27 +563,7 @@ export default class AudioElement extends HTMLElement {
|
|||
onPlay();
|
||||
}
|
||||
|
||||
const togglePlay = (e?: Event, paused = audio.paused) => {
|
||||
e && cancelEvent(e);
|
||||
|
||||
if(paused) {
|
||||
const hadSearchContext = !!this.searchContext;
|
||||
if(appMediaPlaybackController.setSearchContext(this.searchContext || {
|
||||
peerId: NULL_PEER_ID,
|
||||
inputFilter: {_: 'inputMessagesFilterEmpty'},
|
||||
useSearch: false
|
||||
})) {
|
||||
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this, this.message.mid/* , this.searchContext.useSearch */);
|
||||
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
|
||||
}
|
||||
|
||||
audio.play().catch(() => {});
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
};
|
||||
|
||||
attachClickEvent(toggle, (e) => togglePlay(e), {listenerSetter: this.listenerSetter});
|
||||
attachClickEvent(toggle, (e) => this.togglePlay(e), {listenerSetter: this.listenerSetter});
|
||||
|
||||
this.addAudioListener('ended', () => {
|
||||
toggle.classList.remove('playing');
|
||||
|
@ -599,8 +580,6 @@ export default class AudioElement extends HTMLElement {
|
|||
});
|
||||
|
||||
this.addAudioListener('play', onPlay);
|
||||
|
||||
return togglePlay;
|
||||
};
|
||||
|
||||
if(doc.thumbs?.length) {
|
||||
|
@ -629,24 +608,16 @@ export default class AudioElement extends HTMLElement {
|
|||
const autoDownload = doc.type !== 'audio'/* || !this.noAutoDownload */;
|
||||
onLoad(autoDownload);
|
||||
|
||||
const r = (shouldPlay: boolean) => {
|
||||
const r = this.load = (shouldPlay: boolean, controlledAutoplay?: boolean) => {
|
||||
this.load = undefined;
|
||||
|
||||
if(this.audio.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid, this.message.pFlags.is_scheduled);
|
||||
|
||||
const onDownloadInit = () => {
|
||||
if(shouldPlay) {
|
||||
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
|
||||
|
||||
if(IS_SAFARI && !this.audio.autoplay) {
|
||||
this.audio.autoplay = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDownloadInit();
|
||||
this.onDownloadInit(shouldPlay);
|
||||
|
||||
if(!preloader) {
|
||||
if(doc.supportsStreaming) {
|
||||
|
@ -673,7 +644,7 @@ export default class AudioElement extends HTMLElement {
|
|||
deferred.cancel();
|
||||
}, {once: true}) as any;
|
||||
|
||||
onDownloadInit();
|
||||
this.onDownloadInit(shouldPlay);
|
||||
};
|
||||
|
||||
/* if(!this.audio.paused) {
|
||||
|
@ -683,7 +654,7 @@ export default class AudioElement extends HTMLElement {
|
|||
const playListener: any = this.addAudioListener('play', onPlay);
|
||||
this.readyPromise.then(() => {
|
||||
this.listenerSetter.remove(playListener);
|
||||
this.listenerSetter.remove(pauseListener);
|
||||
pauseListener && this.listenerSetter.remove(pauseListener);
|
||||
});
|
||||
} else {
|
||||
preloader = constructDownloadPreloader();
|
||||
|
@ -693,7 +664,7 @@ export default class AudioElement extends HTMLElement {
|
|||
}
|
||||
|
||||
const load = () => {
|
||||
onDownloadInit();
|
||||
this.onDownloadInit(shouldPlay);
|
||||
|
||||
const download = appDownloadManager.downloadMediaURL({media: doc});
|
||||
|
||||
|
@ -729,7 +700,7 @@ export default class AudioElement extends HTMLElement {
|
|||
|
||||
// setTimeout(() => {
|
||||
// release loaded audio
|
||||
if(appMediaPlaybackController.willBePlayedMedia === this.audio) {
|
||||
if(!controlledAutoplay && appMediaPlaybackController.willBePlayedMedia === this.audio) {
|
||||
this.audio.play();
|
||||
appMediaPlaybackController.willBePlayed(undefined);
|
||||
}
|
||||
|
@ -755,6 +726,54 @@ export default class AudioElement extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
private onDownloadInit(shouldPlay: boolean) {
|
||||
if(shouldPlay) {
|
||||
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
|
||||
|
||||
if(IS_SAFARI && !this.audio.autoplay) {
|
||||
this.audio.autoplay = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public togglePlay(e?: Event, paused = this.audio.paused) {
|
||||
e && cancelEvent(e);
|
||||
|
||||
if(paused) {
|
||||
this.setTargetsIfNeeded();
|
||||
this.audio.play().catch(() => {});
|
||||
} else {
|
||||
this.audio.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public setTargetsIfNeeded() {
|
||||
const hadSearchContext = !!this.searchContext;
|
||||
if(appMediaPlaybackController.setSearchContext(this.searchContext || {
|
||||
peerId: NULL_PEER_ID,
|
||||
inputFilter: {_: 'inputMessagesFilterEmpty'},
|
||||
useSearch: false
|
||||
})) {
|
||||
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this, this.message.mid/* , this.searchContext.useSearch */);
|
||||
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
|
||||
}
|
||||
}
|
||||
|
||||
public playWithTimestamp(timestamp: number) {
|
||||
this.load?.(true, true);
|
||||
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
|
||||
this.readyPromise.then(() => {
|
||||
if(appMediaPlaybackController.willBePlayedMedia !== this.audio && this.audio.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
appMediaPlaybackController.willBePlayed(undefined);
|
||||
|
||||
this.audio.currentTime = timestamp;
|
||||
this.togglePlay(undefined, true);
|
||||
});
|
||||
}
|
||||
|
||||
get addAudioListener() {
|
||||
return this.listenerSetter.add(this.audio);
|
||||
}
|
||||
|
|
|
@ -96,7 +96,12 @@ export async function openAvatarViewer(
|
|||
peerId,
|
||||
inputFilter: {_: inputFilter}
|
||||
})
|
||||
.openMedia(message, getTarget(), undefined, undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);
|
||||
.openMedia({
|
||||
message,
|
||||
target: getTarget(),
|
||||
prevTargets: prevTargets ? f(prevTargets) : undefined,
|
||||
nextTargets: nextTargets ? f(nextTargets) : undefined
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -112,13 +117,12 @@ export async function openAvatarViewer(
|
|||
photoId: el.item as string
|
||||
}));
|
||||
|
||||
new AppMediaViewerAvatar(peerId).openMedia(
|
||||
photo.id,
|
||||
getTarget(),
|
||||
undefined,
|
||||
prevTargets ? f(prevTargets) : undefined,
|
||||
nextTargets ? f(nextTargets) : undefined
|
||||
);
|
||||
new AppMediaViewerAvatar(peerId).openMedia({
|
||||
photoId: photo.id,
|
||||
target: getTarget(),
|
||||
prevTargets: prevTargets ? f(prevTargets) : undefined,
|
||||
nextTargets: nextTargets ? f(nextTargets) : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ export default class ChatAudio extends PinnedContainer {
|
|||
}
|
||||
),
|
||||
onClose: () => {
|
||||
appMediaPlaybackController.stop();
|
||||
appMediaPlaybackController.stop(undefined, true);
|
||||
},
|
||||
floating: true
|
||||
});
|
||||
|
|
|
@ -565,8 +565,8 @@ export default class ChatBubbles {
|
|||
if(element instanceof AudioElement || element.classList.contains('media-round')) {
|
||||
element.dataset.mid = '' + message.mid;
|
||||
delete element.dataset.isOutgoing;
|
||||
(element as any).message = message;
|
||||
(element as any).onLoad(true);
|
||||
(element as AudioElement).message = message;
|
||||
(element as AudioElement).onLoad(true);
|
||||
} else {
|
||||
element.dataset.docId = '' + doc.id;
|
||||
(element as any).doc = doc;
|
||||
|
@ -1920,6 +1920,68 @@ export default class ChatBubbles {
|
|||
return;
|
||||
}
|
||||
|
||||
if(await this.checkTargetForMediaViewer(target, e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(['IMG', 'DIV', 'SPAN'/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
|
||||
|
||||
if(['DIV', 'SPAN'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) {
|
||||
if(target.classList.contains('goto-original')) {
|
||||
const savedFrom = bubble.dataset.savedFrom;
|
||||
const [peerId, mid] = savedFrom.split('_');
|
||||
// //this.log('savedFrom', peerId, msgID);
|
||||
this.chat.appImManager.setInnerPeer({
|
||||
peerId: peerId.toPeerId(),
|
||||
lastMsgId: +mid
|
||||
});
|
||||
return;
|
||||
} else if(target.classList.contains('forward')) {
|
||||
const mid = +bubble.dataset.mid;
|
||||
const message = await this.managers.appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
new PopupForward({
|
||||
[this.peerId]: await this.managers.appMessagesManager.getMidsByMessage(message)
|
||||
});
|
||||
// appSidebarRight.forwardTab.open([mid]);
|
||||
return;
|
||||
}
|
||||
|
||||
let isReplyClick = false;
|
||||
|
||||
try {
|
||||
isReplyClick = !!findUpClassName(e.target, 'reply');
|
||||
} catch(err) {}
|
||||
|
||||
if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) {
|
||||
const bubbleMid = +bubble.dataset.mid;
|
||||
this.followStack.push(bubbleMid);
|
||||
|
||||
const message = (await this.chat.getMessage(bubbleMid)) as Message.message;
|
||||
|
||||
const replyToPeerId = message.reply_to.reply_to_peer_id ? getPeerId(message.reply_to.reply_to_peer_id) : this.peerId;
|
||||
const replyToMid = message.reply_to.reply_to_msg_id;
|
||||
|
||||
this.chat.appImManager.setInnerPeer({
|
||||
peerId: replyToPeerId,
|
||||
lastMsgId: replyToMid,
|
||||
type: this.chat.type,
|
||||
threadId: this.chat.threadId
|
||||
});
|
||||
|
||||
/* if(this.chat.type === 'discussion') {
|
||||
this.chat.appImManager.setMessageId(, originalMessageId);
|
||||
} else {
|
||||
this.chat.appImManager.setInnerPeer(this.peerId, originalMessageId);
|
||||
} */
|
||||
// this.chat.setMessageId(, originalMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('chatInner click', e);
|
||||
};
|
||||
|
||||
public async checkTargetForMediaViewer(target: HTMLElement, e?: Event, mediaTimestamp?: number) {
|
||||
const bubble = findUpClassName(target, 'bubble');
|
||||
const documentDiv = findUpClassName(target, 'document-with-thumb');
|
||||
if((target.tagName === 'IMG' && !target.classList.contains('emoji') && !target.classList.contains('document-thumb')) ||
|
||||
target.classList.contains('album-item') ||
|
||||
|
@ -1929,7 +1991,7 @@ export default class ChatBubbles {
|
|||
target.classList.contains('canvas-thumbnail')) {
|
||||
const groupedItem = findUpClassName(target, 'album-item') || findUpClassName(target, 'document-container');
|
||||
const preloader = (groupedItem || bubble).querySelector<HTMLElement>('.preloader-container');
|
||||
if(preloader) {
|
||||
if(preloader && e) {
|
||||
simulateClickEvent(preloader);
|
||||
cancelEvent(e);
|
||||
return;
|
||||
|
@ -2027,67 +2089,18 @@ export default class ChatBubbles {
|
|||
useSearch: this.chat.type !== 'scheduled' && !isSingleMedia,
|
||||
isScheduled: this.chat.type === 'scheduled'
|
||||
})
|
||||
.openMedia(message, targets[idx].element, 0, true, targets.slice(0, idx), targets.slice(idx + 1));
|
||||
|
||||
// appMediaViewer.openMedia(message, target as HTMLImageElement);
|
||||
return;
|
||||
.openMedia({
|
||||
message: message,
|
||||
target: targets[idx].element,
|
||||
fromRight: 0,
|
||||
reverse: true,
|
||||
prevTargets: targets.slice(0, idx),
|
||||
nextTargets: targets.slice(idx + 1),
|
||||
mediaTimestamp
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if(['IMG', 'DIV', 'SPAN'/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
|
||||
|
||||
if(['DIV', 'SPAN'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) {
|
||||
if(target.classList.contains('goto-original')) {
|
||||
const savedFrom = bubble.dataset.savedFrom;
|
||||
const [peerId, mid] = savedFrom.split('_');
|
||||
// //this.log('savedFrom', peerId, msgID);
|
||||
this.chat.appImManager.setInnerPeer({
|
||||
peerId: peerId.toPeerId(),
|
||||
lastMsgId: +mid
|
||||
});
|
||||
return;
|
||||
} else if(target.classList.contains('forward')) {
|
||||
const mid = +bubble.dataset.mid;
|
||||
const message = await this.managers.appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
new PopupForward({
|
||||
[this.peerId]: await this.managers.appMessagesManager.getMidsByMessage(message)
|
||||
});
|
||||
// appSidebarRight.forwardTab.open([mid]);
|
||||
return;
|
||||
}
|
||||
|
||||
let isReplyClick = false;
|
||||
|
||||
try {
|
||||
isReplyClick = !!findUpClassName(e.target, 'reply');
|
||||
} catch(err) {}
|
||||
|
||||
if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) {
|
||||
const bubbleMid = +bubble.dataset.mid;
|
||||
this.followStack.push(bubbleMid);
|
||||
|
||||
const message = (await this.chat.getMessage(bubbleMid)) as Message.message;
|
||||
|
||||
const replyToPeerId = message.reply_to.reply_to_peer_id ? getPeerId(message.reply_to.reply_to_peer_id) : this.peerId;
|
||||
const replyToMid = message.reply_to.reply_to_msg_id;
|
||||
|
||||
this.chat.appImManager.setInnerPeer({
|
||||
peerId: replyToPeerId,
|
||||
lastMsgId: replyToMid,
|
||||
type: this.chat.type,
|
||||
threadId: this.chat.threadId
|
||||
});
|
||||
|
||||
/* if(this.chat.type === 'discussion') {
|
||||
this.chat.appImManager.setMessageId(, originalMessageId);
|
||||
} else {
|
||||
this.chat.appImManager.setInnerPeer(this.peerId, originalMessageId);
|
||||
} */
|
||||
// this.chat.setMessageId(, originalMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('chatInner click', e);
|
||||
};
|
||||
}
|
||||
|
||||
public async onGoDownClick() {
|
||||
if(!this.followStack.length) {
|
||||
|
@ -3219,10 +3232,33 @@ export default class ChatBubbles {
|
|||
|
||||
this.onScroll();
|
||||
|
||||
const afterSetPromise = Promise.all([setPeerPromise, getHeavyAnimationPromise()]);
|
||||
const afterSetPromise = Promise.all([
|
||||
setPeerPromise,
|
||||
getHeavyAnimationPromise()
|
||||
]);
|
||||
afterSetPromise.then(() => { // check whether list isn't full
|
||||
if(!middleware()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollable.checkForTriggers();
|
||||
|
||||
if(options.mediaTimestamp !== undefined) {
|
||||
// ! :(
|
||||
const p = cached && !samePeer && liteMode.isAvailable('animations') && this.chat.appImManager.chats.length > 1 ?
|
||||
pause(400) :
|
||||
Promise.resolve();
|
||||
p.then(() => {
|
||||
return this.getMountedBubble(lastMsgId);
|
||||
}).then((mounted) => {
|
||||
if(!middleware() || !mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playMediaWithTimestamp(mounted.bubble, options.mediaTimestamp);
|
||||
});
|
||||
}
|
||||
|
||||
// if(cached) {
|
||||
// this.onRenderScrollSet();
|
||||
// }
|
||||
|
@ -3267,6 +3303,21 @@ export default class ChatBubbles {
|
|||
return {cached, promise: setPeerPromise};
|
||||
}
|
||||
|
||||
public playMediaWithTimestamp(bubble: HTMLElement, timestamp: number) {
|
||||
const attachment = bubble.querySelector<HTMLElement>('.attachment');
|
||||
if(attachment) {
|
||||
const media = attachment.querySelector<HTMLElement>('img, video, canvas');
|
||||
this.checkTargetForMediaViewer(media, undefined, timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
const audio = bubble.querySelector<AudioElement>('.audio');
|
||||
if(audio) {
|
||||
audio.playWithTimestamp(timestamp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async setFetchReactionsInterval(afterSetPromise: Promise<any>) {
|
||||
const middleware = this.getMiddleware();
|
||||
const needReactionsInterval = this.chat.isChannel;
|
||||
|
@ -3874,6 +3925,8 @@ export default class ChatBubbles {
|
|||
|
||||
customEmojiSize ??= this.chat.appImManager.customEmojiSize;
|
||||
|
||||
const doc = (messageMedia as MessageMedia.messageMediaDocument)?.document as MyDocument;
|
||||
|
||||
const richText = wrapRichText(messageMessage, {
|
||||
entities: totalEntities,
|
||||
passEntities: this.passEntities,
|
||||
|
@ -3881,7 +3934,8 @@ export default class ChatBubbles {
|
|||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
customEmojiSize,
|
||||
middleware,
|
||||
animationGroup: this.chat.animationGroup
|
||||
animationGroup: this.chat.animationGroup,
|
||||
maxMediaTimestamp: ((['voice', 'audio', 'video'] as MyDocument['type'][]).includes(doc?.type) && doc.duration) || undefined
|
||||
});
|
||||
|
||||
let canHaveTail = true;
|
||||
|
|
|
@ -100,6 +100,9 @@ export default class PopupForward extends PopupPickUser {
|
|||
case 'voice':
|
||||
action = 'send_voices';
|
||||
break;
|
||||
case 'video':
|
||||
action = 'send_videos';
|
||||
break;
|
||||
default:
|
||||
action = 'send_docs';
|
||||
break;
|
||||
|
|
|
@ -34,7 +34,7 @@ import rootScope from '../../lib/rootScope';
|
|||
import {ThumbCache} from '../../lib/storages/thumbs';
|
||||
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
|
||||
import appMediaPlaybackController, {MediaSearchContext} from '../appMediaPlaybackController';
|
||||
import {findMediaTargets} from '../audio';
|
||||
import AudioElement, {findMediaTargets} from '../audio';
|
||||
import LazyLoadQueue from '../lazyLoadQueue';
|
||||
import ProgressivePreloader from '../preloader';
|
||||
import wrapPhoto from './photo';
|
||||
|
@ -338,7 +338,8 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
|
|||
};
|
||||
|
||||
if(message.pFlags.is_outgoing) {
|
||||
(divRound as any).onLoad = onLoad;
|
||||
// ! WARNING ! just to type-check
|
||||
(divRound as any as AudioElement).onLoad = onLoad;
|
||||
divRound.dataset.isOutgoing = '1';
|
||||
} else {
|
||||
onLoad();
|
||||
|
@ -567,6 +568,8 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
|
|||
preloader.setDownloadFunction(load);
|
||||
}
|
||||
|
||||
(container as any).preloader = preloader;
|
||||
|
||||
/* if(doc.size >= 20e6 && !doc.downloaded) {
|
||||
let downloadDiv = document.createElement('div');
|
||||
downloadDiv.classList.add('download');
|
||||
|
|
|
@ -11,7 +11,7 @@ import parseUriParams from './string/parseUriParams';
|
|||
export default function addAnchorListener<Params extends {pathnameParams?: any, uriParams?: any}>(options: {
|
||||
name: 'showMaskedAlert' | 'execBotCommand' | 'searchByHashtag' | 'addstickers' | 'im' |
|
||||
'resolve' | 'privatepost' | 'addstickers' | 'voicechat' | 'joinchat' | 'join' | 'invoice' |
|
||||
'addemoji',
|
||||
'addemoji' | 'setMediaTimestamp',
|
||||
protocol?: 'tg',
|
||||
callback: (params: Params, element?: HTMLAnchorElement) => boolean | any,
|
||||
noPathnameParams?: boolean,
|
||||
|
|
|
@ -4525,7 +4525,7 @@ export namespace ReplyMarkup {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/MessageEntity
|
||||
*/
|
||||
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntitySpoiler | MessageEntity.messageEntityCustomEmoji | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak | MessageEntity.messageEntityCaret;
|
||||
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntitySpoiler | MessageEntity.messageEntityCustomEmoji | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak | MessageEntity.messageEntityCaret | MessageEntity.messageEntityTimestamp;
|
||||
|
||||
export namespace MessageEntity {
|
||||
export type messageEntityUnknown = {
|
||||
|
@ -4684,6 +4684,14 @@ export namespace MessageEntity {
|
|||
offset?: number,
|
||||
length?: number
|
||||
};
|
||||
|
||||
export type messageEntityTimestamp = {
|
||||
_: 'messageEntityTimestamp',
|
||||
offset?: number,
|
||||
length?: number,
|
||||
time?: number,
|
||||
raw?: string
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11858,6 +11866,7 @@ export interface ConstructorDeclMap {
|
|||
'messageEntityHighlight': MessageEntity.messageEntityHighlight,
|
||||
'messageEntityLinebreak': MessageEntity.messageEntityLinebreak,
|
||||
'messageEntityCaret': MessageEntity.messageEntityCaret,
|
||||
'messageEntityTimestamp': MessageEntity.messageEntityTimestamp,
|
||||
'messageActionDiscussionStarted': MessageAction.messageActionDiscussionStarted,
|
||||
'messageActionChatLeave': MessageAction.messageActionChatLeave,
|
||||
'messageActionChannelDeletePhoto': MessageAction.messageActionChannelDeletePhoto,
|
||||
|
|
|
@ -120,7 +120,8 @@ export type ChatSetPeerOptions = {
|
|||
startParam?: string,
|
||||
stack?: number,
|
||||
commentId?: number,
|
||||
type?: ChatType
|
||||
type?: ChatType,
|
||||
mediaTimestamp?: number
|
||||
};
|
||||
|
||||
export type ChatSetInnerPeerOptions = Modify<ChatSetPeerOptions, {
|
||||
|
@ -623,6 +624,23 @@ export class AppImManager extends EventListenerBase<{
|
|||
}
|
||||
});
|
||||
|
||||
addAnchorListener<{}>({
|
||||
name: 'setMediaTimestamp',
|
||||
callback: (_, element) => {
|
||||
const timestamp = +element.dataset.timestamp;
|
||||
const bubble = findUpClassName(element, 'bubble');
|
||||
if(bubble) {
|
||||
this.chat.bubbles.playMediaWithTimestamp(bubble, timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if(findUpClassName(element, 'media-viewer-caption')) {
|
||||
const appMediaViewer = (window as any).appMediaViewer;
|
||||
appMediaViewer.setMediaTimestamp(timestamp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
([
|
||||
['addstickers', INTERNAL_LINK_TYPE.STICKER_SET],
|
||||
['addemoji', INTERNAL_LINK_TYPE.EMOJI_SET]
|
||||
|
@ -707,7 +725,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
// pathnameParams: [string, string?],
|
||||
// uriParams: {comment?: number}
|
||||
pathnameParams: ['c', string, string] | [string, string?],
|
||||
uriParams: {thread?: string, comment?: string} | {comment?: string, start?: string}
|
||||
uriParams: {thread?: string, comment?: string, t?: string} | {comment?: string, start?: string, t?: string}
|
||||
}>({
|
||||
name: 'im',
|
||||
callback: async({pathnameParams, uriParams}, element) => {
|
||||
|
@ -726,7 +744,8 @@ export class AppImManager extends EventListenerBase<{
|
|||
post: pathnameParams[2] || pathnameParams[1],
|
||||
thread,
|
||||
comment: uriParams.comment,
|
||||
stack: this.getStackFromElement(element)
|
||||
stack: this.getStackFromElement(element),
|
||||
t: uriParams.t
|
||||
};
|
||||
} else {
|
||||
const thread = 'thread' in uriParams ? uriParams.thread : pathnameParams[2] && pathnameParams[1];
|
||||
|
@ -737,7 +756,8 @@ export class AppImManager extends EventListenerBase<{
|
|||
thread,
|
||||
comment: uriParams.comment,
|
||||
start: 'start' in uriParams ? uriParams.start : undefined,
|
||||
stack: this.getStackFromElement(element)
|
||||
stack: this.getStackFromElement(element),
|
||||
t: uriParams.t
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -765,7 +785,8 @@ export class AppImManager extends EventListenerBase<{
|
|||
post?: string,
|
||||
thread?: string,
|
||||
comment?: string,
|
||||
phone?: string
|
||||
phone?: string,
|
||||
t?: string
|
||||
}
|
||||
}>({
|
||||
name: 'resolve',
|
||||
|
@ -1127,7 +1148,8 @@ export class AppImManager extends EventListenerBase<{
|
|||
commentId,
|
||||
startParam: link.start,
|
||||
stack: link.stack,
|
||||
threadId
|
||||
threadId,
|
||||
mediaTimestamp: link.t && +link.t
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -1153,7 +1175,8 @@ export class AppImManager extends EventListenerBase<{
|
|||
peer: chat,
|
||||
lastMsgId: postId,
|
||||
threadId,
|
||||
stack: link.stack
|
||||
stack: link.stack,
|
||||
mediaTimestamp: link.t && +link.t
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export namespace InternalLink {
|
|||
comment?: string,
|
||||
thread?: string,
|
||||
start?: string,
|
||||
t?: string, // media timestamp
|
||||
stack?: number // local
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ export namespace InternalLink {
|
|||
post: string,
|
||||
thread?: string,
|
||||
comment?: string,
|
||||
t?: string // media timestamp
|
||||
stack?: number // local
|
||||
}
|
||||
|
||||
|
|
|
@ -270,8 +270,7 @@ export default class VideoPlayer extends ControlsHover {
|
|||
}
|
||||
}
|
||||
|
||||
protected togglePlay() {
|
||||
const isPaused = this.video.paused;
|
||||
protected togglePlay(isPaused = this.video.paused) {
|
||||
this.video[isPaused ? 'play' : 'pause']();
|
||||
}
|
||||
|
||||
|
@ -394,6 +393,11 @@ export default class VideoPlayer extends ControlsHover {
|
|||
}
|
||||
}
|
||||
|
||||
public setTimestamp(timestamp: number) {
|
||||
this.video.currentTime = timestamp;
|
||||
this.togglePlay(true);
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
super.cleanup();
|
||||
this.listenerSetter.removeAll();
|
||||
|
|
|
@ -69,8 +69,9 @@ export const URL_REG_EXP = URL_PROTOCOL_REG_EXP_PART +
|
|||
export const URL_PROTOCOL_REG_EXP = new RegExp('^' + URL_PROTOCOL_REG_EXP_PART.slice(0, -1), 'i');
|
||||
export const URL_ANY_PROTOCOL_REG_EXP = /^((?:[^\/]+?):\/\/|mailto:)/;
|
||||
export const USERNAME_REG_EXP = '[a-zA-Z\\d_]{5,32}';
|
||||
export const TIMESTAMP_REG_EXP = '(?:\\s|^)((?:\\d{1,2}:)?(?:[0-5]?[0-9]):(?:[0-5][0-9]))(?:\\s|$)';
|
||||
export const BOT_COMMAND_REG_EXP = '\\/([a-zA-Z\\d_]{1,32})(?:@(' + USERNAME_REG_EXP + '))?(\\b|$)';
|
||||
export const FULL_REG_EXP = new RegExp('(^| )(@)(' + USERNAME_REG_EXP + ')|(' + URL_REG_EXP + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + ALPHA_NUMERIC_REG_EXP + ']{2,64})|(^|\\s)' + BOT_COMMAND_REG_EXP, 'i');
|
||||
export const FULL_REG_EXP = new RegExp('(^| )(@)(' + USERNAME_REG_EXP + ')|(' + URL_REG_EXP + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + ALPHA_NUMERIC_REG_EXP + ']{2,64})|(^|\\s)' + BOT_COMMAND_REG_EXP + '|' + TIMESTAMP_REG_EXP + '', 'i');
|
||||
export const EMAIL_REG_EXP = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
// const markdownTestRegExp = /[`_*@~]/;
|
||||
export const MARKDOWN_REG_EXP = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s|\x01)(`|~~|\*\*|__|_-_|\|\|)([^\n]+?)\7([\x01\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m;
|
||||
|
|
|
@ -16,14 +16,14 @@ import checkBrackets from './checkBrackets';
|
|||
import getEmojiUnified from './getEmojiUnified';
|
||||
|
||||
export default function parseEntities(text: string) {
|
||||
let match: any;
|
||||
let match: RegExpMatchArray;
|
||||
let raw = text;
|
||||
const entities: MessageEntity[] = [];
|
||||
let matchIndex;
|
||||
let rawOffset = 0;
|
||||
// var start = tsNow()
|
||||
FULL_REG_EXP.lastIndex = 0;
|
||||
while((match = raw.match(FULL_REG_EXP))) {
|
||||
while(match = raw.match(FULL_REG_EXP)) {
|
||||
matchIndex = rawOffset + match.index;
|
||||
|
||||
// console.log('parseEntities match:', match);
|
||||
|
@ -101,6 +101,20 @@ export default function parseEntities(text: string) {
|
|||
length: 1 + match[13].length + (match[14] ? 1 + match[14].length : 0),
|
||||
unsafe: true
|
||||
});
|
||||
} else if(match[16]) { // Media timestamp
|
||||
const timestamp = match[16];
|
||||
const splitted: string[] = timestamp.split(':');
|
||||
const splittedLength = splitted.length;
|
||||
const hours = splittedLength === 3 ? +splitted[0] : 0;
|
||||
const minutes = +splitted[splittedLength === 3 ? 1 : 0];
|
||||
const seconds = +splitted[splittedLength - 1];
|
||||
entities.push({
|
||||
_: 'messageEntityTimestamp',
|
||||
offset: matchIndex + (/\D/.test(match[0][0]) ? 1 : 0),
|
||||
length: timestamp.length,
|
||||
raw: timestamp,
|
||||
time: hours * 3600 + minutes * 60 + seconds
|
||||
});
|
||||
}
|
||||
|
||||
raw = raw.substr(match.index + match[0].length);
|
||||
|
|
|
@ -975,6 +975,7 @@ export default function wrapRichText(text: string, options: Partial<{
|
|||
passEntities: Partial<{
|
||||
[_ in MessageEntity['_']]: boolean
|
||||
}>,
|
||||
maxMediaTimestamp: number,
|
||||
noEncoding: boolean,
|
||||
isSelectable: boolean,
|
||||
|
||||
|
@ -1462,6 +1463,20 @@ export default function wrapRichText(text: string, options: Partial<{
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'messageEntityTimestamp': {
|
||||
if(!options.maxMediaTimestamp || entity.time > options.maxMediaTimestamp) {
|
||||
break;
|
||||
}
|
||||
|
||||
element = document.createElement('a');
|
||||
element.classList.add('timestamp');
|
||||
element.dataset.timestamp = '' + entity.time;
|
||||
(element as HTMLAnchorElement).href = '#';
|
||||
element.setAttribute('onclick', 'setMediaTimestamp(this)');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!usedText && partText) {
|
||||
|
|
|
@ -168,6 +168,15 @@
|
|||
"params": [
|
||||
{"name": "unsafe", "type": "boolean"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "messageEntityTimestamp",
|
||||
"params": [
|
||||
{"name": "offset", "type": "number"},
|
||||
{"name": "length", "type": "number"},
|
||||
{"name": "time", "type": "number"},
|
||||
{"name": "raw", "type": "string"}
|
||||
],
|
||||
"type": "MessageEntity"
|
||||
}, {
|
||||
"predicate": "user",
|
||||
"params": [
|
||||
|
|
|
@ -2765,6 +2765,7 @@ $bubble-border-radius-big: 12px;
|
|||
--selection-background-color: var(--message-out-selection-background-color);
|
||||
--message-time-color: var(--message-out-time-color);
|
||||
--message-status-color: var(--message-out-status-color);
|
||||
--link-color: var(--message-primary-color);
|
||||
|
||||
.bubble-content {
|
||||
margin-left: auto;
|
||||
|
@ -2807,10 +2808,6 @@ $bubble-border-radius-big: 12px;
|
|||
}
|
||||
}
|
||||
|
||||
.anchor-url {
|
||||
color: var(--message-primary-color);
|
||||
}
|
||||
|
||||
/* .bubble-content-wrapper {
|
||||
> .user-avatar {
|
||||
left: auto;
|
||||
|
|
Loading…
Reference in New Issue