Skip to content

Commit 1f932dc

Browse files
committed
feat: implemented multi forward of messages, extended share media with opportunity to add emoji, refactored EmoticonsDropdown to support appending to custom container and inject tabs to it
1 parent 2e3ab76 commit 1f932dc

File tree

18 files changed

+620
-210
lines changed

18 files changed

+620
-210
lines changed

src/components/appSelectPeers.ts

Lines changed: 143 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import rootScope from '../lib/rootScope';
1111
import Scrollable from './scrollable';
1212
import {FocusDirection} from '../helpers/fastSmoothScroll';
1313
import CheckboxField from './checkboxField';
14-
import {i18n, LangPackKey, _i18n} from '../lib/langPack';
14+
import {_i18n, i18n, LangPackKey} from '../lib/langPack';
1515
import findUpAttribute from '../helpers/dom/findUpAttribute';
1616
import findUpClassName from '../helpers/dom/findUpClassName';
1717
import PeerTitle from './peerTitle';
@@ -38,12 +38,112 @@ import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex';
3838
import {generateDelimiter} from './generateDelimiter';
3939
import SettingSection from './settingSection';
4040
import liteMode from '../helpers/liteMode';
41+
import {ButtonMenuItemOptions, ButtonMenuSync} from "./buttonMenu";
42+
import ListenerSetter from "../helpers/listenerSetter";
43+
import {attachContextMenuListener} from "../helpers/dom/attachContextMenuListener";
44+
import positionMenu from "../helpers/positionMenu";
45+
import contextMenuController from "../helpers/contextMenuController";
46+
import {addFullScreenListener, isFullScreen} from "../helpers/dom/fullScreen";
4147

4248
type SelectSearchPeerType = 'contacts' | 'dialogs' | 'channelParticipants';
4349

4450
// TODO: правильная сортировка для addMembers, т.е. для peerType: 'contacts', потому что там идут сначала контакты - потом неконтакты, а должно всё сортироваться по имени
51+
export class AppSelectPeersContextMenu {
52+
private buttons: (ButtonMenuItemOptions & { verify: (peerId: PeerId) => boolean | Promise<boolean> })[];
53+
private element: HTMLElement;
54+
private targetPeerId: PeerId;
55+
private managers: AppManagers;
56+
57+
58+
constructor(options: {
59+
listenerSetter: ListenerSetter,
60+
onContextElement: HTMLElement,
61+
toggleMultiSelectState: () => void,
62+
selectContact: (key: PeerId | string) => void
63+
}) {
64+
const {listenerSetter} = options;
65+
this.buttons = [{
66+
icon: 'select',
67+
text: 'Message.Context.Select',
68+
verify: () => true,
69+
onClick: (e) => {
70+
cancelEvent(e);
71+
options.toggleMultiSelectState();
72+
options.selectContact(this.targetPeerId);
73+
console.log(this.targetPeerId)
74+
}
75+
}];
76+
this.element = ButtonMenuSync({buttons: this.buttons, listenerSetter});
77+
this.element.classList.add('select-peers-menu', 'night');
78+
79+
attachContextMenuListener({
80+
element: options.onContextElement,
81+
callback: async(e) => {
82+
83+
const li = findUpClassName(e.target, 'chatlist-chat');
84+
if(!li) {
85+
return;
86+
}
87+
88+
if(this.element.parentElement !== appendTo) {
89+
appendTo.append(this.element);
90+
}
91+
92+
cancelEvent(e);
93+
94+
const peerId = this.targetPeerId = li.dataset.peerId.toPeerId();
95+
96+
await filterAsync(this.buttons, async(button) => {
97+
const good = await button.verify(peerId);
98+
button.element.classList.toggle('hide', !good);
99+
return good;
100+
});
101+
102+
positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, this.element, 'right');
103+
contextMenuController.openBtnMenu(this.element);
104+
},
105+
listenerSetter
106+
});
107+
108+
let appendTo: HTMLElement = document.body;
109+
addFullScreenListener(document.body, () => {
110+
const isFull = isFullScreen();
111+
appendTo = document.body;
112+
113+
if(!isFull) {
114+
contextMenuController.close();
115+
}
116+
}, listenerSetter);
117+
}
118+
119+
}
120+
121+
export interface ISelectPeers {
122+
appendTo: AppSelectPeers['appendTo'],
123+
onChange?: AppSelectPeers['onChange'],
124+
peerType?: AppSelectPeers['peerType'],
125+
peerId?: AppSelectPeers['peerId'],
126+
onFirstRender?: () => void,
127+
renderResultsFunc?: AppSelectPeers['renderResultsFunc'],
128+
chatRightsActions?: AppSelectPeers['chatRightsActions'],
129+
multiSelect?: AppSelectPeers['multiSelect'],
130+
rippleEnabled?: AppSelectPeers['rippleEnabled'],
131+
avatarSize?: AppSelectPeers['avatarSize'],
132+
placeholder?: AppSelectPeers['placeholder'],
133+
selfPresence?: AppSelectPeers['selfPresence'],
134+
exceptSelf?: AppSelectPeers['exceptSelf'],
135+
filterPeerTypeBy?: AppSelectPeers['filterPeerTypeBy'],
136+
sectionNameLangPackKey?: AppSelectPeers['sectionNameLangPackKey'],
137+
managers: AppSelectPeers['managers'],
138+
design?: AppSelectPeers['design'],
139+
listenerSetter?: ListenerSetter,
140+
toggleableMultiSelect?: boolean,
141+
}
142+
45143

