Browse Source

Search multiselect

Fix keyboard inputs on iOS
Alt+ArrowUp/ArrowDown shortcut
master
Eduard Kuzmenko 3 years ago
parent
commit
c2604ec14d
  1. 4
      src/components/appMediaViewer.ts
  2. 238
      src/components/appSearchSuper..ts
  3. 23
      src/components/chat/bubbles.ts
  4. 101
      src/components/chat/contextMenu.ts
  5. 66
      src/components/chat/input.ts
  6. 2
      src/components/chat/replyContainer.ts
  7. 938
      src/components/chat/selection.ts
  8. 8
      src/components/chat/topbar.ts
  9. 9
      src/components/popups/forward.ts
  10. 2
      src/components/rangeSelector.ts
  11. 15
      src/components/row.ts
  12. 86
      src/components/sendingStatus.ts
  13. 20
      src/components/sidebarRight/tabs/sharedMedia.ts
  14. 4
      src/components/singleTransition.ts
  15. 77
      src/components/transition.ts
  16. 3
      src/components/wrappers.ts
  17. 2
      src/config/app.ts
  18. 12
      src/helpers/dom/clickEvent.ts
  19. 2
      src/helpers/dom/fixSafariStickyInputFocusing.ts
  20. 1
      src/lang.ts
  21. 29
      src/lib/appManagers/appDialogsManager.ts
  22. 21
      src/lib/appManagers/appImManager.ts
  23. 2
      src/lib/appManagers/appMessagesManager.ts
  24. 1
      src/lib/rootScope.ts
  25. 5
      src/scss/components/_global.scss
  26. 2
      src/scss/partials/_button.scss
  27. 5
      src/scss/partials/_chat.scss
  28. 45
      src/scss/partials/_chatBubble.scss
  29. 25
      src/scss/partials/_chatlist.scss
  30. 14
      src/scss/partials/_checkbox.scss
  31. 14
      src/scss/partials/_ckin.scss
  32. 4
      src/scss/partials/_document.scss
  33. 4
      src/scss/partials/_profile.scss
  34. 183
      src/scss/partials/_rightSidebar.scss
  35. 4
      src/scss/partials/_row.scss
  36. 34
      src/scss/partials/_transition.scss
  37. 26
      src/scss/style.scss

4
src/components/appMediaViewer.ts

