diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index 0d324f08..fcffacd6 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -35,6 +35,9 @@ export default class ChatContextMenu { bubble = bubbleContainer ? bubbleContainer.parentElement : findUpClassName(e.target, 'bubble'); } catch(e) {} + // ! context menu click by date bubble (there is no pointer-events) + if(!bubble) return; + if(e instanceof MouseEvent) e.preventDefault(); if(this.element.classList.contains('active')) { return false; diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index d3beb474..6fd05fe7 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -22,6 +22,7 @@ import Scrollable from "../scrollable"; import { toast } from "../toast"; import { wrapReply } from "../wrappers"; import InputField from '../inputField'; +import { MessageEntity } from '../../layer'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -195,35 +196,56 @@ export class ChatInput { const value = this.messageInput.innerText; const entities = RichTextProcessor.parseEntities(value); - //console.log('messageInput entities', entities); + console.log('messageInput entities', entities); - const entityUrl = entities.find(e => e._ == 'messageEntityUrl'); - if(entityUrl) { // need to get webpage - const url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length); + const urlEntities = entities.filter(e => e._ == 'messageEntityUrl'); + 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); - //console.log('messageInput url:', url); - - if(this.lastUrl != url) { - this.lastUrl = url; - this.willSendWebPage = null; - apiManager.invokeApi('messages.getWebPage', { - url: url, - hash: 0 - }).then((webpage) => { - webpage = appWebPagesManager.saveWebPage(webpage); - if(webpage._ == 'webPage') { - if(this.lastUrl != url) return; - //console.log('got webpage: ', webpage); + if(!(url.includes('http://') || url.includes('https://')) && !richEntities.find(e => e._ == 'messageEntityTextUrl')) { + continue; + } - this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || ''); + //console.log('messageInput url:', url); + + if(this.lastUrl != url) { + this.lastUrl = url; + this.willSendWebPage = null; + apiManager.invokeApi('messages.getWebPage', { + url: url, + hash: 0 + }).then((webpage) => { + webpage = appWebPagesManager.saveWebPage(webpage); + if(webpage._ == 'webPage') { + if(this.lastUrl != url) return; + //console.log('got webpage: ', webpage); + + this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || ''); + + delete this.noWebPage; + this.willSendWebPage = webpage; + } + }); + } - delete this.noWebPage; - this.willSendWebPage = webpage; - } - }); + break; + } + } else if(this.lastUrl) { + this.lastUrl = ''; + delete this.noWebPage; + this.willSendWebPage = null; + + if(this.helperType) { + this.helperFunc(); + } else { + this.clearHelper(); } } - + if(!value.trim() && !serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { this.messageInput.innerHTML = ''; @@ -481,7 +503,9 @@ export class ChatInput { }; } - this.replyElements.cancelBtn.addEventListener('click', () => { + this.replyElements.cancelBtn.addEventListener('click', (e) => { + cancelEvent(e); + if(this.willSendWebPage) { this.noWebPage = true; this.willSendWebPage = null; diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index 936c001d..d7b18e06 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -8,31 +8,7 @@ import CheckboxField from "../checkbox"; import PopupDeleteMessages from "../popupDeleteMessages"; import PopupForward from "../popupForward"; import { toast } from "../toast"; - -const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void) => { - const timeout = element.dataset.timeout; - if(timeout !== undefined) { - clearTimeout(+timeout); - } - - if(forwards) { - element.classList.add(className); - } - - element.classList.add('animating'); - - element.classList.toggle('backwards', !forwards); - element.dataset.timeout = '' + setTimeout(() => { - delete element.dataset.timeout; - if(!forwards) { - element.classList.remove('backwards', className); - } - - element.classList.remove('animating'); - - onTransitionEnd && onTransitionEnd(); - }, duration); -}; +import SetTransition from "../singleTransition"; const MAX_SELECTION_LENGTH = 100; //const MIN_CLICK_MOVE = 32; // minimum bubble height @@ -61,7 +37,16 @@ export default class ChatSelection { bubblesContainer.addEventListener('mousedown', (e) => { //console.log('selection mousedown', e); - if(e.button != 0 || (!this.selectedMids.size && !(e.target as HTMLElement).classList.contains('bubble'))) { // LEFT BUTTON + const bubble = findUpClassName(e.target, 'bubble'); + // LEFT BUTTON + // проверка внизу нужна для того, чтобы не активировать селект если target потомок .bubble + if(e.button != 0 + || ( + !this.selectedMids.size + && !(e.target as HTMLElement).classList.contains('bubble') + && bubble + ) + ) { return; } @@ -80,7 +65,12 @@ export default class ChatSelection { } */ //const foundTargets: Map = new Map(); + let canceledSelection = false; const onMouseMove = (e: MouseEvent) => { + if(!canceledSelection) { + cancelSelection(); + canceledSelection = true; + } /* if(!good) { if(Math.abs(e.x - x) > MIN_CLICK_MOVE || Math.abs(e.y - y) > MIN_CLICK_MOVE) { good = true; @@ -93,7 +83,7 @@ export default class ChatSelection { foundTargets.set(e.target as HTMLElement, true); */ const bubble = findUpClassName(e.target, 'bubble'); if(!bubble) { - console.error('found no bubble', e); + //console.error('found no bubble', e); return; } diff --git a/src/components/inputField.ts b/src/components/inputField.ts index 300a2218..9ec0cf77 100644 --- a/src/components/inputField.ts +++ b/src/components/inputField.ts @@ -96,7 +96,8 @@ const InputField = (options: { processInput = () => { const wasError = input.classList.contains('error'); - const inputLength = plainText ? input.value.length : getRichValue(input).length; + // * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol + const inputLength = plainText ? input.value.length : [...getRichValue(input)].length; const diff = maxLength - inputLength; const isError = diff < 0; input.classList.toggle('error', isError); diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts index e9e19fac..7871308b 100644 --- a/src/components/sidebarLeft/tabs/editProfile.ts +++ b/src/components/sidebarLeft/tabs/editProfile.ts @@ -204,24 +204,24 @@ export default class AppEditProfileTab implements SliderTab { Object.assign(this.originalValues, { firstName: user.first_name, lastName: user.last_name, - userName: user.username + userName: user.username, + bio: '' }); this.firstNameInput.innerHTML = user.rFirstName; this.lastNameInput.innerHTML = RichTextProcessor.wrapRichText(user.last_name, {noLinks: true, noLinebreaks: true}); + this.bioInput.innerHTML = ''; this.userNameInput.value = this.originalValues.userName = user.username ?? ''; - this.firstNameInput.classList.remove('error'); - this.lastNameInput.classList.remove('error'); - this.bioInput.classList.remove('error'); - this.userNameInput.classList.remove('valid', 'error'); this.userNameInput.nextElementSibling.innerHTML = 'Username (optional)'; appProfileManager.getProfile(user.id, true).then(userFull => { - if(userFull.rAbout) { + if(userFull.about) { this.originalValues.bio = userFull.about; this.bioInput.innerHTML = userFull.rAbout; + + this.handleChange(); } }); @@ -233,6 +233,7 @@ export default class AppEditProfileTab implements SliderTab { this.uploadAvatar = null; this.setProfileUrl(); + this.handleChange(); } public isUsernameValid(username: string) { @@ -268,5 +269,6 @@ export default class AppEditProfileTab implements SliderTab { onCloseAfterTimeout() { this.nextBtn.classList.remove('is-visible'); + this.firstNameInput.innerHTML = this.lastNameInput.innerHTML = this.bioInput.innerHTML = ''; } } \ No newline at end of file diff --git a/src/components/singleTransition.ts b/src/components/singleTransition.ts new file mode 100644 index 00000000..8dc3d67d --- /dev/null +++ b/src/components/singleTransition.ts @@ -0,0 +1,26 @@ +const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void) => { + const timeout = element.dataset.timeout; + if(timeout !== undefined) { + clearTimeout(+timeout); + } + + if(forwards) { + element.classList.add(className); + } + + element.classList.add('animating'); + + element.classList.toggle('backwards', !forwards); + element.dataset.timeout = '' + setTimeout(() => { + delete element.dataset.timeout; + if(!forwards) { + element.classList.remove('backwards', className); + } + + element.classList.remove('animating'); + + onTransitionEnd && onTransitionEnd(); + }, duration); +}; + +export default SetTransition; \ No newline at end of file diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 54732c89..e93da33b 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1515,16 +1515,17 @@ export class AppImManager { } public highlightBubble(element: HTMLElement) { - if(element.dataset.timeout) { - clearTimeout(+element.dataset.timeout); + const datasetKey = 'highlightTimeout'; + if(element.dataset[datasetKey]) { + clearTimeout(+element.dataset[datasetKey]); element.classList.remove('is-highlighted'); void element.offsetWidth; // reflow } element.classList.add('is-highlighted'); - element.dataset.timeout = '' + setTimeout(() => { + element.dataset[datasetKey] = '' + setTimeout(() => { element.classList.remove('is-highlighted'); - delete element.dataset.timeout; + delete element.dataset[datasetKey]; }, 2000); } diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index dce0e65d..c66c050c 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -153,6 +153,7 @@ $bubble-margin: .25rem; z-index: 2; transition: opacity .3s ease; opacity: 0.99999; // for safari + pointer-events: none; &.is-sticky { opacity: 0.00001; // for safari @@ -172,6 +173,7 @@ $bubble-margin: .25rem; .bubble__container { cursor: pointer; + pointer-events: all; } }