46144
export default class AppSelectPeers {
145+
private contextMenu: AppSelectPeersContextMenu;
146+
private listenerSetter: ListenerSetter;
47147
public container = document.createElement('div');
48148
public list = appDialogsManager.createChatList(/* {
49149
handheldsSize: 66,
@@ -52,8 +152,7 @@ export default class AppSelectPeers {
52152
private chatsContainer = document.createElement('div');
53153
public scrollable: Scrollable;
54154
private selectedScrollable: Scrollable;
55-
56-
private selectedContainer: HTMLElement;
155+
public selectedContainer: HTMLElement;
57156
public input: HTMLInputElement;
58157

59158
// public selected: {[peerId: PeerId]: HTMLElement} = {};
@@ -68,7 +167,7 @@ export default class AppSelectPeers {
68167
private query = '';
69168
private cachedContacts: PeerId[];
70169

71-
private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true}> = {};
170+
private loadedWhat: Partial<{ [k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true }> = {};
72171

73172
private renderedPeerIds: Set<PeerId> = new Set();
74173

@@ -83,7 +182,7 @@ export default class AppSelectPeers {
83182
private exceptSelf = false;
84183
private filterPeerTypeBy: IsPeerType[];
85184

86-
private tempIds: {[k in keyof AppSelectPeers['loadedWhat']]: number} = {};
185+
private tempIds: { [k in keyof AppSelectPeers['loadedWhat']]: number } = {};
87186
private peerId: PeerId;
88187

89188
private placeholder: LangPackKey;
@@ -98,25 +197,11 @@ export default class AppSelectPeers {
98197

99198
private design: 'round' | 'square' = 'round';
100199

101-
constructor(options: {
102-
appendTo: AppSelectPeers['appendTo'],
103-
onChange?: AppSelectPeers['onChange'],
104-
peerType?: AppSelectPeers['peerType'],
105-
peerId?: AppSelectPeers['peerId'],
106-
onFirstRender?: () => void,
107-
renderResultsFunc?: AppSelectPeers['renderResultsFunc'],
108-
chatRightsActions?: AppSelectPeers['chatRightsActions'],
109-
multiSelect?: AppSelectPeers['multiSelect'],
110-
rippleEnabled?: AppSelectPeers['rippleEnabled'],
111-
avatarSize?: AppSelectPeers['avatarSize'],
112-
placeholder?: AppSelectPeers['placeholder'],
113-
selfPresence?: AppSelectPeers['selfPresence'],
114-
exceptSelf?: AppSelectPeers['exceptSelf'],
115-
filterPeerTypeBy?: AppSelectPeers['filterPeerTypeBy'],
116-
sectionNameLangPackKey?: AppSelectPeers['sectionNameLangPackKey'],
117-
managers: AppSelectPeers['managers'],
118-
design?: AppSelectPeers['design']
119-
}) {
200+
public toggleableMultiSelect: boolean = false;
201+
public toggleableMultiSelectState: boolean = false;
202+
203+
204+
constructor(options: ISelectPeers) {
120205
safeAssign(this, options);
121206

122207
this.container.classList.add('selector', 'selector-' + this.design);
@@ -195,7 +280,6 @@ export default class AppSelectPeers {
195280
simulateClickEvent(li);
196281
}
197282
});
198-
199283
section.content.append(topContainer);
200284
this.container.append(section.container/* , delimiter */);
201285
}
@@ -221,7 +305,7 @@ export default class AppSelectPeers {
221305
let key: PeerId | string = target.dataset.peerId;
222306
key = key.isPeerId() ? key.toPeerId() : key;
223307

224-
if(!this.multiSelect) {
308+
if(!this.multiSelect || (this.multiSelect && this.toggleableMultiSelect && !this.toggleableMultiSelectState)) {
225309
this.add(key);
226310
return;
227311
}
@@ -260,6 +344,10 @@ export default class AppSelectPeers {
260344
}, 0);
261345
}
262346

