diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 60d0b95e..a5a623f1 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -235,9 +235,10 @@ export default class ChatBubbles { this.bubbleGroups.removeBubble(bubble, tempId); */ if(message.media?.webpage && !bubble.querySelector('.web')) { - const mounted = this.getMountedBubble(mid); + this.renderMessage(message, true, false, bubble, false); + /* const mounted = this.getMountedBubble(mid); if(!mounted) return; - this.renderMessage(mounted.message, true, false, mounted.bubble, false); + this.renderMessage(mounted.message, true, false, mounted.bubble, false); */ } //delete this.bubbles[tempId]; diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 17fe03ac..2ab7faa9 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -12,7 +12,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; import opusDecodeController from "../../lib/opusDecodeController"; import RichTextProcessor from "../../lib/richtextprocessor"; import { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; -import { ButtonMenuItemOptions } from '../buttonMenu'; +import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import emoticonsDropdown from "../emoticonsDropdown"; import PopupCreatePoll from "../popupCreatePoll"; import PopupForward from '../popupForward'; @@ -28,6 +28,7 @@ import DivAndCaption from '../divAndCaption'; import ButtonMenuToggle from '../buttonMenuToggle'; import ListenerSetter from '../../helpers/listenerSetter'; import Button from '../button'; +import { attachContextMenuListener, openBtnMenu } from '../misc'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -55,6 +56,9 @@ export default class ChatInput { public attachMenu: HTMLButtonElement; private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[]; + public sendMenu: HTMLDivElement; + private sendMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[]; + public replyElements: { container?: HTMLElement, cancelBtn?: HTMLButtonElement, @@ -187,6 +191,26 @@ export default class ChatInput { this.attachMenu.classList.add('attach-file', 'tgico-attach'); this.attachMenu.classList.remove('tgico-more'); + this.sendMenuButtons = [{ + icon: 'mute', + text: 'Send Without Sound', + onClick: () => { + + }, + verify: (peerId: number) => true + }, { + icon: 'schedule', + text: 'Schedule Message', + onClick: () => { + + }, + verify: (peerId: number) => true + }]; + + this.sendMenu = ButtonMenu(this.sendMenuButtons, this.listenerSetter); + this.sendMenu.classList.add('menu-send', 'top-left'); + //this.inputContainer.append(this.sendMenu); + this.recordTimeEl = document.createElement('div'); this.recordTimeEl.classList.add('record-time'); @@ -213,7 +237,16 @@ export default class ChatInput { `); - this.btnSendContainer.append(this.recordRippleEl, this.btnSend); + attachContextMenuListener(this.btnSend, (e: any) => { + if(this.isInputEmpty()) { + return; + } + + cancelEvent(e); + openBtnMenu(this.sendMenu); + }, this.listenerSetter); + + this.btnSendContainer.append(this.recordRippleEl, this.btnSend, this.sendMenu); this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer); @@ -691,17 +724,21 @@ export default class ChatInput { //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); //const value = this.messageInput.innerText; - const value = getRichValue(this.messageInput); + const richValue = getRichValue(this.messageInput); - const entities = RichTextProcessor.parseEntities(value); - //console.log('messageInput entities', entities); + //const entities = RichTextProcessor.parseEntities(value); + const markdownEntities: MessageEntity[] = []; + const value = RichTextProcessor.parseMarkdown(richValue, markdownEntities); + const entities = RichTextProcessor.mergeEntities(markdownEntities, RichTextProcessor.parseEntities(value)); + + //this.chat.log('messageInput entities', richValue, value, markdownEntities); if(this.stickersHelper) { let emoticon = ''; if(entities.length && entities[0]._ == 'messageEntityEmoji') { const entity = entities[0]; - if(entity.length == value.length && !entity.offset) { - emoticon = value; + if(entity.length == richValue.length && !entity.offset) { + emoticon = richValue; } } @@ -714,16 +751,18 @@ export default class ChatInput { this.undoHistory.length = 0; } - const urlEntities = entities.filter(e => e._ == 'messageEntityUrl'); + const urlEntities: Array = entities.filter(e => e._ == 'messageEntityUrl' || e._ == 'messageEntityTextUrl') as any; if(urlEntities.length) { - const richEntities: MessageEntity[] = []; - const richValue = RichTextProcessor.parseMarkdown(getRichValue(this.messageInput), richEntities); - //console.log('messageInput url', entities, richEntities); for(const entity of urlEntities) { - const url = value.slice(entity.offset, entity.offset + entity.length); + let url: string; + if(entity._ === 'messageEntityTextUrl') { + url = entity.url; + } else { + url = richValue.slice(entity.offset, entity.offset + entity.length); - if(!(url.includes('http://') || url.includes('https://')) && !richEntities.find(e => e._ == 'messageEntityTextUrl')) { - continue; + if(!(url.includes('http://') || url.includes('https://'))) { + continue; + } } //console.log('messageInput url:', url); @@ -732,7 +771,7 @@ export default class ChatInput { this.lastUrl = url; this.willSendWebPage = null; apiManager.invokeApi('messages.getWebPage', { - url: url, + url, hash: 0 }).then((webpage) => { webpage = this.appWebPagesManager.saveWebPage(webpage); @@ -762,7 +801,7 @@ export default class ChatInput { } } - if(!value.trim() && !serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { + if(!richValue.trim() && !serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { this.messageInput.innerHTML = ''; this.appMessagesManager.setTyping(this.chat.peerId, 'sendMessageCancelAction'); diff --git a/src/components/misc.ts b/src/components/misc.ts index f55b4abd..4a3dd647 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -134,7 +134,8 @@ export const closeBtnMenu = () => { if(openedMenu) { openedMenu.classList.remove('active'); openedMenu.parentElement.classList.remove('menu-open'); - openedMenu.previousElementSibling.remove(); // remove overlay + //openedMenu.previousElementSibling.remove(); // remove overlay + if(menuOverlay) menuOverlay.remove(); openedMenu = null; } @@ -165,7 +166,7 @@ window.addEventListener('resize', () => { } */ }); -let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null; +let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null, menuOverlay: HTMLElement = null; export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { closeBtnMenu(); @@ -173,9 +174,19 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { openedMenu.classList.add('active'); openedMenu.parentElement.classList.add('menu-open'); - const overlay = document.createElement('div'); - overlay.classList.add('btn-menu-overlay'); - openedMenu.parentElement.insertBefore(overlay, openedMenu); + if(!menuOverlay) { + menuOverlay = document.createElement('div'); + menuOverlay.classList.add('btn-menu-overlay'); + + // ! because this event must be canceled, and can't cancel on menu click (below) + menuOverlay.addEventListener(CLICK_EVENT_NAME, (e) => { + cancelEvent(e); + onClick(e); + }); + } + + openedMenu.parentElement.insertBefore(menuOverlay, openedMenu); + //document.body.classList.add('disable-hover'); openedMenuOnClose = onClose; @@ -186,11 +197,11 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { window.addEventListener('contextmenu', onClick, {once: true}); } - // ! because this event must be canceled, and can't cancel on menu click (below) + /* // ! because this event must be canceled, and can't cancel on menu click (below) overlay.addEventListener(CLICK_EVENT_NAME, (e) => { cancelEvent(e); onClick(e); - }); + }); */ // ! safari iOS doesn't handle window click event on overlay, idk why document.addEventListener(CLICK_EVENT_NAME, onClick); diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 029e05ef..44d90a35 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -690,7 +690,7 @@ export default class AppSharedMediaTab implements SliderTab { const peerId = this.peerId; - let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes.filter(t => t !== this.sharedMediaType); + let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes.filter(t => t !== this.sharedMediaType && t !== 'inputMessagesFilterEmpty'); typesToLoad = typesToLoad.filter(type => !this.loadedAllMedia[type] || this.usedFromHistory[type] < this.historiesStorage[peerId][type].length); diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index 6fc6c0e3..246a5db1 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -119,6 +119,10 @@ const queryableFunctions = { throw new Error('Invalid file'); } */ + /* let perf = performance.now(); + let json = JSON.parse(jsString); + console.log('sticker decode:', performance.now() - perf); */ + const match = jsString.match(/"fr":\s*?(\d+?),/); const frameRate = +match?.[1] || DEFAULT_FPS; diff --git a/src/scss/partials/_button.scss b/src/scss/partials/_button.scss index 631a26db..7e4ed86c 100644 --- a/src/scss/partials/_button.scss +++ b/src/scss/partials/_button.scss @@ -192,11 +192,14 @@ //background-color: rgba(0, 0, 0, .2); } - &-toggle &-overlay { + /* &-toggle */&-overlay { left: -100vw; right: -100vw; top: -100vh; bottom: -100vh; + + width: auto !important; + max-width: none !important; } }