@ -1684,7 +1684,9 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1684,7 +1684,9 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
onForwardClick = () => {
if(this.currentMessageId) {
//appSidebarRight.forwardTab.open([this.currentMessageId]);
new PopupForward(this.currentPeerId, [this.currentMessageId], () => {
new PopupForward({
[this.currentPeerId]: [this.currentMessageId]
}, () => {
return this.close();
});
}

238
src/components/appSearchSuper..ts

@ -21,7 +21,7 @@ import AppMediaViewer from "./appMediaViewer"; @@ -21,7 +21,7 @@ import AppMediaViewer from "./appMediaViewer";
import { SearchGroup, SearchGroupType } from "./appSearch";
import { horizontalMenu } from "./horizontalMenu";
import LazyLoadQueue from "./lazyLoadQueue";
import { putPreloader } from "./misc";
import { attachContextMenuListener, openBtnMenu, positionMenu, putPreloader } from "./misc";
import { ripple } from "./ripple";
import Scrollable, { ScrollableX } from "./scrollable";
import { wrapDocument, wrapPhoto, wrapVideo } from "./wrappers";
@ -42,6 +42,14 @@ import { isTouchSupported } from "../helpers/touchSupport"; @@ -42,6 +42,14 @@ import { isTouchSupported } from "../helpers/touchSupport";
import handleTabSwipe from "../helpers/dom/handleTabSwipe";
import windowSize from "../helpers/windowSize";
import { formatPhoneNumber } from "../helpers/formatPhoneNumber";
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
import PopupForward from "./popups/forward";
import PopupDeleteMessages from "./popups/deleteMessages";
import Row from "./row";
import htmlToDocumentFragment from "../helpers/dom/htmlToDocumentFragment";
import { SearchSelection } from "./chat/selection";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent";
//const testScroll = false;
@ -69,6 +77,149 @@ export type SearchSuperMediaTab = { @@ -69,6 +77,149 @@ export type SearchSuperMediaTab = {
scroll?: {scrollTop: number, scrollHeight: number}
};
class SearchContextMenu {
private buttons: (ButtonMenuItemOptions & {verify?: () => boolean, withSelection?: true})[];
private element: HTMLElement;
private target: HTMLElement;
private peerId: number;
private mid: number;
private isSelected: boolean;
constructor(
private attachTo: HTMLElement,
private searchSuper: AppSearchSuper
) {
const onContextMenu = (e: MouseEvent) => {
if(this.init) {
this.init();
this.init = null;
}
let item: HTMLElement;
try {
item = findUpClassName(e.target, 'search-super-item');
} catch(e) {}
if(!item) return;
if(e instanceof MouseEvent) e.preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
if(e instanceof MouseEvent) e.cancelBubble = true;
this.target = item;
this.peerId = +item.dataset.peerId;
this.mid = +item.dataset.mid;
this.isSelected = searchSuper.selection.isMidSelected(this.peerId, this.mid);
this.buttons.forEach(button => {
let good: boolean;
if(this.isSelected && !button.withSelection) {
good = false;
} else {
good = button.verify ? button.verify() : true;
}
button.element.classList.toggle('hide', !good);
});
item.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
item.classList.remove('menu-open');
});
};
if(isTouchSupported) {
} else {
attachContextMenuListener(attachTo, onContextMenu as any);
}
}
private init() {
this.buttons = [{
icon: 'forward',
text: 'Forward',
onClick: this.onForwardClick
}, {
icon: 'forward',
text: 'Message.Context.Selection.Forward',
onClick: this.onForwardClick,
verify: () => this.isSelected &&
!this.searchSuper.selection.selectionForwardBtn.classList.contains('hide'),
withSelection: true
}, {
icon: 'message',
text: 'Message.Context.Goto',
onClick: this.onGotoClick,
withSelection: true
}, {
icon: 'select',
text: 'Message.Context.Select',
onClick: this.onSelectClick
}, {
icon: 'select',
text: 'Message.Context.Selection.Clear',
onClick: this.onClearSelectionClick,
verify: () => this.isSelected,
withSelection: true
}, {
icon: 'delete danger',
text: 'Delete',
onClick: this.onDeleteClick,
verify: () => appMessagesManager.canDeleteMessage(appMessagesManager.getMessageByPeer(this.peerId, this.mid))
}, {
icon: 'delete danger',
text: 'Message.Context.Selection.Delete',
onClick: this.onDeleteClick,
verify: () => this.isSelected && !this.searchSuper.selection.selectionDeleteBtn.classList.contains('hide'),
withSelection: true
}];
this.element = ButtonMenu(this.buttons);
this.element.classList.add('search-contextmenu', 'contextmenu');
document.getElementById('page-chats').append(this.element);
}
private onGotoClick = () => {
rootScope.dispatchEvent('history_focus', {
peerId: this.peerId,
mid: this.mid,
threadId: this.searchSuper.searchContext.threadId
});
};
private onForwardClick = () => {
if(this.searchSuper.selection.isSelecting) {
simulateClickEvent(this.searchSuper.selection.selectionForwardBtn);
} else {
new PopupForward({
[this.peerId]: [this.mid]
});
}
};
private onSelectClick = () => {
this.searchSuper.selection.toggleByElement(this.target);
};
private onClearSelectionClick = () => {
this.searchSuper.selection.cancelSelection();
};
private onDeleteClick = () => {
if(this.searchSuper.selection.isSelecting) {
simulateClickEvent(this.searchSuper.selection.selectionDeleteBtn);
} else {
new PopupDeleteMessages(this.peerId, [this.mid], 'chat');
}
};
}
export default class AppSearchSuper {
public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any;
@ -76,8 +227,9 @@ export default class AppSearchSuper { @@ -76,8 +227,9 @@ export default class AppSearchSuper {
public container: HTMLElement;
public nav: HTMLElement;
private navScrollableContainer: HTMLDivElement;
private tabsContainer: HTMLElement;
public navScrollableContainer: HTMLDivElement;
public tabsContainer: HTMLElement;
public navScrollable: ScrollableX;
private tabsMenu: HTMLElement;
private prevTabId = -1;
@ -88,7 +240,7 @@ export default class AppSearchSuper { @@ -88,7 +240,7 @@ export default class AppSearchSuper {
public usedFromHistory: Partial<{[type in SearchSuperType]: number}> = {};
public urlsToRevoke: string[] = [];
private searchContext: SearchSuperContext;
public searchContext: SearchSuperContext;
public loadMutex: Promise<any> = Promise.resolve();
private nextRates: Partial<{[type in SearchSuperType]: number}> = {};
@ -127,16 +279,23 @@ export default class AppSearchSuper { @@ -127,16 +279,23 @@ export default class AppSearchSuper {
public onChangeTab?: (mediaTab: SearchSuperMediaTab) => void;
public showSender? = false;
private searchContextMenu: SearchContextMenu;
public selection: SearchSelection;
constructor(options: Pick<AppSearchSuper, 'mediaTabs' | 'scrollable' | 'searchGroups' | 'asChatList' | 'groupByMonth' | 'hideEmptyTabs' | 'onChangeTab' | 'showSender'>) {
safeAssign(this, options);
this.container = document.createElement('div');
this.container.classList.add('search-super');
this.searchContextMenu = new SearchContextMenu(this.container, this);
this.selection = new SearchSelection(this, appMessagesManager);
const navScrollableContainer = this.navScrollableContainer = document.createElement('div');
navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky');
const navScrollable = new ScrollableX(navScrollableContainer);
const navScrollable = this.navScrollable = new ScrollableX(navScrollableContainer);
navScrollable.container.classList.add('search-super-nav-scrollable');
const nav = this.nav = document.createElement('nav');
nav.classList.add('search-super-tabs', 'menu-horizontal-div');
@ -285,6 +444,13 @@ export default class AppSearchSuper { @@ -285,6 +444,13 @@ export default class AppSearchSuper {
this.onTransitionEnd();
}, undefined, navScrollable);
attachClickEvent(this.tabsContainer, (e) => {
if(this.selection.isSelecting) {
cancelEvent(e);
this.selection.toggleByElement(findUpClassName(e.target, 'search-super-item'));
}
}, {capture: true, passive: false});
const onMediaClick = (className: string, targetClassName: string, inputFilter: MyInputMessagesFilter, e: MouseEvent) => {
const target = findUpClassName(e.target as HTMLDivElement, className);
@ -314,8 +480,20 @@ export default class AppSearchSuper { @@ -314,8 +480,20 @@ export default class AppSearchSuper {
.openMedia(message, targets[idx].element, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
};
this.tabs.inputMessagesFilterPhotoVideo.addEventListener('click', onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'));
this.tabs.inputMessagesFilterDocument.addEventListener('click', onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument'));
attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'));
attachClickEvent(this.tabs.inputMessagesFilterDocument, onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument'));
attachClickEvent(this.tabs.inputMessagesFilterUrl, (e) => {
const target = e.target as HTMLElement;
if(target.tagName === 'A') {
return;
}
try {
const a = findUpClassName(target, 'row').querySelector('.anchor-url:last-child') as HTMLAnchorElement;
a.click();
} catch(err) {}
});
this.mediaTab = this.mediaTabs[0];
@ -593,7 +771,7 @@ export default class AppSearchSuper { @@ -593,7 +771,7 @@ export default class AppSearchSuper {
let div = document.createElement('div');
let previewDiv = document.createElement('div');
previewDiv.classList.add('preview');
previewDiv.classList.add('preview', 'row-media');
//this.log('wrapping webpage', webpage);
@ -618,7 +796,19 @@ export default class AppSearchSuper { @@ -618,7 +796,19 @@ export default class AppSearchSuper {
let title = webpage.rTitle || '';
let subtitle = webpage.rDescription || '';
let url = RichTextProcessor.wrapRichText(webpage.url || '');
const subtitleFragment = htmlToDocumentFragment(subtitle);
const aFragment = htmlToDocumentFragment(RichTextProcessor.wrapRichText(webpage.url || ''));
const a = aFragment.firstElementChild;
if(a instanceof HTMLAnchorElement) {
a.innerText = decodeURIComponent(a.href);
}
if(subtitleFragment.firstChild) {
subtitleFragment.append('\n');
}
subtitleFragment.append(a);
if(!title) {
//title = new URL(webpage.url).hostname;
@ -632,18 +822,32 @@ export default class AppSearchSuper { @@ -632,18 +822,32 @@ export default class AppSearchSuper {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
}
const row = new Row({
title,
titleRight: titleAdditionHTML,
subtitle: subtitleFragment,
havePadding: true,
clickable: true,
noRipple: true
});
/* const mediaDiv = document.createElement('div');
mediaDiv.classList.add('row-media'); */
row.container.append(previewDiv);
/* ripple(div);
div.append(previewDiv);
div.insertAdjacentHTML('beforeend', `
<div class="title">${title}${titleAdditionHTML}</div>
<div class="subtitle">${subtitle}</div>
<div class="url">${url}</div>
${sender}
`);
`); */
if(div.innerText.trim().length) {
elemsToAppend.push({element: div, message});
if(row.container.innerText.trim().length) {
elemsToAppend.push({element: row.container, message});
}
}
break;
@ -675,6 +879,10 @@ export default class AppSearchSuper { @@ -675,6 +879,10 @@ export default class AppSearchSuper {
element.dataset.mid = '' + message.mid;
element.dataset.peerId = '' + message.peerId;
monthContainer.items[method](element);
if(this.selection.isSelecting) {
this.selection.toggleElementCheckbox(element, true);
}
});
}
@ -1245,6 +1453,10 @@ export default class AppSearchSuper { @@ -1245,6 +1453,10 @@ export default class AppSearchSuper {
this.usedFromHistory[mediaTab.inputFilter] = -1;
});
if(this.selection.isSelecting) {
this.selection.cancelSelection();
}
// * must go to first tab (это костыль)
/* const membersTab = this.mediaTabsMap.get('members');
if(membersTab) {

23
src/components/chat/bubbles.ts

@ -439,6 +439,7 @@ export default class ChatBubbles { @@ -439,6 +439,7 @@ export default class ChatBubbles {
});
});
// attachClickEvent(this.bubblesContainer, this.onBubblesClick, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(this.bubblesContainer)('click', this.onBubblesClick/* , {capture: true, passive: false} */);
if(DEBUG) {
@ -865,7 +866,7 @@ export default class ChatBubbles { @@ -865,7 +866,7 @@ export default class ChatBubbles {
}
if(!isTouchSupported && findUpClassName(target, 'time')) {
this.chat.selection.toggleByBubble(bubble);
this.chat.selection.toggleByElement(bubble);
return;
}
@ -884,7 +885,7 @@ export default class ChatBubbles { @@ -884,7 +885,7 @@ export default class ChatBubbles {
}
//this.chatSelection.toggleByBubble(bubble);
this.chat.selection.toggleByBubble(findUpClassName(target, 'grouped-item') || bubble);
this.chat.selection.toggleByElement(findUpClassName(target, 'grouped-item') || bubble);
return;
}
@ -1076,7 +1077,9 @@ export default class ChatBubbles { @@ -1076,7 +1077,9 @@ export default class ChatBubbles {
} else if(target.classList.contains('forward')) {
const mid = +bubble.dataset.mid;
const message = this.appMessagesManager.getMessageByPeer(this.peerId, mid);
new PopupForward(this.peerId, this.appMessagesManager.getMidsByMessage(message));
new PopupForward({
[this.peerId]: this.appMessagesManager.getMidsByMessage(message)
});
//appSidebarRight.forwardTab.open([mid]);
return;
}
@ -1390,7 +1393,7 @@ export default class ChatBubbles { @@ -1390,7 +1393,7 @@ export default class ChatBubbles {
});
if(permanent && this.chat.selection.isSelecting) {
this.chat.selection.deleteSelectedMids(mids);
this.chat.selection.deleteSelectedMids(this.peerId, mids);
}
animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP);
@ -2213,6 +2216,7 @@ export default class ChatBubbles { @@ -2213,6 +2216,7 @@ export default class ChatBubbles {
// ! reset due to album edit or delete item
this.bubbles[+message.mid] = bubble;
bubble.dataset.mid = message.mid;
bubble.dataset.peerId = '' + message.peerId;
bubble.dataset.timestamp = message.date;
const loadPromises: Promise<any>[] = [];
@ -2281,7 +2285,7 @@ export default class ChatBubbles { @@ -2281,7 +2285,7 @@ export default class ChatBubbles {
});
let canHaveTail = true;
let isStandaloneMedia = false;
let needToSetHTML = true;
if(totalEntities && !messageMedia) {
let emojiEntities = totalEntities.filter((e) => e._ === 'messageEntityEmoji');
@ -2308,6 +2312,7 @@ export default class ChatBubbles { @@ -2308,6 +2312,7 @@ export default class ChatBubbles {
}
bubble.classList.add('is-message-empty', 'emoji-big');
isStandaloneMedia = true;
canHaveTail = false;
needToSetHTML = false;
}
@ -2391,7 +2396,9 @@ export default class ChatBubbles { @@ -2391,7 +2396,9 @@ export default class ChatBubbles {
}
return new Promise<number>((resolve, reject) => {
new PopupForward(this.peerId, [], (peerId) => {
new PopupForward({
[this.peerId]: []
}, (peerId) => {
resolve(peerId);
}, () => {
reject();
@ -2468,8 +2475,6 @@ export default class ChatBubbles { @@ -2468,8 +2475,6 @@ export default class ChatBubbles {
const isOut = our && (!message.fwd_from || this.peerId !== rootScope.myId);
let nameContainer: HTMLElement = bubbleContainer;
let isStandaloneMedia = false;
// media
if(messageMedia/* && messageMedia._ === 'messageMediaPhoto' */) {
let attachmentDiv = document.createElement('div');
@ -2856,7 +2861,7 @@ export default class ChatBubbles { @@ -2856,7 +2861,7 @@ export default class ChatBubbles {
}
if(this.chat.selection.isSelecting) {
this.chat.selection.toggleBubbleCheckbox(bubble, true);
this.chat.selection.toggleElementCheckbox(bubble, true);
}
let savedFrom = '';

101
src/components/chat/contextMenu.ts

@ -19,11 +19,10 @@ import PopupPinMessage from "../popups/unpinMessage"; @@ -19,11 +19,10 @@ import PopupPinMessage from "../popups/unpinMessage";
import { copyTextToClipboard } from "../../helpers/clipboard";
import PopupSendNow from "../popups/sendNow";
import { toast } from "../toast";
import I18n, { i18n, LangPackKey } from "../../lib/langPack";
import I18n, { LangPackKey } from "../../lib/langPack";
import findUpClassName from "../../helpers/dom/findUpClassName";
import { cancelEvent } from "../../helpers/dom/cancelEvent";
import cancelSelection from "../../helpers/dom/cancelSelection";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty";
import { Message } from "../../layer";
import PopupReportMessages from "../popups/reportMessages";
@ -33,6 +32,7 @@ export default class ChatContextMenu { @@ -33,6 +32,7 @@ export default class ChatContextMenu {
private element: HTMLElement;
private isSelectable: boolean;
private isSelected: boolean;
private target: HTMLElement;
private isTargetAGroupedItem: boolean;
private isTextSelected: boolean;
@ -75,17 +75,6 @@ export default class ChatContextMenu { @@ -75,17 +75,6 @@ export default class ChatContextMenu {
let mid = +bubble.dataset.mid;
if(!mid) return;
// * если открыть контекстное меню для альбома не по бабблу, и последний элемент не выбран, чтобы показать остальные пункты
if(chat.selection.isSelecting && !contentWrapper) {
const mids = this.chat.getMidsByMid(mid);
if(mids.length > 1) {
const selectedMid = chat.selection.selectedMids.has(mid) ? mid : mids.find(mid => chat.selection.selectedMids.has(mid));
if(selectedMid) {
mid = selectedMid;
}
}
}
this.isSelectable = this.chat.selection.canSelectBubble(bubble);
this.peerId = this.chat.peerId;
//this.msgID = msgID;
@ -97,6 +86,19 @@ export default class ChatContextMenu { @@ -97,6 +86,19 @@ export default class ChatContextMenu {
);
this.isUsernameTarget = this.target.tagName === 'A' && this.target.classList.contains('mention');
// * если открыть контекстное меню для альбома не по бабблу, и последний элемент не выбран, чтобы показать остальные пункты
if(chat.selection.isSelecting && !contentWrapper) {
const mids = this.chat.getMidsByMid(mid);
if(mids.length > 1) {
const selectedMid = this.chat.selection.isMidSelected(this.peerId, mid) ?
mid :
mids.find(mid => this.chat.selection.isMidSelected(this.peerId, mid));
if(selectedMid) {
mid = selectedMid;
}
}
}
const groupedItem = findUpClassName(this.target, 'grouped-item');
this.isTargetAGroupedItem = !!groupedItem;
if(groupedItem) {
@ -105,6 +107,7 @@ export default class ChatContextMenu { @@ -105,6 +107,7 @@ export default class ChatContextMenu {
this.mid = mid;
}
this.isSelected = this.chat.selection.isMidSelected(this.peerId, this.mid);
this.message = this.chat.getMessage(this.mid);
this.buttons.forEach(button => {
@ -147,29 +150,10 @@ export default class ChatContextMenu { @@ -147,29 +150,10 @@ export default class ChatContextMenu {
if(good) {
cancelEvent(e);
//onContextMenu((e as TouchEvent).changedTouches[0]);
onContextMenu((e as TouchEvent).changedTouches ? (e as TouchEvent).changedTouches[0] : e as MouseEvent);
// onContextMenu((e as TouchEvent).changedTouches ? (e as TouchEvent).changedTouches[0] : e as MouseEvent);
onContextMenu(e);
}
}, {listenerSetter: this.chat.bubbles.listenerSetter});
attachContextMenuListener(attachTo, (e) => {
if(chat.selection.isSelecting) return;
// * these two lines will fix instant text selection on iOS Safari
document.body.classList.add('no-select'); // * need no-select on body because chat-input transforms in channels
attachTo.addEventListener('touchend', (e) => {
cancelEvent(e); // ! this one will fix propagation to document loader button, etc
document.body.classList.remove('no-select');
//this.chat.bubbles.onBubblesClick(e);
}, {once: true, capture: true});
cancelSelection();
//cancelEvent(e as any);
const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble');
if(bubble) {
chat.selection.toggleByBubble(bubble);
}
}, this.chat.bubbles.listenerSetter);
} else attachContextMenuListener(attachTo, onContextMenu, this.chat.bubbles.listenerSetter);
}
@ -183,7 +167,7 @@ export default class ChatContextMenu { @@ -183,7 +167,7 @@ export default class ChatContextMenu {
icon: 'send2',
text: 'Message.Context.Selection.SendNow',
onClick: this.onSendScheduledClick,
verify: () => this.chat.type === 'scheduled' && this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionSendNowBtn.hasAttribute('disabled'),
verify: () => this.chat.type === 'scheduled' && this.isSelected && !this.chat.selection.selectionSendNowBtn.hasAttribute('disabled'),
notDirect: () => true,
withSelection: true
}, {
@ -228,7 +212,21 @@ export default class ChatContextMenu { @@ -228,7 +212,21 @@ export default class ChatContextMenu {
icon: 'copy',
text: 'Message.Context.Selection.Copy',
onClick: this.onCopyClick,
verify: () => this.chat.selection.selectedMids.has(this.mid) && !![...this.chat.selection.selectedMids].find(mid => !!this.chat.getMessage(mid).message),
verify: () => {
if(!this.isSelected) {
return false;
}
for(const [peerId, mids] of this.chat.selection.selectedMids) {
for(const mid of mids) {
if(!!this.appMessagesManager.getMessageByPeer(peerId, mid).message) {
return true;
}
}
}
return false;
},
notDirect: () => true,
withSelection: true
}, {
@ -319,7 +317,7 @@ export default class ChatContextMenu { @@ -319,7 +317,7 @@ export default class ChatContextMenu {
text: 'Message.Context.Selection.Forward',
onClick: this.onForwardClick,
verify: () => this.chat.selection.selectionForwardBtn &&
this.chat.selection.selectedMids.has(this.mid) &&
this.isSelected &&
!this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
notDirect: () => true,
withSelection: true
@ -336,14 +334,14 @@ export default class ChatContextMenu { @@ -336,14 +334,14 @@ export default class ChatContextMenu {
icon: 'select',
text: 'Message.Context.Select',
onClick: this.onSelectClick,
verify: () => !this.message.action && !this.chat.selection.selectedMids.has(this.mid) && this.isSelectable,
verify: () => !this.message.action && !this.isSelected && this.isSelectable,
notDirect: () => true,
withSelection: true
}, {
icon: 'select',
text: 'Message.Context.Selection.Clear',
onClick: this.onClearSelectionClick,
verify: () => this.chat.selection.selectedMids.has(this.mid),
verify: () => this.isSelected,
notDirect: () => true,
withSelection: true
}, {
@ -355,7 +353,7 @@ export default class ChatContextMenu { @@ -355,7 +353,7 @@ export default class ChatContextMenu {
icon: 'delete danger',
text: 'Message.Context.Selection.Delete',
onClick: this.onDeleteClick,
verify: () => this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionDeleteBtn.hasAttribute('disabled'),
verify: () => this.isSelected && !this.chat.selection.selectionDeleteBtn.hasAttribute('disabled'),
notDirect: () => true,
withSelection: true
}];
@ -364,11 +362,11 @@ export default class ChatContextMenu { @@ -364,11 +362,11 @@ export default class ChatContextMenu {
this.element.id = 'bubble-contextmenu';
this.element.classList.add('contextmenu');
this.chat.container.append(this.element);
};
}
private onSendScheduledClick = () => {
if(this.chat.selection.isSelecting) {
this.chat.selection.selectionSendNowBtn.click();
simulateClickEvent(this.chat.selection.selectionSendNowBtn);
} else {
new PopupSendNow(this.peerId, this.chat.getMidsByMid(this.mid));
}
@ -384,11 +382,15 @@ export default class ChatContextMenu { @@ -384,11 +382,15 @@ export default class ChatContextMenu {
private onCopyClick = () => {
if(isSelectionEmpty()) {
const mids = this.chat.selection.isSelecting ? [...this.chat.selection.selectedMids].sort((a, b) => a - b) : [this.mid];
const mids = this.chat.selection.isSelecting ?
[...this.chat.selection.selectedMids.get(this.peerId)].sort((a, b) => a - b) :
[this.mid];
const str = mids.reduce((acc, mid) => {
const message = this.chat.getMessage(mid);
return acc + (message?.message ? message.message + '\n' : '');
}, '').trim();
copyTextToClipboard(str);
} else {
document.execCommand('copy');
@ -443,14 +445,17 @@ export default class ChatContextMenu { @@ -443,14 +445,17 @@ export default class ChatContextMenu {
private onForwardClick = () => {
if(this.chat.selection.isSelecting) {
this.chat.selection.selectionForwardBtn.click();
simulateClickEvent(this.chat.selection.selectionForwardBtn);
} else {
new PopupForward(this.peerId, this.isTargetAGroupedItem ? [this.mid] : this.chat.getMidsByMid(this.mid));
const mids = this.isTargetAGroupedItem ? [this.mid] : this.chat.getMidsByMid(this.mid);
new PopupForward({
[this.peerId]: mids
});
}
};
private onSelectClick = () => {
this.chat.selection.toggleByBubble(findUpClassName(this.target, 'grouped-item') || findUpClassName(this.target, 'bubble'));
this.chat.selection.toggleByElement(findUpClassName(this.target, 'grouped-item') || findUpClassName(this.target, 'bubble'));
};
private onClearSelectionClick = () => {
@ -459,7 +464,7 @@ export default class ChatContextMenu { @@ -459,7 +464,7 @@ export default class ChatContextMenu {
private onDeleteClick = () => {
if(this.chat.selection.isSelecting) {
this.chat.selection.selectionDeleteBtn.click();
simulateClickEvent(this.chat.selection.selectionDeleteBtn);
} else {
new PopupDeleteMessages(this.peerId, this.isTargetAGroupedItem ? [this.mid] : this.chat.getMidsByMid(this.mid), this.chat.type);
}

66
src/components/chat/input.ts

@ -77,6 +77,7 @@ import PeerTitle from '../peerTitle'; @@ -77,6 +77,7 @@ import PeerTitle from '../peerTitle';
import { fastRaf } from '../../helpers/schedulers';
import PopupDeleteMessages from '../popups/deleteMessages';
import fixSafariStickyInputFocusing, { IS_STICKY_INPUT_BUGGED } from '../../helpers/dom/fixSafariStickyInputFocusing';
import { copy } from '../../helpers/object';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -117,8 +118,7 @@ export default class ChatInput { @@ -117,8 +118,7 @@ export default class ChatInput {
private getWebPagePromise: Promise<void>;
private willSendWebPage: WebPage = null;
private forwardingMids: number[] = [];
private forwardingFromPeerId: number = 0;
private forwarding: {[fromPeerId: number]: number[]};
public replyToMsgId: number;
public editMsgId: number;
private noWebPage: true;
@ -1402,7 +1402,7 @@ export default class ChatInput { @@ -1402,7 +1402,7 @@ export default class ChatInput {
private onBtnSendClick = (e: Event) => {
cancelEvent(e);
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length || this.editMsgId) {
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwarding || this.editMsgId) {
if(this.recording) {
if((Date.now() - this.recordStartTime) < RECORD_MIN_TIME) {
this.onCancelRecordClick();
@ -1526,13 +1526,11 @@ export default class ChatInput { @@ -1526,13 +1526,11 @@ export default class ChatInput {
if(this.helperWaitingForward) return;
this.helperWaitingForward = true;
const fromId = this.forwardingFromPeerId;
const mids = this.forwardingMids.slice();
const helperFunc = this.helperFunc;
this.clearHelper();
this.updateSendBtn();
let selected = false;
new PopupForward(fromId, mids, () => {
new PopupForward(copy(this.forwarding), () => {
selected = true;
}, () => {
this.helperWaitingForward = false;
@ -1593,7 +1591,7 @@ export default class ChatInput { @@ -1593,7 +1591,7 @@ export default class ChatInput {
const isInputEmpty = this.isInputEmpty();
if(this.editMsgId) icon = 'edit';
else if(!this.recorder || this.recording || !isInputEmpty || this.forwardingMids.length) icon = this.chat.type === 'scheduled' ? 'schedule' : 'send';
else if(!this.recorder || this.recording || !isInputEmpty || this.forwarding) icon = this.chat.type === 'scheduled' ? 'schedule' : 'send';
else icon = 'record';
['send', 'record', 'edit', 'schedule'].forEach(i => {
@ -1673,17 +1671,18 @@ export default class ChatInput { @@ -1673,17 +1671,18 @@ export default class ChatInput {
}
// * wait for sendText set messageId for invokeAfterMsg
if(this.forwardingMids.length) {
const mids = this.forwardingMids.slice();
const fromPeerId = this.forwardingFromPeerId;
if(this.forwarding) {
const forwarding = copy(this.forwarding);
const peerId = this.chat.peerId;
const silent = this.sendSilent;
const scheduleDate = this.scheduleDate;
setTimeout(() => {
this.appMessagesManager.forwardMessages(peerId, fromPeerId, mids, {
for(const fromPeerId in forwarding) {
this.appMessagesManager.forwardMessages(peerId, +fromPeerId, forwarding[fromPeerId], {
silent,
scheduleDate: scheduleDate
});
}
}, 0);
}
@ -1751,17 +1750,26 @@ export default class ChatInput { @@ -1751,17 +1750,26 @@ export default class ChatInput {
f();
}
public initMessagesForward(fromPeerId: number, mids: number[]) {
public initMessagesForward(fromPeerIdsMids: {[fromPeerId: number]: number[]}) {
const f = () => {
//const peerTitles: string[]
const smth: Set<string | number> = new Set(mids.map(mid => {
const fromPeerIds = Object.keys(fromPeerIdsMids).map(str => +str);
const smth: Set<string | number> = new Set();
let length = 0;
fromPeerIds.forEach(fromPeerId => {
const mids = fromPeerIdsMids[fromPeerId];
mids.forEach(mid => {
const message = this.appMessagesManager.getMessageByPeer(fromPeerId, mid);
if(message.fwd_from && message.fwd_from.from_name && !message.fromId && !message.fwdFromId) {
return message.fwd_from.from_name;
if(message.fwd_from?.from_name && !message.fromId && !message.fwdFromId) {
smth.add(message.fwd_from.from_name);
} else {
return message.fromId;
smth.add(message.fromId);
}
}));
});
length += mids.length;
});
const onlyFirstName = smth.size > 2;
const peerTitles = [...smth].map(smth => {
@ -1777,25 +1785,30 @@ export default class ChatInput { @@ -1777,25 +1785,30 @@ export default class ChatInput {
title.append(peerTitles[0], i18n('AndOther', [peerTitles.length - 1]));
}
const firstMessage = this.appMessagesManager.getMessageByPeer(fromPeerId, mids[0]);
let firstMessage: any, usingFullAlbum: boolean;
if(fromPeerIds.length === 1) {
const fromPeerId = fromPeerIds[0];
const mids = fromPeerIdsMids[fromPeerId];
firstMessage = this.appMessagesManager.getMessageByPeer(fromPeerId, mids[0]);
let usingFullAlbum = !!firstMessage.grouped_id;
if(firstMessage.grouped_id) {
usingFullAlbum = !!firstMessage.grouped_id;
if(usingFullAlbum) {
const albumMids = this.appMessagesManager.getMidsByMessage(firstMessage);
if(albumMids.length !== mids.length || albumMids.find(mid => !mids.includes(mid))) {
if(albumMids.length !== length || albumMids.find(mid => !mids.includes(mid))) {
usingFullAlbum = false;
}
}
}
if(usingFullAlbum || length === 1) {
const mids = fromPeerIdsMids[fromPeerIds[0]];
const replyFragment = this.appMessagesManager.wrapMessageForReply(firstMessage, undefined, mids);
if(usingFullAlbum || mids.length === 1) {
this.setTopInfo('forward', f, title, replyFragment);
} else {
this.setTopInfo('forward', f, title, i18n('ForwardedMessageCount', [mids.length]));
this.setTopInfo('forward', f, title, i18n('ForwardedMessageCount', [length]));
}
this.forwardingMids = mids.slice();
this.forwardingFromPeerId = fromPeerId;
this.forwarding = fromPeerIdsMids;
};
f();
@ -1845,8 +1858,7 @@ export default class ChatInput { @@ -1845,8 +1858,7 @@ export default class ChatInput {
}
this.replyToMsgId = undefined;
this.forwardingMids.length = 0;
this.forwardingFromPeerId = 0;
this.forwarding = undefined;
this.editMsgId = undefined;
this.helperType = this.helperFunc = undefined;

2
src/components/chat/replyContainer.ts

@ -52,7 +52,7 @@ export function wrapReplyDivAndCaption(options: { @@ -52,7 +52,7 @@ export function wrapReplyDivAndCaption(options: {
media = media.webpage;
}
if(media.photo || (media.document && ['video', 'sticker', 'gif', 'round'].indexOf(media.document.type) !== -1)) {
if(media.photo || (media.document && ['video', 'sticker', 'gif', 'round', 'photo'].indexOf(media.document.type) !== -1)) {
middleware = appImManager.chat.bubbles.getMiddleware();
const lazyLoadQueue = appImManager.chat.bubbles.lazyLoadQueue;

938
src/components/chat/selection.ts

File diff suppressed because it is too large Load Diff

8
src/components/chat/topbar.ts

@ -249,13 +249,13 @@ export default class ChatTopbar { @@ -249,13 +249,13 @@ export default class ChatTopbar {
return;
}
const original = selection.toggleByBubble.bind(selection);
selection.toggleByBubble = (bubble) => {
const original = selection.toggleByElement.bind(selection);
selection.toggleByElement = (bubble) => {
appStateManager.pushToState('chatContextMenuHintWasShown', true);
toast(i18n('Chat.Menu.Hint'));
selection.toggleByBubble = original;
selection.toggleByBubble(bubble);
selection.toggleByElement = original;
selection.toggleByElement(bubble);
};
});
},

9
src/components/popups/forward.ts

@ -8,7 +8,12 @@ import appImManager from "../../lib/appManagers/appImManager"; @@ -8,7 +8,12 @@ import appImManager from "../../lib/appManagers/appImManager";
import PopupPickUser from "./pickUser";
export default class PopupForward extends PopupPickUser {
constructor(fromPeerId: number, mids: number[], onSelect?: (peerId: number) => Promise<void> | void, onClose?: () => void, overrideOnSelect = false) {
constructor(
peerIdMids: {[fromPeerId: number]: number[]},
onSelect?: (peerId: number) => Promise<void> | void,
onClose?: () => void,
overrideOnSelect = false
) {
super({
peerTypes: ['dialogs', 'contacts'],
onSelect: overrideOnSelect ? onSelect : async(peerId) => {
@ -20,7 +25,7 @@ export default class PopupForward extends PopupPickUser { @@ -20,7 +25,7 @@ export default class PopupForward extends PopupPickUser {
}
appImManager.setInnerPeer(peerId);
appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice());
appImManager.chat.input.initMessagesForward(peerIdMids);
},
onClose,
placeholder: 'ShareModal.Search.ForwardPlaceholder',

2
src/components/rangeSelector.ts

@ -73,11 +73,13 @@ export default class RangeSelector { @@ -73,11 +73,13 @@ export default class RangeSelector {
this.rect = this.container.getBoundingClientRect();
this.mousedown = true;
this.scrub(event);
this.container.classList.add('is-focused');
this.events?.onMouseDown && this.events.onMouseDown(event);
};
protected onMouseUp = (event: GrabEvent) => {
this.mousedown = false;
this.container.classList.remove('is-focused');
this.events?.onMouseUp && this.events.onMouseUp(event);
};

15
src/components/row.ts

@ -11,6 +11,7 @@ import { SliderSuperTab } from "./slider"; @@ -11,6 +11,7 @@ import { SliderSuperTab } from "./slider";
import RadioForm from "./radioForm";
import { i18n, LangPackKey } from "../lib/langPack";
import replaceContent from "../helpers/dom/replaceContent";
import setInnerHTML from "../helpers/dom/setInnerHTML";
export default class Row {
public container: HTMLElement;
@ -24,7 +25,7 @@ export default class Row { @@ -24,7 +25,7 @@ export default class Row {
constructor(options: Partial<{
icon: string,
subtitle: string,
subtitle: string | HTMLElement | DocumentFragment,
subtitleLangKey: LangPackKey,
subtitleLangArgs: any[],
radioField: Row['radioField'],
@ -35,7 +36,8 @@ export default class Row { @@ -35,7 +36,8 @@ export default class Row {
titleRight: string | HTMLElement,
clickable: boolean | ((e: Event) => void),
navigationTab: SliderSuperTab,
havePadding: boolean
havePadding: boolean,
noRipple: boolean
}> = {}) {
this.container = document.createElement(options.radioField || options.checkboxField ? 'label' : 'div');
this.container.classList.add('row');
@ -44,7 +46,11 @@ export default class Row { @@ -44,7 +46,11 @@ export default class Row {
this.subtitle.classList.add('row-subtitle');
this.subtitle.setAttribute('dir', 'auto');
if(options.subtitle) {
this.subtitle.innerHTML = options.subtitle;
if(typeof(options.subtitle) === 'string') {
setInnerHTML(this.subtitle, options.subtitle);
} else {
this.subtitle.append(options.subtitle);
}
} else if(options.subtitleLangKey) {
this.subtitle.append(i18n(options.subtitleLangKey, options.subtitleLangArgs));
}
@ -137,7 +143,10 @@ export default class Row { @@ -137,7 +143,10 @@ export default class Row {
}
this.container.classList.add('row-clickable', 'hover-effect');
if(!options.noRipple) {
ripple(this.container, undefined, undefined, true);
}
/* if(options.radioField || options.checkboxField) {
this.container.prepend(this.container.lastElementChild);

86
src/components/sendingStatus.ts

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { Message } from "../layer";
/* import findUpClassName from "../helpers/dom/findUpClassName";
import rootScope from "../lib/rootScope";
import Transition from "./transition"; */
export enum SENDING_STATUS {
Error = -1,
Pending,
Sent,
Read
}
export function getSendingStatus(message: Message.message | Message.messageService) {
return message.pFlags.is_outgoing ?
SENDING_STATUS.Pending : (
message.pFlags.unread ?
SENDING_STATUS.Sent :
SENDING_STATUS.Read
);
}
export function setSendingStatus(
container: HTMLElement,
message?: Message.message | Message.messageService,
disableAnimationIfRippleFound?: boolean
) {
let className: 'check' | 'checks' | 'sending';
if(message?.pFlags.out) {
if(message.pFlags.is_outgoing) {
className = 'sending';
} else if(message.pFlags.unread) {
className = 'check';
} else {
className = 'checks';
}
}
if(!className) {
container.textContent = '';
return;
}
const iconClassName = 'tgico-' + className;
const lastElement = container.lastElementChild as HTMLElement;
if(lastElement && lastElement.classList.contains(iconClassName)) {
return;
}
const element = document.createElement('i');
element.classList.add('sending-status-icon', /* 'transition-item', */ iconClassName);
container.append(element);
if(lastElement) {
lastElement.remove();
}
/* if(!lastElement) {
element.classList.add('active');
return;
}
const select = Transition(container, undefined, 350, () => {
lastElement.remove();
}, false, true, false);
let animate = rootScope.settings.animationsEnabled && className !== 'sending' && !lastElement.classList.contains('tgico-sending');
if(disableAnimationIfRippleFound && animate) {
const parent = findUpClassName(container, 'rp');
if(parent.querySelector('.c-ripple__circle') || parent.matches(':hover')) {
animate = false;
}
}
select(element, animate, lastElement); */
/* SetTransition(lastElement, 'is-visible', false, 350, () => {
// lastElement.remove();
}, 2);
SetTransition(element, 'is-visible', true, 350, undefined, 2); */
}

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

@ -245,7 +245,8 @@ class PeerProfileAvatars { @@ -245,7 +245,8 @@ class PeerProfileAvatars {
const rect = this.container.getBoundingClientRect();
const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
// const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
const e = _e;
const x = e.pageX;
const clickX = x - rect.left;
@ -1055,16 +1056,15 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -1055,16 +1056,15 @@ export default class AppSharedMediaTab extends SliderSuperTab {
const inputFilter = mediaTab.inputFilter;
const filtered = this.searchSuper.filterMessagesByType(mids.map(mid => appMessagesManager.getMessageByPeer(peerId, mid)), inputFilter);
if(filtered.length) {
if(this.historiesStorage[peerId][inputFilter]) {
this.historiesStorage[peerId][inputFilter].unshift(...filtered.map(message => ({mid: message.mid, peerId: message.peerId})));
const history = this.historiesStorage[peerId][inputFilter];
if(history) {
history.unshift(...filtered.map(message => ({mid: message.mid, peerId: message.peerId})));
}
if(this.peerId === peerId && this.searchSuper.usedFromHistory[inputFilter] !== -1) {
this.searchSuper.usedFromHistory[inputFilter] += filtered.length;
this.searchSuper.performSearchResult(filtered, mediaTab, false);
}
break;
}
}
}
@ -1078,17 +1078,21 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -1078,17 +1078,21 @@ export default class AppSharedMediaTab extends SliderSuperTab {
for(const type of this.searchSuper.mediaTabs) {
const inputFilter = type.inputFilter;
if(!this.historiesStorage[peerId][inputFilter]) continue;
const history = this.historiesStorage[peerId][inputFilter];
if(!history) continue;
const idx = history.findIndex(m => m.mid === mid);
if(idx !== -1) {
history.splice(idx, 1);
if(this.peerId === peerId) {
const container = this.searchSuper.tabs[inputFilter];
const div = container.querySelector(`div[data-mid="${mid}"][data-peer-id="${peerId}"]`);
const div = container.querySelector(`div[data-mid="${mid}"][data-peer-id="${peerId}"]`) as HTMLElement;
if(div) {
if(this.searchSuper.selection.isSelecting) {
this.searchSuper.selection.toggleByElement(div);
}
div.remove();
}

4
src/components/singleTransition.ts

@ -26,6 +26,10 @@ const SetTransition = ( @@ -26,6 +26,10 @@ const SetTransition = (
}
}
// if(forwards && className && element.classList.contains(className) && !element.classList.contains('animating')) {
// return;
// }
if(useRafs && rootScope.settings.animationsEnabled && duration) {
element.dataset.raf = '' + window.requestAnimationFrame(() => {
delete element.dataset.raf;

77
src/components/transition.ts

@ -10,6 +10,7 @@ import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck"; @@ -10,6 +10,7 @@ import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck";
import whichChild from "../helpers/dom/whichChild";
import findUpClassName from "../helpers/dom/findUpClassName";
import { isSafari } from "../helpers/userAgent";
import { cancelEvent } from "../helpers/dom/cancelEvent";
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width;
@ -80,7 +81,13 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight @@ -80,7 +81,13 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
};
}
export const TransitionSlider = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade' | 'slide-fade' | 'none'/* | 'counter' */, transitionTime: number, onTransitionEnd?: (id: number) => void, isHeavy = true) => {
export const TransitionSlider = (
content: HTMLElement,
type: 'tabs' | 'navigation' | 'zoom-fade' | 'slide-fade' | 'none'/* | 'counter' */,
transitionTime: number,
onTransitionEnd?: (id: number) => void,
isHeavy = true
) => {
let animationFunction: TransitionFunction = null;
switch(type) {
@ -101,14 +108,26 @@ export const TransitionSlider = (content: HTMLElement, type: 'tabs' | 'navigatio @@ -101,14 +108,26 @@ export const TransitionSlider = (content: HTMLElement, type: 'tabs' | 'navigatio
type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void);
const Transition = (content: HTMLElement, animationFunction: TransitionFunction, transitionTime: number, onTransitionEnd?: (id: number) => void, isHeavy = true) => {
const Transition = (
content: HTMLElement,
animationFunction: TransitionFunction,
transitionTime: number,
onTransitionEnd?: (id: number) => void,
isHeavy = true,
once = false,
withAnimationListener = true
) => {
const onTransitionEndCallbacks: Map<HTMLElement, Function> = new Map();
let animationDeferred: CancellablePromise<void>;
let animationStarted = 0;
// let animationStarted = 0;
let from: HTMLElement = null;
// TODO: check for transition type (transform, etc) using by animationFunction
content.addEventListener(animationFunction ? 'transitionend' : 'animationend', (e) => {
if(withAnimationListener) {
const listenerName = animationFunction ? 'transitionend' : 'animationend';
const onEndEvent = (e: TransitionEvent | AnimationEvent) => {
cancelEvent(e);
if((e.target as HTMLElement).parentElement !== content) {
return;
}
@ -134,29 +153,49 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction, @@ -134,29 +153,49 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction,
}
content.classList.remove('animating', 'backwards', 'disable-hover');
});
function selectTab(id: number | HTMLElement, animate = true) {
const self = selectTab;
if(once) {
content.removeEventListener(listenerName, onEndEvent/* , {capture: false} */);
from = animationDeferred = undefined;
onTransitionEndCallbacks.clear();
}
};
// TODO: check for transition type (transform, etc) using by animationFunction
content.addEventListener(listenerName, onEndEvent/* , {passive: true, capture: false} */);
}
function selectTab(id: number | HTMLElement, animate = true, overrideFrom?: typeof from) {
if(overrideFrom) {
from = overrideFrom;
}
if(id instanceof HTMLElement) {
id = whichChild(id);
}
const prevId = self.prevId();
const prevId = selectTab.prevId();
if(id === prevId) return false;
//console.log('selectTab id:', id);
const _from = from;
const to = content.children[id] as HTMLElement;
if(!rootScope.settings.animationsEnabled || prevId === -1) {
animate = false;
}
if(!withAnimationListener) {
const timeout = content.dataset.timeout;
if(timeout !== undefined) {
clearTimeout(+timeout);
}
delete content.dataset.timeout;
}
if(!animate) {
if(_from) _from.classList.remove('active', 'to', 'from');
if(from) from.classList.remove('active', 'to', 'from');
if(to) {
to.classList.remove('to', 'from');
to.classList.add('active');
@ -170,12 +209,21 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction, @@ -170,12 +209,21 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction,
return;
}
if(!withAnimationListener) {
content.dataset.timeout = '' + window.setTimeout(() => {
to.classList.remove('to');
from && from.classList.remove('from');
content.classList.remove('animating', 'backwards', 'disable-hover');
delete content.dataset.timeout;
}, transitionTime);
}
if(from) {
from.classList.remove('to');
from.classList.add('from');
}
content.classList.add('animating', 'disable-hover');
content.classList.add('animating'/* , 'disable-hover' */);
const toRight = prevId < id;
content.classList.toggle('backwards', !toRight);
@ -200,7 +248,8 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction, @@ -200,7 +248,8 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction,
});
}
if(_from/* && false */) {
if(from/* && false */) {
const _from = from;
const callback = () => {
_from.classList.remove('active', 'from');
@ -223,7 +272,7 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction, @@ -223,7 +272,7 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction,
if(isHeavy) {
if(!animationDeferred) {
animationDeferred = deferredPromise<void>();
animationStarted = performance.now();
// animationStarted = performance.now();
}
dispatchHeavyAnimationEvent(animationDeferred, transitionTime * 2);

3
src/components/wrappers.ts

@ -550,7 +550,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS @@ -550,7 +550,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
icoDiv.classList.add('document-ico');
const cacheContext = appDownloadManager.getCacheContext(doc);
if(doc.thumbs?.length || (message.pFlags.is_outgoing && cacheContext.url && doc.type === 'photo')) {
if((doc.thumbs?.length || (message.pFlags.is_outgoing && cacheContext.url && doc.type === 'photo')) && doc.mime_type !== 'image/gif') {
docDiv.classList.add('document-with-thumb');
let imgs: HTMLImageElement[] = [];
@ -1564,6 +1564,7 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, @@ -1564,6 +1564,7 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
const container = document.createElement('div');
container.classList.add('document-container');
container.dataset.mid = '' + mid;
container.dataset.peerId = '' + message.peerId;
const wrapper = document.createElement('div');
wrapper.classList.add('document-wrapper');

2
src/config/app.ts

@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org'; @@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org';
const App = {
id: 1025907,
hash: '452b0359b988148995f22ff0f4229750',
version: '0.8.2',
version: '0.8.3',
langPackVersion: '0.3.3',
langPack: 'macos',
langPackCode: 'en',

12
src/helpers/dom/clickEvent.ts

@ -8,9 +8,9 @@ import type ListenerSetter from "../listenerSetter"; @@ -8,9 +8,9 @@ import type ListenerSetter from "../listenerSetter";
import { isTouchSupported } from "../touchSupport";
import simulateEvent from "./dispatchEvent";
export const CLICK_EVENT_NAME: 'mousedown' | 'touchend' | 'click' = (isTouchSupported ? 'mousedown' : 'click') as any;
export const CLICK_EVENT_NAME: 'mousedown' /* | 'touchend' */ | 'click' = (isTouchSupported ? 'mousedown' : 'click') as any;
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter, touchMouseDown: true}>;
export function attachClickEvent(elem: HTMLElement, callback: (e: TouchEvent | MouseEvent) => void, options: AttachClickOptions = {}) {
export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* TouchEvent | */MouseEvent) => void, options: AttachClickOptions = {}) {
const add = options.listenerSetter ? options.listenerSetter.add(elem) : elem.addEventListener.bind(elem);
// const remove = options.listenerSetter ? options.listenerSetter.removeManual.bind(options.listenerSetter, elem) : elem.removeEventListener.bind(elem);
@ -46,11 +46,11 @@ export function attachClickEvent(elem: HTMLElement, callback: (e: TouchEvent | M @@ -46,11 +46,11 @@ export function attachClickEvent(elem: HTMLElement, callback: (e: TouchEvent | M
}
export function detachClickEvent(elem: HTMLElement, callback: (e: TouchEvent | MouseEvent) => void, options?: AddEventListenerOptions) {
if(CLICK_EVENT_NAME === 'touchend') {
elem.removeEventListener('touchstart', callback, options);
} else {
// if(CLICK_EVENT_NAME === 'touchend') {
// elem.removeEventListener('touchstart', callback, options);
// } else {
elem.removeEventListener(CLICK_EVENT_NAME, callback, options);
}
// }
}
export function simulateClickEvent(elem: HTMLElement) {

2
src/helpers/dom/fixSafariStickyInputFocusing.ts

@ -56,7 +56,7 @@ if(IS_STICKY_INPUT_BUGGED) { @@ -56,7 +56,7 @@ if(IS_STICKY_INPUT_BUGGED) {
// let hasFocus = false;
let lastFocusOutTimeStamp = 0;
document.addEventListener('focusin', (e) => {
if((e.timeStamp - lastFocusOutTimeStamp) < 50/* && document.activeElement === input */) {
if(!(e.target as HTMLElement).classList.contains('is-sticky-input-bugged') || (e.timeStamp - lastFocusOutTimeStamp) < 50/* && document.activeElement === input */) {
return;
}

1
src/lang.ts

@ -793,6 +793,7 @@ const lang = { @@ -793,6 +793,7 @@ const lang = {
"Message.Context.Select": "Select",
"Message.Context.Pin": "Pin",
"Message.Context.Unpin": "Unpin",
"Message.Context.Goto": "Show Message",
"MessageContext.CopyMessageLink1": "Copy Message Link",
"NewPoll.Anonymous": "Anonymous Voting",
"NewPoll.Explanation.Placeholder": "Add a Comment (Optional)",

29
src/lib/appManagers/appDialogsManager.ts

@ -22,7 +22,7 @@ import rootScope from "../rootScope"; @@ -22,7 +22,7 @@ import rootScope from "../rootScope";
import apiUpdatesManager from "./apiUpdatesManager";
import appPeersManager from './appPeersManager';
import appImManager from "./appImManager";
import appMessagesManager, { Dialog } from "./appMessagesManager";
import appMessagesManager, { Dialog, MyMessage } from "./appMessagesManager";
import appStateManager, { State } from "./appStateManager";
import appUsersManager from "./appUsersManager";
import Button from "../../components/button";
@ -51,6 +51,7 @@ import windowSize from "../../helpers/windowSize"; @@ -51,6 +51,7 @@ import windowSize from "../../helpers/windowSize";
import isInDOM from "../../helpers/dom/isInDOM";
import appPhotosManager, { MyPhoto } from "./appPhotosManager";
import { MyDocument } from "./appDocsManager";
import { setSendingStatus } from "../../components/sendingStatus";
export type DialogDom = {
avatarEl: AvatarElement,
@ -440,6 +441,7 @@ export class AppDialogsManager { @@ -440,6 +441,7 @@ export class AppDialogsManager {
public setFilterId(filterId: number) {
this.filterId = filterId;
this.indexKey = appMessagesManager.dialogsStorage ? appMessagesManager.dialogsStorage.getDialogIndexKey(this.filterId) : 'index';
rootScope.filterId = filterId;
}
private async onStateLoaded(state: State) {
@ -1433,22 +1435,15 @@ export class AppDialogsManager { @@ -1433,22 +1435,15 @@ export class AppDialogsManager {
}
}
const lastMessage = dialog.draft?._ === 'draftMessage' ?
dialog.draft :
appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
if(!lastMessage.deleted && lastMessage.pFlags.out && lastMessage.peerId !== rootScope.myId/* &&
dialog.read_outbox_max_id */) { // maybe comment, 06.20.2020
const isUnread = !!lastMessage.pFlags?.unread
/* && dialog.read_outbox_max_id !== 0 */; // maybe uncomment, 31.01.2020
if(isUnread) {
dom.statusSpan.classList.remove('tgico-checks');
dom.statusSpan.classList.add('tgico-check');
} else {
dom.statusSpan.classList.remove('tgico-check');
dom.statusSpan.classList.add('tgico-checks');
let setStatusMessage: MyMessage;
if(dialog.draft?._ !== 'draftMessage') {
const lastMessage: MyMessage = appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
if(!lastMessage.deleted && lastMessage.pFlags.out && lastMessage.peerId !== rootScope.myId) {
setStatusMessage = lastMessage;
}
} else dom.statusSpan.classList.remove('tgico-check', 'tgico-checks');
}
setSendingStatus(dom.statusSpan, setStatusMessage, true);
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
let isPinned: boolean;
@ -1672,7 +1667,7 @@ export class AppDialogsManager { @@ -1672,7 +1667,7 @@ export class AppDialogsManager {
li.dataset.peerId = '' + peerId;
const statusSpan = document.createElement('span');
statusSpan.classList.add('message-status');
statusSpan.classList.add('message-status', 'sending-status'/* , 'transition', 'reveal' */);
const lastTimeSpan = document.createElement('span');
lastTimeSpan.classList.add('message-time');

21
src/lib/appManagers/appImManager.ts

@ -806,6 +806,16 @@ export class AppImManager { @@ -806,6 +806,16 @@ export class AppImManager {
if(e.code === 'KeyC' && (e.ctrlKey || e.metaKey) && target.tagName !== 'INPUT') {
return;
} else if(e.altKey && (e.code === 'ArrowUp' || e.code === 'ArrowDown') && rootScope.peerId) {
const folder = appMessagesManager.dialogsStorage.getFolder(rootScope.filterId, true);
const idx = folder.findIndex(dialog => dialog.peerId === rootScope.peerId);
if(idx !== -1) {
const nextIndex = e.code === 'ArrowUp' ? idx - 1 : idx + 1;
const nextDialog = folder[nextIndex];
if(nextDialog) {
this.setPeer(nextDialog.peerId);
}
}
} else if(e.code === 'ArrowUp') {
if(!chat.input.editMsgId && chat.input.isInputEmpty()) {
const historyStorage = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
@ -854,23 +864,18 @@ export class AppImManager { @@ -854,23 +864,18 @@ export class AppImManager {
document.body.addEventListener('keydown', onKeyDown);
rootScope.addEventListener('history_multiappend', (e) => {
const msgIdsByPeer = e;
rootScope.addEventListener('history_multiappend', (msgIdsByPeer) => {
for(const peerId in msgIdsByPeer) {
appSidebarRight.sharedMediaTab.renderNewMessages(+peerId, Array.from(msgIdsByPeer[peerId]));
}
});
rootScope.addEventListener('history_delete', (e) => {
const {peerId, msgs} = e;
rootScope.addEventListener('history_delete', ({peerId, msgs}) => {
appSidebarRight.sharedMediaTab.deleteDeletedMessages(peerId, Array.from(msgs));
});
// Calls when message successfully sent and we have an id
rootScope.addEventListener('message_sent', (e) => {
const {storage, tempId, mid} = e;
rootScope.addEventListener('message_sent', ({storage, tempId, mid}) => {
const message = appMessagesManager.getMessageFromStorage(storage, mid);
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerId, [mid]);
});

2
src/lib/appManagers/appMessagesManager.ts

@ -2171,7 +2171,7 @@ export class AppMessagesManager { @@ -2171,7 +2171,7 @@ export class AppMessagesManager {
//return Object.keys(this.groupedMessagesStorage[grouped_id]).map(id => +id).sort((a, b) => a - b);
}
public getMidsByMessage(message: any) {
public getMidsByMessage(message: Message.message) {
if(message?.grouped_id) return this.getMidsByAlbum(message.grouped_id);
else return [message.mid];
}

1
src/lib/rootScope.ts

@ -147,6 +147,7 @@ export class RootScope extends EventListenerBase<{ @@ -147,6 +147,7 @@ export class RootScope extends EventListenerBase<{
public connectionStatus: {[name: string]: ConnectionStatusChange} = {};
public settings: State['settings'];
public peerId = 0;
public filterId = 0;
public systemTheme: Theme['name'];
public config: Partial<Config.config> = {
forwarded_count_max: 100,

5
src/scss/components/_global.scss

@ -155,6 +155,11 @@ Utility Classes @@ -155,6 +155,11 @@ Utility Classes
border-radius: 0 !important;
}
.disable-hover/* ,
.disable-hover * */ {
pointer-events: none !important;
}
/* .flex-grow {
flex-grow: 1;
}

2
src/scss/partials/_button.scss

@ -212,7 +212,7 @@ @@ -212,7 +212,7 @@
right: 0;
top: 0;
bottom: 0;
z-index: 1;
z-index: 3;
cursor: default;
user-select: none;
//background-color: rgba(0, 0, 0, .2);

5
src/scss/partials/_chat.scss

@ -1147,11 +1147,6 @@ $chat-helper-size: 36px; @@ -1147,11 +1147,6 @@ $chat-helper-size: 36px;
@include respond-to(handhelds) {
padding: 0 $chat-padding-handhelds;
html.is-ios & {
-webkit-user-select: none;
-webkit-touch-callout: none;
}
}
&.is-chat {

45
src/scss/partials/_chatBubble.scss

@ -221,7 +221,8 @@ $bubble-margin: .25rem; @@ -221,7 +221,8 @@ $bubble-margin: .25rem;
// ! hide context menu for media on android
.bubbles.is-selecting & {
img,
video {
video,
a {
pointer-events: none;
}
}
@ -1031,7 +1032,7 @@ $bubble-margin: .25rem; @@ -1031,7 +1032,7 @@ $bubble-margin: .25rem;
&-title,
&-subtitle,
i {
color: #fff;
color: #fff !important;
}
&-border {
@ -1412,31 +1413,31 @@ $bubble-margin: .25rem; @@ -1412,31 +1413,31 @@ $bubble-margin: .25rem;
position: absolute;
/* position: relative;
width: max-content; */
bottom: .1rem;
right: .2rem;
border-radius: 12px;
bottom: .1875rem;
right: .1875rem;
border-radius: .75rem;
background-color: var(--message-time-background);
padding: 0 .2rem;
padding: 0 .3125rem;
z-index: 2;
.time {
margin-left: 0;
color: #fff;
visibility: visible;
display: flex;
align-items: center;
padding: 0 2.5px;
line-height: 18px;
pointer-events: all; // show title
padding: 0;
margin: 0;
white-space: nowrap;
height: 18px;
.inner {
right: unset;
bottom: unset;
color: #fff;
margin: inherit;
&:after {
color: #fff;
}
.inner {
display: none;
}
}
}
@ -2102,6 +2103,10 @@ $bubble-margin: .25rem; @@ -2102,6 +2103,10 @@ $bubble-margin: .25rem;
.reply, .name {
right: calc(100% + 10px);
}
.message {
right: 0;
}
}
&:not(.just-media) {
@ -2117,14 +2122,6 @@ $bubble-margin: .25rem; @@ -2117,14 +2122,6 @@ $bubble-margin: .25rem;
}
}
&.sticker,
&.round,
&.emoji-big {
.message {
right: 0;
}
}
.quote:before {
background-color: var(--message-out-primary-color);
}
@ -2152,10 +2149,6 @@ $bubble-margin: .25rem; @@ -2152,10 +2149,6 @@ $bubble-margin: .25rem;
}
}
&.is-message-empty .time .inner {
color: var(--message-out-primary-color);
}
/* &.is-message-empty .time:after {
margin-bottom: 1px;
} */

25
src/scss/partials/_chatlist.scss

@ -109,6 +109,7 @@ ul.chatlist { @@ -109,6 +109,7 @@ ul.chatlist {
} */
li {
--background: unset;
//height: var(--height);
height: 72px;
//max-height: var(--height);
@ -124,12 +125,14 @@ ul.chatlist { @@ -124,12 +125,14 @@ ul.chatlist {
padding-right: 8.5px;
padding-left: 8.5px; */
overflow: hidden;
background: var(--background);
@include respond-to(handhelds) {
border-radius: 0;
}
@include hover-background-effect();
//@include hover-background-effect();
@include hover(gray, --background, false);
&.is-muted {
.user-title {
@ -183,13 +186,13 @@ ul.chatlist { @@ -183,13 +186,13 @@ ul.chatlist {
} */
&.menu-open {
background: var(--light-secondary-text-color);
--background: var(--light-secondary-text-color);
}
@include respond-to(not-handhelds) {
&.active {
--background: var(--primary-color) !important;
//background: var(--light-secondary-text-color);
background: var(--primary-color) !important;
.user-caption,
.tgico-chatspinned:before,
@ -405,25 +408,25 @@ ul.chatlist { @@ -405,25 +408,25 @@ ul.chatlist {
}
.message-status {
margin-right: .1rem;
//margin-top: .3rem;
margin-top: -.3rem;
margin-right: 0.125rem;
display: inline-block;
vertical-align: middle;
&[class*=" tgico-"] {
color: var(--chatlist-status-color);
line-height: 1;
width: 1.25rem;
height: 1.25rem;
font-size: 1.25rem;
}
position: relative;
margin-top: -.0625rem;
&:before {
vertical-align: middle;
}
}
.message-time {
/* .message-time {
vertical-align: middle;
}
} */
.tgico-chatspinned {
background: transparent;

14
src/scss/partials/_checkbox.scss

@ -56,10 +56,12 @@ @@ -56,10 +56,12 @@
}
&-background {
// it is needed for circle scale animation
top: -15%;
right: -15%;
bottom: -15%;
left: -15%;
background-color: var(--primary-color);
transform: scale(1);
border-radius: 50%;
@ -138,6 +140,14 @@ @@ -138,6 +140,14 @@
.checkbox-box {
border-radius: 50%;
overflow: auto;
&-background {
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&-border {
border: 2px solid var(--secondary-color);
@ -147,6 +157,10 @@ @@ -147,6 +157,10 @@
&-check {
--offset: calc(var(--size) - (var(--size) / 2 + .125rem));
}
html.is-safari & {
-webkit-mask-image: none;
}
}
}

14
src/scss/partials/_ckin.scss

@ -287,6 +287,9 @@ video::-webkit-media-controls-enclosure { @@ -287,6 +287,9 @@ video::-webkit-media-controls-enclosure {
margin: 0;
outline: none;
caret-color: var(--color);
position: absolute;
top: -.5rem;
bottom: -.5rem;
&:focus {
outline: none;
@ -348,17 +351,24 @@ video::-webkit-media-controls-enclosure { @@ -348,17 +351,24 @@ video::-webkit-media-controls-enclosure {
position: absolute;
right: 0;
top: 50%;
transform: translate(calc(var(--thumb-size) / 2), -50%);
transform: translate(calc(var(--thumb-size) / 2), -50%) scale(1);
@include animation-level(2) {
transition: transform .125s ease-in-out;
}
}
}
}
&.is-focused .progress-line__filled:not(.progress-line__loaded):after {
transform: translate(calc(var(--thumb-size) / 2), -50%) scale(1.25);
}
&__loaded, &:before {
opacity: .3;
background-color: var(--secondary-color);
}
&__seek,
&__filled,
&__loaded {
border-radius: var(--border-radius);

4
src/scss/partials/_document.scss

@ -10,6 +10,10 @@ @@ -10,6 +10,10 @@
padding-left: 4.25rem;
height: 70px;
.media-photo {
border-radius: inherit;
}
&-ico {
background-color: var(--background-color);
border-radius: $border-radius;

4
src/scss/partials/_profile.scss

@ -194,7 +194,7 @@ @@ -194,7 +194,7 @@
position: relative;
width: 100%;
.checkbox-field {
/* .checkbox-field {
margin: 0;
padding: 0;
margin-left: -54px;
@ -202,7 +202,7 @@ @@ -202,7 +202,7 @@
.checkbox-caption {
padding-left: 54px;
}
} */
&-wrapper {
flex: 1 1 auto;

183
src/scss/partials/_rightSidebar.scss

@ -310,7 +310,9 @@ @@ -310,7 +310,9 @@
display: none;
}
.document-name, .audio-title, .title {
.document-name,
.audio-title,
.title {
display: flex;
justify-content: space-between;
}
@ -347,6 +349,11 @@ @@ -347,6 +349,11 @@
overflow: hidden;
}
.checkbox-field {
right: .25rem;
top: .25rem;
}
/* span.video-play {
background-color: var(--message-time-background);
color: #fff;
@ -357,6 +364,37 @@ @@ -357,6 +364,37 @@
} */
}
.checkbox {
&-box {
box-shadow: 0px 0px 3px 0px rgb(0 0 0 / 40%);
&-border {
border-color: var(--message-checkbox-border-color);
}
&-background {
background-color: var(--message-checkbox-color);
}
}
&-field {
position: absolute;
z-index: 2;
margin: 0;
}
}
.document,
.audio {
.checkbox-field {
top: 50%;
left: 0;
margin-left: 2rem;
margin-top: 1rem;
transform: translateY(-50%);
}
}
&-content-media &-month {
&-items {
width: 100%;
@ -385,7 +423,8 @@ @@ -385,7 +423,8 @@
//height: 54px;
height: calc(48px + 1.5rem);
&-ico, &-download {
&-ico,
&-download {
width: 48px;
height: 48px;
border-radius: 5px !important;
@ -412,24 +451,21 @@ @@ -412,24 +451,21 @@
.search-super-item {
display: flex;
flex-direction: column;
margin-top: 20px;
margin-left: 5px;
padding-bottom: 2px;
//padding-bottom: 10px;
padding-left: 4.4375rem;
position: relative;
padding-left: 60px;
overflow: hidden;
//min-height: 48px;
min-height: 58px;
min-height: 4.375rem;
cursor: pointer;
justify-content: flex-start;
}
.preview {
.row-media {
height: 3rem;
width: 3rem;
border-radius: .375rem;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
left: .6875rem;
&.empty {
display: flex;
@ -440,45 +476,62 @@ @@ -440,45 +476,62 @@
text-transform: uppercase;
background-color: var(--primary-color);
}
}
/* .anchor-url {
&:before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: " ";
cursor: pointer;
}
} */
.media-photo {
object-fit: cover;
width: 100%;
height: 100%;
border-radius: inherit;
}
.row-title {
margin-top: .1875rem;
}
.url {
white-space: nowrap;
text-overflow: ellipsis;
.row-subtitle {
overflow: hidden;
font-size: 14px;
margin-top: -1px;
white-space: pre-wrap;
text-overflow: ellipsis;
word-break: break-word;
&.sender {
margin-top: .125rem;
}
}
.title {
font-size: 16px;
margin-top: 2px;
.sent-time {
margin: 1px 0 0;
}
.subtitle {
font-size: 14px;
max-width: 310px;
&.sender {
margin-top: 2px;
}
.checkbox-field {
padding: 0 !important;
margin: 2rem 0 0 -1.75rem !important;
}
@include respond-to(not-handhelds) {
.search-super-month-items {
padding: 0 24px 15px 15px;
margin: .5625rem;
}
}
@include respond-to(handhelds) {
padding: 0 16px 15px 7px;
.search-super-month-name {
padding: .875rem 1rem;
}
}
}
&-content-music, &-content-voice {
@ -528,6 +581,76 @@ @@ -528,6 +581,76 @@
}
}
}
&-tabs-scrollable {
.search-super-nav-scrollable {
opacity: 1;
}
.search-super-nav-scrollable,
.search-super-selection-container {
@include animation-level(2) {
transition: opacity .2s ease-in-out;
}
}
&.is-selecting {
&:not(.backwards) {
.search-super-nav-scrollable {
opacity: 0;
}
.search-super-selection-container {
opacity: 1;
}
}
}
}
&.is-selecting {
a {
pointer-events: none;
}
.row {
&:not(.menu-open) {
background-color: transparent !important;
}
}
}
&-selection {
&-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
opacity: 0;
.btn-icon + .btn-icon {
margin-left: .5rem;
}
@include respond-to(handhelds) {
padding: 0 .5rem;
}
}
&-count {
flex-grow: 1;
font-weight: 500;
color: var(--primary-text-color);
white-space: nowrap;
text-transform: capitalize;
margin-left: 1.5rem;
}
}
}
#search-container {

4
src/scss/partials/_row.scss

@ -115,4 +115,8 @@ @@ -115,4 +115,8 @@
margin: 0 !important;
left: .5rem;
}
&.menu-open {
background-color: var(--light-secondary-text-color);
}
}

34
src/scss/partials/_transition.scss

@ -1,14 +1,7 @@ @@ -1,14 +1,7 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
// * Jolly Cobra's transition
.transition {
--easeOutSine: cubic-bezier(.39, .575, .565, 1);
--easeInSine: cubic-bezier(.47, 0, .745, .715);
.transition-item {
> .transition-item {
position: absolute;
top: 0;
left: 0;
@ -71,6 +64,8 @@ @@ -71,6 +64,8 @@
* slide-fade
*/
&.slide-fade {
--easeOutSine: cubic-bezier(.39, .575, .565, 1);
--easeInSine: cubic-bezier(.47, 0, .745, .715);
position: relative;
> .from {
@ -117,6 +112,18 @@ @@ -117,6 +112,18 @@
}
}
}
/* &.reveal {
> .to {
clip-path: inset(0 100% 0 0);
}
&.animating {
> .to {
animation: reveal-in 350ms ease-in;
}
}
} */
}
/*
@ -188,6 +195,15 @@ @@ -188,6 +195,15 @@
}
}
/* @keyframes reveal-in {
0% {
clip-path: inset(0 100% 0 0);
}
100% {
clip-path: inset(0 0 0 0);
}
} */
/* .zoom-fade {
transition: .15s ease-in-out opacity, .15s ease-in-out transform;
transform: scale3d(1.1, 1.1, 1);

26
src/scss/style.scss

@ -406,6 +406,10 @@ html.is-ios { @@ -406,6 +406,10 @@ html.is-ios {
//&, body {
position: fixed; // fix iOS fullscreen scroll
//}
// disable image longtapping
-webkit-user-select: none;
-webkit-touch-callout: none;
}
@supports(padding: unquote('max(0px)')) {
@ -490,11 +494,6 @@ input, textarea, button, select, a, div { @@ -490,11 +494,6 @@ input, textarea, button, select, a, div {
//}
}
.disable-hover/* ,
.disable-hover * */ {
pointer-events: none !important;
}
@include respond-to(not-handhelds) {
.only-handhelds {
display: none !important;
@ -1359,3 +1358,20 @@ middle-ellipsis-element { @@ -1359,3 +1358,20 @@ middle-ellipsis-element {
content: "W";
}
}
.sending-status {
&:empty {
display: none;
}
/* &.animating {
.sending-status-icon {
background: var(--background);
}
} */
&-icon {
position: absolute;
line-height: 1 !important;
}
}

Loading…
Cancel
Save