347+
private toggleMultiSelectState = () => {
348+
this.toggleableMultiSelectState = true;
349+
this.container.querySelectorAll('.checkbox-field').forEach(node => node.classList.remove('hide'));
350+
}
263351
private onInput = () => {
264352
const value = this.input.value;
265353
if(this.query !== value) {
@@ -553,7 +641,7 @@ export default class AppSelectPeers {
553641
}
554642

555643
private getMoreSomething(peerType: SelectSearchPeerType) {
556-
const map: {[type in SelectSearchPeerType]: () => Promise<any>} = {
644+
const map: { [type in SelectSearchPeerType]: () => Promise<any> } = {
557645
dialogs: this.getMoreDialogs,
558646
contacts: this.getMoreContacts,
559647
channelParticipants: this.getMoreChannelParticipants
@@ -565,7 +653,22 @@ export default class AppSelectPeers {
565653

566654
private async renderResults(peerIds: PeerId[]) {
567655
// console.log('will renderResults:', peerIds);
568-
656+
if(this.toggleableMultiSelect) {
657+
const {listenerSetter} = this;
658+
this.contextMenu = new AppSelectPeersContextMenu({
659+
onContextElement: this.list,
660+
listenerSetter,
661+
toggleMultiSelectState: () => {
662+
this.toggleMultiSelectState();
663+
},
664+
selectContact: (peerId) => {
665+
window.requestAnimationFrame(() => {
666+
const li = this.chatsContainer.querySelector('[data-peer-id="' + peerId + '"]') as HTMLElement;
667+
simulateClickEvent(li);
668+
})
669+
}
670+
});
671+
}
569672
// оставим только неконтакты с диалогов
570673
if(!this.peerType.includes('dialogs') && this.loadedWhat.contacts) {
571674
peerIds = await filterAsync(peerIds, (peerId) => {
@@ -582,8 +685,12 @@ export default class AppSelectPeers {
582685
});
583686

584687
if(this.multiSelect) {
688+
585689
const selected = this.selected.has(peerId);
586-
const checkboxField = new CheckboxField();
690+
const checkboxField = new CheckboxField({round: true});
691+
if(!this.toggleableMultiSelectState) {
692+
checkboxField.hide();
693+
}
587694

588695
if(selected) {
589696
// dom.listEl.classList.add('active');
@@ -610,7 +717,7 @@ export default class AppSelectPeers {
610717
// console.trace('add');
611718
this.selected.add(key);
612719

613-
if(!this.multiSelect) {
720+
if(!this.multiSelect || (this.multiSelect && this.toggleableMultiSelect && !this.toggleableMultiSelectState)) {
614721
this.onChange(this.selected.size);
615722
return;
616723
}
@@ -641,7 +748,7 @@ export default class AppSelectPeers {
641748
}
642749

643750
if(title) {
644-
if(typeof(title) === 'string') {
751+
if(typeof (title) === 'string') {
645752
div.innerHTML = title;
646753
} else {
647754
replaceContent(div, title);
@@ -650,8 +757,12 @@ export default class AppSelectPeers {
650757
}
651758

652759
div.insertAdjacentElement('afterbegin', avatarEl);
760+
try {
761+
this.selectedContainer.insertBefore(div, this.input);
762+
} catch(e) {
763+
this.selectedContainer.append(div);
764+
}
653765

654-
this.selectedContainer.insertBefore(div, this.input);
655766
// this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
656767
this.onChange?.(this.selected.size);
657768

src/components/checkboxField.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import type ListenerSetter from '../helpers/listenerSetter';
88
import ripple from './ripple';
9-
import {LangPackKey, _i18n} from '../lib/langPack';
9+
import {_i18n, LangPackKey} from '../lib/langPack';
1010
import getDeepProperty from '../helpers/object/getDeepProperty';
1111
import rootScope from '../lib/rootScope';
1212
import apiManagerProxy from '../lib/mtproto/mtprotoworker';
@@ -46,6 +46,7 @@ export default class CheckboxField {
4646
label.classList.add('checkbox-field-round');
4747
}
4848

49+
4950
if(options.disabled) {
5051
this.toggleDisability(true);
5152
}
@@ -179,6 +180,14 @@ export default class CheckboxField {
179180
this.input.checked = checked;
180181
}
181182

183+
public show() {
184+
this.label.classList.remove('hide');
185+
}
186+
187+
public hide() {
188+
this.label.classList.add('hide');
189+
}
190+
182191
public isDisabled() {
183192
return this.label.classList.contains('checkbox-disabled');
184193
}

0 commit comments

Comments
 (0)