diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index a105a687..d07d9ddb 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -43,7 +43,7 @@ import LazyLoadQueue from "../lazyLoadQueue"; import ListenerSetter from "../../helpers/listenerSetter"; import PollElement from "../poll"; import AudioElement from "../audio"; -import { Message, MessageEntity, MessageReplyHeader, ReplyMarkup, Update } from "../../layer"; +import { Message, MessageEntity, MessageReplyHeader, Photo, PhotoSize, ReplyMarkup, Update, WebPage } from "../../layer"; import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { FocusDirection } from "../../helpers/fastSmoothScroll"; import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck"; @@ -841,11 +841,8 @@ export default class ChatBubbles { const message = this.chat.getMessage(bubbleMid) as Message.message; const peerId = this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id); const threadId = message.reply_to.reply_to_top_id; - - this.appMessagesManager.wrapSingleMessage(peerId, threadId).then(() => { - this.appMessagesManager.generateThreadServiceStartMessage(this.appMessagesManager.getMessageByPeer(peerId, threadId)); - this.chat.appImManager.setInnerPeer(peerId, message.fwd_from.saved_from_msg_id, 'discussion', threadId); - }); + const lastMsgId = message.fwd_from.saved_from_msg_id; + this.chat.appImManager.openThread(peerId, lastMsgId, threadId); } else { const message = this.appMessagesManager.filterMessages(this.chat.getMessage(bubbleMid), message => !!(message as Message.message).replies)[0] as Message.message; const replies = message.replies; @@ -2424,7 +2421,7 @@ export default class ChatBubbles { case 'messageMediaWebPage': { processingWebPage = true; - let webpage = messageMedia.webpage; + let webpage: WebPage.webPage | WebPage.webPageEmpty = messageMedia.webpage; ////////this.log('messageMediaWebPage', webpage); if(webpage._ === 'webPageEmpty') { break; @@ -2439,7 +2436,8 @@ export default class ChatBubbles { quote.classList.add('quote'); let previewResizer: HTMLDivElement, preview: HTMLDivElement; - if(webpage.photo || webpage.document) { + const photo: Photo.photo = webpage.photo as any; + if(photo || webpage.document) { previewResizer = document.createElement('div'); previewResizer.classList.add('preview-resizer'); preview = document.createElement('div'); @@ -2487,15 +2485,14 @@ export default class ChatBubbles { quoteTextDiv.append(previewResizer); } - // let t: HTMLElement; + let t: HTMLElement; if(webpage.site_name) { - let nameEl = document.createElement('a'); - nameEl.classList.add('webpage-name'); - nameEl.setAttribute('target', '_blank'); - nameEl.href = webpage.url || '#'; - setInnerHTML(nameEl, RichTextProcessor.wrapEmojiText(webpage.site_name)); - quoteTextDiv.append(nameEl); - // t = nameEl; + const html = RichTextProcessor.wrapRichText(webpage.url); + const a: HTMLAnchorElement = htmlToDocumentFragment(html).firstElementChild as any; + a.classList.add('webpage-name'); + setInnerHTML(a, RichTextProcessor.wrapEmojiText(webpage.site_name)); + quoteTextDiv.append(a); + t = a; } if(webpage.rTitle) { @@ -2503,7 +2500,7 @@ export default class ChatBubbles { titleDiv.classList.add('title'); setInnerHTML(titleDiv, webpage.rTitle); quoteTextDiv.append(titleDiv); - // t = titleDiv; + t = titleDiv; } if(webpage.rDescription) { @@ -2511,7 +2508,7 @@ export default class ChatBubbles { textDiv.classList.add('text'); setInnerHTML(textDiv, webpage.rDescription); quoteTextDiv.append(textDiv); - // t = textDiv; + t = textDiv; } /* if(t) { @@ -2522,15 +2519,15 @@ export default class ChatBubbles { quote.append(quoteTextDiv); - if(webpage.photo && !doc) { + if(photo && !doc) { bubble.classList.add('photo'); - const size = webpage.photo.sizes[webpage.photo.sizes.length - 1]; + const size: PhotoSize.photoSize = photo.sizes[photo.sizes.length - 1] as any; let isSquare = false; - if(size.w === size.h && quoteTextDiv.childElementCount) { + if(size.w === size.h && t) { bubble.classList.add('is-square-photo'); isSquare = true; - this.appPhotosManager.setAttachmentSize(webpage.photo, preview, 32, 32, false); + this.appPhotosManager.setAttachmentSize(photo, preview, 32, 32, false); /* if(t) { t.append(timeSpan); @@ -2540,7 +2537,7 @@ export default class ChatBubbles { } wrapPhoto({ - photo: webpage.photo, + photo, message, container: preview, boxWidth: isSquare ? 0 : mediaSizes.active.webpage.width, diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index bd3c5b03..ba01282b 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -272,11 +272,11 @@ export class AppImManager { this.addAnchorListener<{hashtag: string}>({ name: 'searchByHashtag', callback: (params) => { - if(!params) { + const {hashtag} = params; + if(!hashtag) { return; } - const {hashtag} = params; this.chat.initSearch('#' + hashtag + ' '); } }); @@ -316,34 +316,75 @@ export class AppImManager { }, parsePathname: true }); + + this.addAnchorListener<{ + // pathnameParams: ['c', string, string], + // uriParams: {thread?: number} + // } | { + // pathnameParams: [string, string?], + // uriParams: {comment?: number} + pathnameParams: ['c', string, string] | [string, string?], + uriParams: {thread?: number} | {comment?: number} + }>({ + name: 'im', + callback: (params) => { + console.log(params); + + const {pathnameParams, uriParams} = params; + if(pathnameParams[0] === 'c') { + const peerId = -+pathnameParams[1]; + const postId = appMessagesIdsManager.generateMessageId(+pathnameParams[2]); + const threadId = 'thread' in uriParams ? appMessagesIdsManager.generateMessageId(+uriParams.thread) : undefined; + + if(threadId) this.openThread(peerId, postId, threadId); + else this.setInnerPeer(peerId, postId); + } else { + const username = pathnameParams[0]; + const postId = pathnameParams[1] ? appMessagesIdsManager.generateMessageId(+pathnameParams[1]) : undefined; + const commentId = 'comment' in uriParams ? appMessagesIdsManager.generateMessageId(uriParams.comment) : undefined; + + this.openUsername(username, postId, undefined, commentId); + } + }, + parsePathname: true, + parseUriParams: true + }); } private addAnchorListener(options: { - name: 'showMaskedAlert' | 'execBotCommand' | 'searchByHashtag' | 'addstickers' | 'joinchat', + name: 'showMaskedAlert' | 'execBotCommand' | 'searchByHashtag' | 'addstickers' | 'joinchat' | 'im', callback: (params: Params, element: HTMLAnchorElement) => boolean | void, noParams?: boolean, - parsePathname?: boolean + parsePathname?: boolean, + parseUriParams?: boolean, }) { (window as any)[options.name] = (element: HTMLAnchorElement/* , e: Event */) => { cancelEvent(null); const href = element.href; - let params: any; + let pathnameParams: any[]; + let uriParams: any; + if(!options.noParams) { - params = options.parsePathname ? new URL(element.href).pathname.split('/').slice(1) : this.parseUriParams(href); + if(options.parsePathname) pathnameParams = new URL(element.href).pathname.split('/').slice(1); + if(options.parseUriParams) uriParams = this.parseUriParams(href); } - const res = options.callback(params, element); + let out: any; + if(pathnameParams && uriParams) { + out = {pathnameParams, uriParams}; + } else { + out = pathnameParams || uriParams; + } + + const res = options.callback(out, element); return res === undefined ? res : false; }; } private parseUriParams(uri: string, splitted = uri.split('?')) { - if(!splitted[1]) { - return; - } - const params: any = {}; + if(!splitted[1]) return params; splitted[1].split('&').forEach(item => { params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]); }); @@ -382,12 +423,14 @@ export class AppImManager { //location.hash = ''; }; - public openUsername(username: string, msgId?: number) { + public openUsername(username: string, msgId?: number, threadId?: number, commentId?: number) { return appUsersManager.resolveUsername(username).then(peer => { const isUser = peer._ === 'user'; const peerId = isUser ? peer.id : -peer.id; - return this.setInnerPeer(peerId, msgId); + if(threadId) return this.openThread(peerId, msgId, threadId); + else if(commentId) return this.openComment(peerId, msgId, commentId); + else return this.setInnerPeer(peerId, msgId); }, (err) => { if(err.type === 'USERNAME_NOT_OCCUPIED') { toast(i18n('NoUsernameFound')); @@ -395,6 +438,27 @@ export class AppImManager { }); } + /** + * Opens thread when peerId of discussion group is known + */ + public openThread(peerId: number, lastMsgId: number, threadId: number) { + return appMessagesManager.wrapSingleMessage(peerId, threadId).then(() => { + const message = appMessagesManager.getMessageByPeer(peerId, threadId); + appMessagesManager.generateThreadServiceStartMessage(message); + + return this.setInnerPeer(peerId, lastMsgId, 'discussion', threadId); + }); + } + + /** + * Opens comment directly from original channel + */ + public openComment(peerId: number, msgId: number, commentId: number) { + return appMessagesManager.getDiscussionMessage(peerId, msgId).then(message => { + return this.openThread(message.peerId, commentId, message.mid); + }); + } + public setCurrentBackground(broadcastEvent = false) { const theme = rootScope.getTheme(); diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts index e0a86982..4e121c8e 100644 --- a/src/lib/richtextprocessor.ts +++ b/src/lib/richtextprocessor.ts @@ -597,7 +597,7 @@ namespace RichTextProcessor { //inner = encodeEntities(replaceUrlEncodings(entityText)); } - const currentContext = url[0] === '#'; + const currentContext = !!onclick; if(!onclick && masked && !currentContext) { onclick = 'showMaskedAlert'; } @@ -644,12 +644,15 @@ namespace RichTextProcessor { } case 'messageEntityMention': { - const contextUrl = !options.noLinks && siteMentions[contextSite]; - if(contextUrl) { + // const contextUrl = !options.noLinks && siteMentions[contextSite]; + if(!options.noLinks) { const entityText = text.substr(entity.offset, entity.length); const username = entityText.substr(1); - insertPart(entity, ``, ''); + const {url, onclick} = wrapUrl('t.me/' + username); + + // insertPart(entity, ``, ''); + insertPart(entity, ``, ''); } break; @@ -811,43 +814,11 @@ namespace RichTextProcessor { case 'addstickers': onclick = path[0]; break; - - /* case 'joinchat': - onclick = 'joinchat'; - url = 'tg://join?invite=' + path[1]; - break; - - case 'addstickers': - onclick = 'addstickers'; - url = 'tg://addstickers?set=' + path[1]; - break; */ - + default: - if(path[1] && path[1].match(/^\d+$/)) { // https://t.me/.+/[0-9]+ (channel w/ username) - if(path[0] === 'c' && path[2]) { // https://t.me/c/111111111/111 (channel w/o username) - url = '#/im?p=' + path[1] + '&post=' + path[2]; - } else { // https://t.me/durov/151 (channel w/ username) - url = siteMentions['Telegram'].replace('{1}', path[0] + '&post=' + path[1]); - } - } else if(path.length === 1) { - const domainQuery = path[0].split('?'); - const domain = domainQuery[0]; - const query = domainQuery[1]; - - if(domain === 'iv') { - const match = (query || '').match(/url=([^&=]+)/); - if(match) { - url = match[1]; - try { - url = decodeURIComponent(url); - } catch (e) {} - - return wrapUrl(url, unsafe); - } - } - - url = siteMentions['Telegram'].replace('{1}', domain + (query ? '&' + query : '')); - //url = 'tg://resolve?domain=' + domain + (query ? '&' + query : ''); + if((path[1] && path[1].match(/^\d+(?:\?(?:comment|thread)=\d+)?$/)) || path.length === 1) { + onclick = 'im'; + break; } break; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index d4171f1f..8bb85139 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -860,6 +860,7 @@ $bubble-margin: .25rem; .webpage-name { font-size: var(--messages-secondary-text-size); font-weight: 500 !important; + text-decoration: none; @include hover() { text-decoration: underline;