Browse Source

Merge pull request #2 from morethanwords/master

Merge main
master
wang chenyu 4 years ago committed by GitHub
parent
commit
ef77df84ca
  1. 75
      src/components/chat/bubbles.ts
  2. 4
      src/components/chat/contextMenu.ts
  3. 2
      src/components/chat/selection.ts
  4. 2
      src/components/divAndCaption.ts
  5. 11
      src/components/inputField.ts
  6. 1
      src/components/peerTitle.ts
  7. 2
      src/components/row.ts
  8. 1
      src/components/sidebarLeft/tabs/addMembers.ts
  9. 31
      src/components/sidebarRight/tabs/chatType.ts
  10. 199
      src/components/sidebarRight/tabs/editChannel.ts
  11. 164
      src/components/sidebarRight/tabs/editChat.ts
  12. 15
      src/components/sidebarRight/tabs/sharedMedia.ts
  13. 2
      src/config/app.ts
  14. 5
      src/helpers/dom.ts
  15. 179
      src/helpers/slicedArray.ts
  16. 18
      src/lang.ts
  17. 1
      src/lib/appManagers/appDialogsManager.ts
  18. 8
      src/lib/appManagers/appImManager.ts
  19. 182
      src/lib/appManagers/appMessagesManager.ts
  20. 2
      src/lib/appManagers/appPhotosManager.ts
  21. 14
      src/lib/appManagers/appUsersManager.ts
  22. 7
      src/lib/storages/dialogs.ts
  23. 2
      src/scss/partials/_button.scss
  24. 20
      src/scss/partials/_chatBubble.scss
  25. 1
      src/scss/partials/_selector.scss
  26. 4
      src/scss/partials/_sidebar.scss
  27. 2
      src/scss/style.scss
  28. 121
      src/tests/slicedArray.test.ts

75
src/components/chat/bubbles.ts

@ -15,7 +15,7 @@ import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; @@ -15,7 +15,7 @@ import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type sessionStorage from '../../lib/sessionStorage';
import type Chat from "./chat";
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import { cancelEvent, whichChild, attachClickEvent, positionElementByIndex, reflowScrollableElement, replaceContent, htmlToDocumentFragment } from "../../helpers/dom";
import { cancelEvent, whichChild, attachClickEvent, positionElementByIndex, reflowScrollableElement, replaceContent, htmlToDocumentFragment, setInnerHTML } from "../../helpers/dom";
import { getObjectKeysAndSort } from "../../helpers/object";
import { isTouchSupported } from "../../helpers/touchSupport";
import { logger } from "../../lib/logger";
@ -130,6 +130,8 @@ export default class ChatBubbles { @@ -130,6 +130,8 @@ export default class ChatBubbles {
public isFirstLoad = true;
private needReflowScroll: boolean;
private fetchNewPromise: Promise<void>;
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) {
//this.chat.log.error('Bubbles construction');
@ -1399,6 +1401,7 @@ export default class ChatBubbles { @@ -1399,6 +1401,7 @@ export default class ChatBubbles {
this.messagesQueuePromise = null;
this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined;
this.fetchNewPromise = undefined;
if(this.stickyIntersector) {
this.stickyIntersector.disconnect();
@ -1618,8 +1621,7 @@ export default class ChatBubbles { @@ -1618,8 +1621,7 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, !isJump);
const isFetchIntervalNeeded = () => peerId < 0 && !this.appChatsManager.isInChat(peerId);
const needFetchInterval = isFetchIntervalNeeded();
const needFetchInterval = this.appMessagesManager.isFetchIntervalNeeded(peerId);
const needFetchNew = savedPosition || needFetchInterval;
if(!needFetchNew) {
// warning
@ -1627,20 +1629,44 @@ export default class ChatBubbles { @@ -1627,20 +1629,44 @@ export default class ChatBubbles {
this.scrollable.loadedAll.bottom = true;
}
} else {
const middleware = this.getMiddleware();
Promise.all([setPeerPromise, getHeavyAnimationPromise()]).then(() => {
if(!middleware()) {
return;
}
this.scrollable.checkForTriggers();
if(needFetchInterval) {
const middleware = this.getMiddleware();
const interval = window.setInterval(() => {
if(!middleware() || !isFetchIntervalNeeded()) {
clearInterval(interval);
return;
}
this.scrollable.loadedAll.bottom = false;
this.loadMoreHistory(false);
}, 30e3);
const f = () => {
this.fetchNewPromise = new Promise<void>((resolve) => {
if(!middleware() || !this.appMessagesManager.isFetchIntervalNeeded(peerId)) {
resolve();
return;
}
this.appMessagesManager.getNewHistory(peerId, this.chat.threadId).then((historyStorage) => {
if(!middleware()) {
resolve();
return;
}
const slice = historyStorage.history.slice;
const isBottomEnd = slice.isEnd(SliceEnd.Bottom);
if(this.scrollable.loadedAll.bottom !== isBottomEnd) {
this.scrollable.loadedAll.bottom = isBottomEnd;
this.onScroll();
}
setTimeout(f, 30e3);
resolve();
});
}).finally(() => {
this.fetchNewPromise = undefined;
});
};
f();
}
});
}
@ -1975,7 +2001,7 @@ export default class ChatBubbles { @@ -1975,7 +2001,7 @@ export default class ChatBubbles {
bubble.classList.add('is-message-empty', 'emoji-big');
canHaveTail = false;
} else {
messageDiv.innerHTML = richText;
setInnerHTML(messageDiv, richText);
}
/* if(strLength === emojiStrLength) {
@ -1983,7 +2009,7 @@ export default class ChatBubbles { @@ -1983,7 +2009,7 @@ export default class ChatBubbles {
messageDiv.classList.add('message-empty');
} */
} else {
messageDiv.innerHTML = richText;
setInnerHTML(messageDiv, richText);
}
const timeSpan = MessageRender.setTime(this.chat, message, bubble, bubbleContainer, messageDiv);
@ -2219,21 +2245,21 @@ export default class ChatBubbles { @@ -2219,21 +2245,21 @@ export default class ChatBubbles {
nameEl.classList.add('name');
nameEl.setAttribute('target', '_blank');
nameEl.href = webpage.url || '#';
nameEl.innerHTML = RichTextProcessor.wrapEmojiText(webpage.site_name);
setInnerHTML(nameEl, RichTextProcessor.wrapEmojiText(webpage.site_name));
quoteTextDiv.append(nameEl);
}
if(webpage.rTitle) {
let titleDiv = document.createElement('div');
titleDiv.classList.add('title');
titleDiv.innerHTML = webpage.rTitle;
setInnerHTML(titleDiv, webpage.rTitle);
quoteTextDiv.append(titleDiv);
}
if(webpage.rDescription) {
let textDiv = document.createElement('div');
textDiv.classList.add('text');
textDiv.innerHTML = webpage.rDescription;
setInnerHTML(textDiv, webpage.rDescription);
quoteTextDiv.append(textDiv);
}
@ -2626,9 +2652,15 @@ export default class ChatBubbles { @@ -2626,9 +2652,15 @@ export default class ChatBubbles {
} */
const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId);
if(history.includes(historyStorage.maxId)) {
const firstSlice = historyStorage.history.first;
const lastSlice = historyStorage.history.last;
if(firstSlice.isEnd(SliceEnd.Bottom) && history.includes(firstSlice[0])) {
this.scrollable.loadedAll.bottom = true;
}
if(lastSlice.isEnd(SliceEnd.Top) && history.includes(lastSlice[lastSlice.length - 1])) {
this.scrollable.loadedAll.top = true;
}
//console.time('appImManager render history');
@ -2818,8 +2850,9 @@ export default class ChatBubbles { @@ -2818,8 +2850,9 @@ export default class ChatBubbles {
additionMsgIds = [additionMsgId];
} else {
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
if(historyStorage.history.length < loadCount && !historyStorage.history.slice.isEnd(SliceEnd.Both)) {
additionMsgIds = historyStorage.history.slice.slice();
const slice = historyStorage.history.slice;
if(slice.length < loadCount && !slice.isEnd(SliceEnd.Both)) {
additionMsgIds = slice.slice();
// * filter last album, because we don't know is it the last item
for(let i = additionMsgIds.length - 1; i >= 0; --i) {

4
src/components/chat/contextMenu.ts

@ -158,7 +158,7 @@ export default class ChatContextMenu { @@ -158,7 +158,7 @@ export default class ChatContextMenu {
private init() {
this.buttons = [{
icon: 'send2',
text: 'Chat.Context.Scheduled.SendNow',
text: 'MessageScheduleSend',
onClick: this.onSendScheduledClick,
verify: () => this.chat.type === 'scheduled' && !this.message.pFlags.is_outgoing
}, {
@ -170,7 +170,7 @@ export default class ChatContextMenu { @@ -170,7 +170,7 @@ export default class ChatContextMenu {
withSelection: true
}, {
icon: 'schedule',
text: 'Chat.Context.Scheduled.Reschedule',
text: 'MessageScheduleEditTime',
onClick: () => {
this.chat.input.scheduleSending(() => {
this.appMessagesManager.editMessage(this.message, this.message.message, {

2
src/components/chat/selection.ts

@ -359,7 +359,7 @@ export default class ChatSelection { @@ -359,7 +359,7 @@ export default class ChatSelection {
if(this.chat.type === 'scheduled') {
this.selectionSendNowBtn = Button('btn-primary btn-transparent btn-short text-bold selection-container-send', {icon: 'send2'});
this.selectionSendNowBtn.append(i18n('Chat.Context.Scheduled.SendNow'));
this.selectionSendNowBtn.append(i18n('MessageScheduleSend'));
this.listenerSetter.add(this.selectionSendNowBtn, 'click', () => {
new PopupSendNow(this.bubbles.peerId, [...this.selectedMids], () => {
this.cancelSelection();

2
src/components/divAndCaption.ts

@ -23,9 +23,11 @@ export default class DivAndCaption<T> { @@ -23,9 +23,11 @@ export default class DivAndCaption<T> {
this.title = document.createElement('div');
this.title.classList.add(className + '-title');
this.title.setAttribute('dir', 'auto');
this.subtitle = document.createElement('div');
this.subtitle.classList.add(className + '-subtitle');
this.subtitle.setAttribute('dir', 'auto');
this.content.append(this.title, this.subtitle);
this.container.append(this.border, this.content);

11
src/components/inputField.ts

@ -42,7 +42,8 @@ let init = () => { @@ -42,7 +42,8 @@ let init = () => {
init = null;
};
const checkAndSetRTL = (input: HTMLElement) => {
// ! it doesn't respect symbols other than strongs
/* const checkAndSetRTL = (input: HTMLElement) => {
//const isEmpty = isInputEmpty(input);
//console.log('input', isEmpty);
@ -56,7 +57,7 @@ const checkAndSetRTL = (input: HTMLElement) => { @@ -56,7 +57,7 @@ const checkAndSetRTL = (input: HTMLElement) => {
//console.log('RTL', direction, char);
input.style.direction = direction;
};
}; */
export enum InputState {
Neutral = 0,
@ -112,7 +113,7 @@ class InputField { @@ -112,7 +113,7 @@ class InputField {
input = this.container.firstElementChild as HTMLElement;
const observer = new MutationObserver(() => {
checkAndSetRTL(input);
//checkAndSetRTL(input);
if(processInput) {
processInput();
@ -148,9 +149,11 @@ class InputField { @@ -148,9 +149,11 @@ class InputField {
`;
input = this.container.firstElementChild as HTMLElement;
input.addEventListener('input', () => checkAndSetRTL(input));
//input.addEventListener('input', () => checkAndSetRTL(input));
}
input.setAttribute('dir', 'auto');
if(placeholder) {
_i18n(input, placeholder, undefined, 'placeholder');

1
src/components/peerTitle.ts

@ -43,6 +43,7 @@ export default class PeerTitle { @@ -43,6 +43,7 @@ export default class PeerTitle {
constructor(options: PeerTitleOptions) {
this.element = document.createElement('span');
this.element.classList.add('peer-title');
this.element.setAttribute('dir', 'auto');
this.update(options);
weakMap.set(this.element, this);

2
src/components/row.ts

@ -40,6 +40,7 @@ export default class Row { @@ -40,6 +40,7 @@ export default class Row {
this.subtitle = document.createElement('div');
this.subtitle.classList.add('row-subtitle');
this.subtitle.setAttribute('dir', 'auto');
if(options.subtitle) {
this.subtitle.innerHTML = options.subtitle;
} else if(options.subtitleLangKey) {
@ -89,6 +90,7 @@ export default class Row { @@ -89,6 +90,7 @@ export default class Row {
this.title = document.createElement('div');
this.title.classList.add('row-title');
this.title.setAttribute('dir', 'auto');
if(options.title) {
this.title.innerHTML = options.title;
} else {

1
src/components/sidebarLeft/tabs/addMembers.ts

@ -20,6 +20,7 @@ export default class AppAddMembersTab extends SliderSuperTab { @@ -20,6 +20,7 @@ export default class AppAddMembersTab extends SliderSuperTab {
protected init() {
this.nextBtn = ButtonCorner({icon: 'arrow_next'});
this.content.append(this.nextBtn);
this.scrollable.container.remove();
this.nextBtn.addEventListener('click', () => {
const peerIds = this.selector.getSelected();

31
src/components/sidebarRight/tabs/groupType.ts → src/components/sidebarRight/tabs/chatType.ts

@ -22,34 +22,37 @@ import I18n from "../../../lib/langPack"; @@ -22,34 +22,37 @@ import I18n from "../../../lib/langPack";
import PopupPeer from "../../popups/peer";
import ButtonCorner from "../../buttonCorner";
export default class AppGroupTypeTab extends SliderSuperTabEventable {
public peerId: number;
export default class AppChatTypeTab extends SliderSuperTabEventable {
public chatId: number;
public chatFull: ChatFull;
protected init() {
this.container.classList.add('edit-peer-container', 'group-type-container');
this.setTitle('GroupType');
const isBroadcast = appChatsManager.isBroadcast(this.chatId);
this.setTitle(isBroadcast ? 'ChannelType' : 'GroupType');
const section = new SettingSection({
name: 'GroupType'
name: isBroadcast ? 'ChannelType' : 'GroupType'
});
const random = randomLong();
const privateRow = new Row({
radioField: new RadioField({
langKey: 'MegaPrivate',
langKey: isBroadcast ? 'ChannelPrivate' : 'MegaPrivate',
name: random,
value: 'private'
}),
subtitleLangKey: 'MegaPrivateInfo'
subtitleLangKey: isBroadcast ? 'ChannelPrivateInfo' : 'MegaPrivateInfo'
});
const publicRow = new Row({
radioField: new RadioField({
langKey: 'MegaPublic',
langKey: isBroadcast ? 'ChannelPublic' : 'MegaPublic',
name: random,
value: 'public'
}),
subtitleLangKey: 'MegaPublicInfo'
subtitleLangKey: isBroadcast ? 'ChannelPublicInfo' : 'MegaPublicInfo'
});
const form = RadioFormFromRows([privateRow, publicRow], (value) => {
const a = [privateSection, publicSection];
@ -61,7 +64,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -61,7 +64,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
onChange();
});
const chat: Chat = appChatsManager.getChat(-this.peerId);
const chat: Chat = appChatsManager.getChat(this.chatId);
section.content.append(form);
@ -70,7 +73,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -70,7 +73,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
//let revoked = false;
const linkRow = new Row({
title: (this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link,
subtitleLangKey: 'MegaPrivateLinkHelp',
subtitleLangKey: isBroadcast ? 'ChannelPrivateLinkHelp' : 'MegaPrivateLinkHelp',
clickable: () => {
copyTextToClipboard((this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link);
toast(I18n.format('LinkCopied', true));
@ -86,7 +89,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -86,7 +89,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
callback: () => {
const toggle = toggleDisability([btnRevoke], true);
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
appProfileManager.getChatInviteLink(this.chatId, true).then(link => {
toggle();
linkRow.title.innerHTML = link;
//revoked = true;
@ -102,7 +105,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -102,7 +105,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
privateSection.content.append(linkRow.container, btnRevoke);
const publicSection = new SettingSection({
caption: 'Channel.UsernameAboutGroup',
caption: isBroadcast ? 'Channel.UsernameAboutChannel' : 'Channel.UsernameAboutGroup',
noDelimiter: true
});
@ -126,7 +129,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -126,7 +129,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
invalidText: 'Link.Invalid',
takenText: 'Link.Taken',
onChange: onChange,
peerId: this.peerId,
peerId: -this.chatId,
head: placeholder
});
@ -141,7 +144,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable { @@ -141,7 +144,7 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
attachClickEvent(applyBtn, () => {
/* const unsetLoader = */setButtonLoader(applyBtn);
const username = publicRow.radioField.checked ? linkInputField.getValue() : '';
appChatsManager.migrateChat(-this.peerId).then(channelId => {
appChatsManager.migrateChat(this.chatId).then(channelId => {
return appChatsManager.updateUsername(channelId, username);
}).then(() => {
//unsetLoader();

199
src/components/sidebarRight/tabs/editChannel.ts

@ -1,199 +0,0 @@ @@ -1,199 +0,0 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { SliderSuperTab } from "../../slider"
import InputField from "../../inputField";
import EditPeer from "../../editPeer";
import { SettingSection } from "../../sidebarLeft";
import Row from "../../row";
import CheckboxField from "../../checkboxField";
import Button from "../../button";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import { attachClickEvent, toggleDisability } from "../../../helpers/dom";
import PopupPeer from "../../popups/peer";
import { addCancelButton } from "../../popups";
import { i18n } from "../../../lib/langPack";
import { numberThousandSplitter } from "../../../helpers/number";
export default class AppEditChannelTab extends SliderSuperTab {
private nameInputField: InputField;
private descriptionInputField: InputField;
private editPeer: EditPeer;
public peerId: number;
protected async init() {
this.container.classList.add('edit-peer-container', 'edit-channel-container');
this.setTitle('Edit');
const chatFull = await appProfileManager.getChannelFull(-this.peerId, true);
{
const section = new SettingSection({noDelimiter: true});
if(appChatsManager.hasRights(-this.peerId, 'change_info')) {
const inputFields: InputField[] = [];
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
this.nameInputField = new InputField({
label: 'Channel.ChannelNameHolder',
name: 'channel-name',
maxLength: 255
});
this.descriptionInputField = new InputField({
label: 'DescriptionPlaceholder',
name: 'channel-description',
maxLength: 255
});
this.nameInputField.setOriginalValue(appChatsManager.getChat(-this.peerId).title);
this.descriptionInputField.setOriginalValue(chatFull.about);
inputWrapper.append(this.nameInputField.container, this.descriptionInputField.container);
inputFields.push(this.nameInputField, this.descriptionInputField);
this.editPeer = new EditPeer({
peerId: this.peerId,
inputFields,
listenerSetter: this.listenerSetter
});
this.content.append(this.editPeer.nextBtn);
section.content.append(this.editPeer.avatarEdit.container, inputWrapper);
attachClickEvent(this.editPeer.nextBtn, () => {
this.editPeer.nextBtn.disabled = true;
let promises: Promise<any>[] = [];
const id = -this.peerId;
if(this.nameInputField.isValid()) {
promises.push(appChatsManager.editTitle(id, this.nameInputField.value));
}
if(this.descriptionInputField.isValid()) {
promises.push(appChatsManager.editAbout(id, this.descriptionInputField.value));
}
if(this.editPeer.uploadAvatar) {
promises.push(this.editPeer.uploadAvatar().then(inputFile => {
return appChatsManager.editPhoto(id, inputFile);
}));
}
Promise.race(promises).finally(() => {
this.editPeer.nextBtn.removeAttribute('disabled');
this.close();
});
}, {listenerSetter: this.listenerSetter});
}
/* if(appChatsManager.hasRights(-this.peerId, 'change_type')) {
const channelTypeRow = new Row({
titleLangKey: 'ChannelType',
subtitleLangKey: 'TypePrivate',
clickable: true,
icon: 'lock'
});
section.content.append(channelTypeRow.container);
}
if(appChatsManager.hasRights(-this.peerId, 'change_info')) {
const discussionRow = new Row({
titleLangKey: 'PeerInfo.Discussion',
subtitleLangKey: 'PeerInfo.Discussion.Add',
clickable: true,
icon: 'message'
});
section.content.append(discussionRow.container);
}
const administratorsRow = new Row({
titleLangKey: 'PeerInfo.Administrators',
subtitle: '' + chatFull.admins_count,
icon: 'admin',
clickable: true
});
section.content.append(administratorsRow.container);
if(appChatsManager.hasRights(-this.peerId, 'change_info')) {
const signMessagesCheckboxField = new CheckboxField({
text: 'PeerInfo.SignMessages',
checked: false
});
section.content.append(signMessagesCheckboxField.label);
} */
this.scrollable.append(section.container);
}
/* {
const section = new SettingSection({
});
const subscribersRow = new Row({
titleLangKey: 'PeerInfo.Subscribers',
icon: 'newgroup',
clickable: true
});
subscribersRow.subtitle.append(i18n('Subscribers', [numberThousandSplitter(335356)]));
section.content.append(subscribersRow.container);
this.scrollable.append(section.container);
} */
if(appChatsManager.hasRights(-this.peerId, 'delete_chat')) {
const section = new SettingSection({
});
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'PeerInfo.DeleteChannel'});
attachClickEvent(btnDelete, () => {
new PopupPeer('popup-delete-channel', {
peerId: this.peerId,
titleLangKey: 'ChannelDeleteMenu',
descriptionLangKey: 'AreYouSureDeleteAndExitChannel',
buttons: addCancelButton([{
langKey: 'ChannelDeleteMenu',
callback: () => {
const toggle = toggleDisability([btnDelete], true);
},
isDanger: true
}, {
langKey: 'DeleteChannelForAll',
callback: () => {
const toggle = toggleDisability([btnDelete], true);
appChatsManager.deleteChannel(-this.peerId).then(() => {
this.close();
}, () => {
toggle();
});
},
isDanger: true
}])
}).show();
}, {listenerSetter: this.listenerSetter});
section.content.append(btnDelete);
this.scrollable.append(section.container);
}
}
}

164
src/components/sidebarRight/tabs/editGroup.ts → src/components/sidebarRight/tabs/editChat.ts

@ -13,15 +13,17 @@ import Button from "../../button"; @@ -13,15 +13,17 @@ import Button from "../../button";
import appChatsManager, { ChatRights } from "../../../lib/appManagers/appChatsManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import { attachClickEvent, toggleDisability } from "../../../helpers/dom";
import { ChatFull } from "../../../layer";
import AppGroupTypeTab from "./groupType";
import { Chat } from "../../../layer";
import AppChatTypeTab from "./chatType";
import rootScope from "../../../lib/rootScope";
import AppGroupPermissionsTab from "./groupPermissions";
import { i18n } from "../../../lib/langPack";
import { i18n, LangPackKey } from "../../../lib/langPack";
import PopupDeleteDialog from "../../popups/deleteDialog";
import { addCancelButton } from "../../popups";
import PopupPeer from "../../popups/peer";
export default class AppEditGroupTab extends SliderSuperTab {
private groupNameInputField: InputField;
export default class AppEditChatTab extends SliderSuperTab {
private chatNameInputField: InputField;
private descriptionInputField: InputField;
private editPeer: EditPeer;
public chatId: number;
@ -33,9 +35,13 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -33,9 +35,13 @@ export default class AppEditGroupTab extends SliderSuperTab {
this.container.classList.add('edit-peer-container', 'edit-group-container');
this.setTitle('Edit');
const chatFull = await appProfileManager.getChatFull(this.chatId, true);
const chat: Chat.chat | Chat.channel = appChatsManager.getChat(this.chatId);
const isBroadcast = appChatsManager.isBroadcast(this.chatId);
const isChannel = appChatsManager.isChannel(this.chatId);
{
const section = new SettingSection({noDelimiter: true});
const inputFields: InputField[] = [];
@ -43,26 +49,23 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -43,26 +49,23 @@ export default class AppEditGroupTab extends SliderSuperTab {
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
this.groupNameInputField = new InputField({
label: 'CreateGroup.NameHolder',
name: 'group-name',
this.chatNameInputField = new InputField({
label: isBroadcast ? 'Channel.ChannelNameHolder' : 'CreateGroup.NameHolder',
name: 'chat-name',
maxLength: 255
});
this.descriptionInputField = new InputField({
label: 'DescriptionPlaceholder',
name: 'group-description',
name: 'chat-description',
maxLength: 255
});
const chat = appChatsManager.getChat(this.chatId);
this.groupNameInputField.setOriginalValue(chat.title);
this.chatNameInputField.setOriginalValue(chat.title);
this.descriptionInputField.setOriginalValue(chatFull.about);
inputWrapper.append(this.groupNameInputField.container, this.descriptionInputField.container);
inputWrapper.append(this.chatNameInputField.container, this.descriptionInputField.container);
inputFields.push(this.groupNameInputField, this.descriptionInputField);
inputFields.push(this.chatNameInputField, this.descriptionInputField);
this.editPeer = new EditPeer({
peerId: -this.chatId,
@ -74,29 +77,37 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -74,29 +77,37 @@ export default class AppEditGroupTab extends SliderSuperTab {
section.content.append(this.editPeer.avatarEdit.container, inputWrapper);
if(appChatsManager.hasRights(this.chatId, 'change_type')) {
const groupTypeRow = new Row({
titleLangKey: 'GroupType',
const chatTypeRow = new Row({
titleLangKey: isBroadcast ? 'ChannelType' : 'GroupType',
clickable: () => {
const tab = new AppGroupTypeTab(this.slider);
tab.peerId = -this.chatId;
const tab = new AppChatTypeTab(this.slider);
tab.chatId = this.chatId;
tab.chatFull = chatFull;
tab.open();
this.listenerSetter.add(tab.eventListener, 'destroy', setGroupTypeSubtitle);
this.listenerSetter.add(tab.eventListener, 'destroy', setChatTypeSubtitle);
},
icon: 'lock'
});
const setGroupTypeSubtitle = () => {
groupTypeRow.subtitle.textContent = '';
groupTypeRow.subtitle.append(i18n(chat.username ? 'TypePublicGroup' : 'TypePrivateGroup'));
const setChatTypeSubtitle = () => {
chatTypeRow.subtitle.textContent = '';
let key: LangPackKey;
if(isBroadcast) {
key = (chat as Chat.channel).username ? 'TypePublic' : 'TypePrivate';
} else {
key = (chat as Chat.channel).username ? 'TypePublicGroup' : 'TypePrivateGroup';
}
chatTypeRow.subtitle.append(i18n(key));
};
setGroupTypeSubtitle();
section.content.append(groupTypeRow.container);
setChatTypeSubtitle();
section.content.append(chatTypeRow.container);
}
if(appChatsManager.hasRights(this.chatId, 'change_permissions')) {
if(appChatsManager.hasRights(this.chatId, 'change_permissions') && !isBroadcast) {
const flags = [
'send_messages',
'send_media',
@ -149,8 +160,8 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -149,8 +160,8 @@ export default class AppEditGroupTab extends SliderSuperTab {
let promises: Promise<any>[] = [];
const id = this.chatId;
if(this.groupNameInputField.isValid()) {
promises.push(appChatsManager.editTitle(id, this.groupNameInputField.value));
if(this.chatNameInputField.isValid()) {
promises.push(appChatsManager.editTitle(id, this.chatNameInputField.value));
}
if(this.descriptionInputField.isValid()) {
@ -168,6 +179,36 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -168,6 +179,36 @@ export default class AppEditGroupTab extends SliderSuperTab {
this.close();
});
}, {listenerSetter: this.listenerSetter});
/*
if(appChatsManager.hasRights(-this.peerId, 'change_info')) {
const discussionRow = new Row({
titleLangKey: 'PeerInfo.Discussion',
subtitleLangKey: 'PeerInfo.Discussion.Add',
clickable: true,
icon: 'message'
});
section.content.append(discussionRow.container);
}
const administratorsRow = new Row({
titleLangKey: 'PeerInfo.Administrators',
subtitle: '' + chatFull.admins_count,
icon: 'admin',
clickable: true
});
section.content.append(administratorsRow.container);
if(appChatsManager.hasRights(-this.peerId, 'change_info')) {
const signMessagesCheckboxField = new CheckboxField({
text: 'PeerInfo.SignMessages',
checked: false
});
section.content.append(signMessagesCheckboxField.label);
} */
}
/* {
@ -176,12 +217,13 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -176,12 +217,13 @@ export default class AppEditGroupTab extends SliderSuperTab {
});
const membersRow = new Row({
titleLangKey: 'GroupMembers',
subtitle: '2 500',
titleLangKey: isBroadcast ? 'PeerInfo.Subscribers' : 'GroupMembers',
icon: 'newgroup',
clickable: true
});
membersRow.subtitle.append(i18n('Subscribers', [numberThousandSplitter(335356)]));
section.content.append(membersRow.container);
if(appChatsManager.hasRights(this.chatId, 'change_permissions')) {
@ -203,17 +245,45 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -203,17 +245,45 @@ export default class AppEditGroupTab extends SliderSuperTab {
if(appChatsManager.hasRights(this.chatId, 'delete_chat')) {
const section = new SettingSection({});
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'DeleteMega'});
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: isBroadcast ? 'PeerInfo.DeleteChannel' : 'DeleteMega'});
attachClickEvent(btnDelete, () => {
new PopupDeleteDialog(-this.chatId, undefined, (promise) => {
const toggle = toggleDisability([btnDelete], true);
promise.then(() => {
this.close();
}, () => {
toggle();
if(isBroadcast) {
new PopupPeer('popup-delete-channel', {
peerId: -this.chatId,
titleLangKey: 'ChannelDeleteMenu',
descriptionLangKey: 'AreYouSureDeleteAndExitChannel',
buttons: addCancelButton([{
langKey: 'ChannelDeleteMenu',
callback: () => {
const toggle = toggleDisability([btnDelete], true);
},
isDanger: true
}, {
langKey: 'DeleteChannelForAll',
callback: () => {
const toggle = toggleDisability([btnDelete], true);
appChatsManager.deleteChannel(this.chatId).then(() => {
this.close();
}, () => {
toggle();
});
},
isDanger: true
}])
}).show();
} else {
new PopupDeleteDialog(-this.chatId, undefined, (promise) => {
const toggle = toggleDisability([btnDelete], true);
promise.then(() => {
this.close();
}, () => {
toggle();
});
});
});
}
}, {listenerSetter: this.listenerSetter});
section.content.append(btnDelete);
@ -221,13 +291,15 @@ export default class AppEditGroupTab extends SliderSuperTab { @@ -221,13 +291,15 @@ export default class AppEditGroupTab extends SliderSuperTab {
this.scrollable.append(section.container);
}
// ! this one will fire earlier than tab's closeAfterTimeout (destroy) event and listeners will be erased, so destroy won't fire
this.listenerSetter.add(rootScope, 'dialog_migrate', ({migrateFrom, migrateTo}) => {
if(-this.chatId === migrateFrom) {
this.chatId = -migrateTo;
this._init();
}
});
if(!isChannel) {
// ! this one will fire earlier than tab's closeAfterTimeout (destroy) event and listeners will be erased, so destroy won't fire
this.listenerSetter.add(rootScope, 'dialog_migrate', ({migrateFrom, migrateTo}) => {
if(-this.chatId === migrateFrom) {
this.chatId = -migrateTo;
this._init();
}
});
}
}
protected init() {

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

@ -19,9 +19,8 @@ import { attachClickEvent, replaceContent, cancelEvent } from "../../../helpers/ @@ -19,9 +19,8 @@ import { attachClickEvent, replaceContent, cancelEvent } from "../../../helpers/
import appSidebarRight from "..";
import { TransitionSlider } from "../../transition";
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
import AppEditGroupTab from "./editGroup";
import AppEditChatTab from "./editChat";
import PeerTitle from "../../peerTitle";
import AppEditChannelTab from "./editChannel";
import AppEditContactTab from "./editContact";
import appChatsManager, { Channel } from "../../../lib/appManagers/appChatsManager";
import { Chat, Message, MessageAction, ChatFull, Photo } from "../../../layer";
@ -854,17 +853,15 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -854,17 +853,15 @@ export default class AppSharedMediaTab extends SliderSuperTab {
});
attachClickEvent(this.editBtn, (e) => {
let tab: AppEditGroupTab | AppEditChannelTab | AppEditContactTab;
if(appPeersManager.isAnyGroup(this.peerId)) {
tab = new AppEditGroupTab(appSidebarRight);
} else if(this.peerId > 0) {
tab = new AppEditContactTab(appSidebarRight);
let tab: AppEditChatTab | AppEditContactTab;
if(this.peerId < 0) {
tab = new AppEditChatTab(appSidebarRight);
} else {
tab = new AppEditChannelTab(appSidebarRight);
tab = new AppEditContactTab(appSidebarRight);
}
if(tab) {
if(tab instanceof AppEditGroupTab) {
if(tab instanceof AppEditChatTab) {
tab.chatId = -this.peerId;
} else {
tab.peerId = this.peerId;

2
src/config/app.ts

@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
const App = {
id: 1025907,
hash: '452b0359b988148995f22ff0f4229750',
version: '0.5.0',
version: '0.5.1',
langPackVersion: '0.1.6',
langPack: 'macos',
langPackCode: 'en',

5
src/helpers/dom.ts

@ -694,3 +694,8 @@ export function replaceContent(elem: HTMLElement, node: string | Node) { @@ -694,3 +694,8 @@ export function replaceContent(elem: HTMLElement, node: string | Node) {
elem.append(node);
}
}
export function setInnerHTML(elem: HTMLElement, html: string) {
elem.setAttribute('dir', 'auto');
elem.innerHTML = html;
}

179
src/helpers/slicedArray.ts

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { MOUNT_CLASS_TO } from "../config/debug";
/**
* Descend sorted storage
*/
@ -14,15 +16,19 @@ export enum SliceEnd { @@ -14,15 +16,19 @@ export enum SliceEnd {
None = 0,
Top = 1,
Bottom = 2,
Both = 4
Both = SliceEnd.Top | SliceEnd.Bottom
};
export interface Slice extends Array<ItemType> {
slicedArray: SlicedArray;
//slicedArray: SlicedArray;
end: SliceEnd;
isEnd: (side: SliceEnd) => boolean;
setEnd: (side: SliceEnd) => void;
unsetEnd: (side: SliceEnd) => void;
slice: (from?: number, to?: number) => Slice;
splice: (start: number, deleteCount: number, ...items: ItemType[]) => Slice;
}
export interface SliceConstructor {
@ -35,51 +41,84 @@ export default class SlicedArray { @@ -35,51 +41,84 @@ export default class SlicedArray {
private sliceConstructor: SliceConstructor;
constructor() {
const self = this;
this.sliceConstructor = class Slice extends Array<ItemType> implements Slice {
slicedArray: SlicedArray;
end: SliceEnd = SliceEnd.None;
// @ts-ignore
this.sliceConstructor = SlicedArray.getSliceConstructor(this);
constructor(...items: ItemType[]) {
super(...items);
this.slicedArray = self;
}
const first = this.constructSlice();
//first.setEnd(SliceEnd.Bottom);
this.slices = [first];
}
isEnd(side: SliceEnd) {
if(this.end & side) {
private static getSliceConstructor(slicedArray: SlicedArray) {
return class Slice extends Array<ItemType> implements Slice {
//slicedArray: SlicedArray;
end: SliceEnd = SliceEnd.None;
/* constructor(...items: ItemType[]) {
super(...items);
//this.slicedArray = slicedArray;
} */
isEnd(side: SliceEnd): boolean {
if((this.end & side) === side) {
return true;
}
}/* else if(!this.slicedArray) {
return false;
} */
let isEnd = false;
if(side === SliceEnd.Top) {
const slice = self.last;
return slice.end & side ? this.includes(slice[slice.length - 1]) || !slice.length : false;
const slice = slicedArray.last;
isEnd = slice.end & side ? this.includes(slice[slice.length - 1])/* || !slice.length */ : false;
} else if(side === SliceEnd.Bottom) {
const slice = self.first;
return slice.end & side ? this.includes(slice[0]) || !slice.length : false;
}/* else if(side === SliceEnd.Both) {
} */
const slice = slicedArray.first;
isEnd = slice.end & side ? this.includes(slice[0])/* || !slice.length */ : false;
} else if(side === SliceEnd.Both) {
return this.isEnd(SliceEnd.Top) && this.isEnd(SliceEnd.Bottom);
}
return false;
if(isEnd) {
this.setEnd(side);
}
return isEnd;
}
setEnd(side: SliceEnd) {
this.end |= side;
}
if(side !== SliceEnd.Both && this.end & SliceEnd.Top && this.end & SliceEnd.Bottom) {
this.end |= SliceEnd.Both;
unsetEnd(side: SliceEnd) {
this.end ^= side;
}
splice(start: number, deleteCount: number, ...items: ItemType[]) {
const ret = super.splice(start, deleteCount, ...items);
if(!this.length) {
const slices = slicedArray.slices as number[][];
const idx = slices.indexOf(this);
if(idx !== -1) {
if(slices.length === 1) { // left empty slice without ends
this.unsetEnd(SliceEnd.Both);
} else { // delete this slice
slices.splice(idx, 1);
}
}
}
return ret;
}
}
const first = this.constructSlice();
first.setEnd(SliceEnd.Bottom);
this.slices = [first];
}
public constructSlice(...items: ItemType[]) {
//const slice = new Slice(this, ...items);
const slice = new this.sliceConstructor(...items);
// can't pass items directly to constructor because first argument is length
const slice = new this.sliceConstructor(items.length);
for(let i = 0, length = items.length; i < length; ++i) {
slice[i] = items[i];
}
return slice;
// ! code below will slow execution in 15 times
@ -128,7 +167,7 @@ export default class SlicedArray { @@ -128,7 +167,7 @@ export default class SlicedArray {
*/
}
public insertSlice(slice: ItemType[]) {
public insertSlice(slice: ItemType[], flatten = true) {
if(!slice.length) {
return;
}
@ -136,15 +175,15 @@ export default class SlicedArray { @@ -136,15 +175,15 @@ export default class SlicedArray {
const first = this.slices[0];
if(!first.length) {
first.push(...slice);
return;
return first;
}
const lowerBound = slice[slice.length - 1];
const upperBound = slice[0];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1;
for(let i = 0; i < this.slices.length; ++i) {
foundSlice = this.slices[i];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1, foundSliceIndex = 0;
for(; foundSliceIndex < this.slices.length; ++foundSliceIndex) {
foundSlice = this.slices[foundSliceIndex];
lowerIndex = foundSlice.indexOf(lowerBound);
upperIndex = foundSlice.indexOf(upperBound);
@ -173,29 +212,38 @@ export default class SlicedArray { @@ -173,29 +212,38 @@ export default class SlicedArray {
}
this.slices.splice(insertIndex, 0, this.constructSlice(...slice));
foundSliceIndex = insertIndex;
}
this.flatten();
}
private flatten() {
if(this.slices.length < 2) {
return;
if(flatten) {
return this.flatten(foundSliceIndex);
}
}
for(let i = 0, length = this.slices.length; i < (length - 1); ++i) {
const prevSlice = this.slices[i];
const nextSlice = this.slices[i + 1];
private flatten(foundSliceIndex: number) {
if(this.slices.length >= 2) {
for(let i = 0, length = this.slices.length; i < (length - 1); ++i) {
const prevSlice = this.slices[i];
const nextSlice = this.slices[i + 1];
const upperIndex = prevSlice.indexOf(nextSlice[0]);
if(upperIndex !== -1) {
prevSlice.setEnd(nextSlice.end);
this.slices.splice(i + 1, 1);
const upperIndex = prevSlice.indexOf(nextSlice[0]);
if(upperIndex !== -1) {
prevSlice.setEnd(nextSlice.end);
this.slices.splice(i + 1, 1);
length--;
if(i < foundSliceIndex) {
--foundSliceIndex;
}
this.insertSlice(nextSlice);
--length; // respect array bounds
--i; // repeat from the same place
this.insertSlice(nextSlice, false);
}
}
}
return this.slices[foundSliceIndex];
}
// *
@ -217,7 +265,7 @@ export default class SlicedArray { @@ -217,7 +265,7 @@ export default class SlicedArray {
}
public findSlice(item: ItemType) {
for(let i = 0; i < this.slices.length; ++i) {
for(let i = 0, length = this.slices.length; i < length; ++i) {
const slice = this.slices[i];
const index = slice.indexOf(item);
if(index !== -1) {
@ -295,6 +343,8 @@ export default class SlicedArray { @@ -295,6 +343,8 @@ export default class SlicedArray {
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const bottomWasMeantToLoad = Math.abs(add_offset);
// can use 'slice' here to check because if it's end, then 'sliced' is out of 'slice'
// useful when there is only 1 message in chat on its reopening
const topFulfilled = (slice.length - sliceOffset) >= topWasMeantToLoad || (slice.isEnd(SliceEnd.Top) ? (sliced.setEnd(SliceEnd.Top), true) : false);
const bottomFulfilled = (sliceOffset - bottomWasMeantToLoad) >= 0 || (slice.isEnd(SliceEnd.Bottom) ? (sliced.setEnd(SliceEnd.Bottom), true) : false);
@ -308,19 +358,40 @@ export default class SlicedArray { @@ -308,19 +358,40 @@ export default class SlicedArray {
}
public unshift(...items: ItemType[]) {
this.first.unshift(...items);
let slice = this.first;
if(!slice.length) {
slice.setEnd(SliceEnd.Bottom);
} else if(!slice.isEnd(SliceEnd.Bottom)) {
slice = this.constructSlice();
slice.setEnd(SliceEnd.Bottom);
this.slices.unshift(slice);
}
slice.unshift(...items);
}
public push(...items: ItemType[]) {
this.last.push(...items);
let slice = this.last;
if(!slice.length) {
slice.setEnd(SliceEnd.Top);
} else if(!slice.isEnd(SliceEnd.Top)) {
slice = this.constructSlice();
slice.setEnd(SliceEnd.Top);
this.slices.push(slice);
}
slice.push(...items);
}
public delete(item: ItemType) {
const found = this.findSlice(item);
if(found) {
found.slice.splice(found.index, 1);
return true;
}
return false;
}
}
(window as any).slicedArray = new SlicedArray();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.SlicedArray = SlicedArray);

18
src/lang.ts

@ -133,6 +133,7 @@ const lang = { @@ -133,6 +133,7 @@ const lang = {
"AttachAudio": "Voice message",
"AttachRound": "Video message",
"AttachGame": "Game",
"Bot": "bot",
//"ChannelJoined": "You joined this channel",
"ChannelMegaJoined": "You joined this group",
"Channel.DescriptionPlaceholder": "Description (optional)",
@ -408,6 +409,14 @@ const lang = { @@ -408,6 +409,14 @@ const lang = {
"OpenUrlAlert2": "Do you want to open %1$s?",
"FilterNoChatsToDisplay": "Folder is empty",
"FilterNoChatsToDisplayInfo": "No chats currently belong to this folder.",
"SupportStatus": "support",
"Lately": "last seen recently",
"WithinAWeek": "last seen within a week",
"WithinAMonth": "last seen within a month",
"ALongTimeAgo": "last seen a long time ago",
"Online": "online",
"MessageScheduleSend": "Send Now",
"MessageScheduleEditTime": "Reschedule",
// * macos
"AccountSettings.Filters": "Chat Folders",
@ -420,8 +429,6 @@ const lang = { @@ -420,8 +429,6 @@ const lang = {
"Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.",
"Chat.CopySelectedText": "Copy Selected Text",
"Chat.Confirm.Unpin": "Would you like to unpin this message?",
"Chat.Context.Scheduled.SendNow": "Send Now",
"Chat.Context.Scheduled.Reschedule": "Re-schedule",
"Chat.Date.ScheduledFor": "Scheduled for %@",
//"Chat.Date.ScheduledUntilOnline": "Scheduled until online",
"Chat.Date.ScheduledForToday": "Scheduled for today",
@ -559,12 +566,7 @@ const lang = { @@ -559,12 +566,7 @@ const lang = {
"Peer.Activity.Chat.Multi.SendingFile1": "%@ and %d others are sending files",
"Peer.ServiceNotifications": "service notifications",
"Peer.RepliesNotifications": "Reply Notifications",
"Peer.Status.online": "online",
"Peer.Status.recently": "last seen recently",
"Peer.Status.justNow": "last seen just now",
"Peer.Status.lastWeek": "last seen within a week",
"Peer.Status.lastMonth": "last seen within a month",
"Peer.Status.longTimeAgo": "last seen a long time ago",
"Peer.Status.Today": "today",
"Peer.Status.Yesterday": "yesterday",
"Peer.Status.LastSeenAt": "last seen %@ at %@",
@ -614,8 +616,6 @@ const lang = { @@ -614,8 +616,6 @@ const lang = {
"one_value": "Send Video",
"other_value": "Send %d Videos"
},
"Presence.bot": "bot",
"Presence.Support": "support",
"PrivacyAndSecurity.Item.On": "On",
"PrivacyAndSecurity.Item.Off": "Off",
"PrivacySettings.VoiceCalls": "Calls",

1
src/lib/appManagers/appDialogsManager.ts

@ -1357,6 +1357,7 @@ export class AppDialogsManager { @@ -1357,6 +1357,7 @@ export class AppDialogsManager {
const span = document.createElement('span');
span.classList.add('user-last-message');
span.setAttribute('dir', 'auto');
//captionDiv.append(titleSpan);
//captionDiv.append(span);

8
src/lib/appManagers/appImManager.ts

@ -50,6 +50,7 @@ import { copy, getObjectKeysAndSort } from '../../helpers/object'; @@ -50,6 +50,7 @@ import { copy, getObjectKeysAndSort } from '../../helpers/object';
import { getFilesFromEvent } from '../../helpers/files';
import PeerTitle from '../../components/peerTitle';
import PopupPeer from '../../components/popups/peer';
import { SliceEnd } from '../../helpers/slicedArray';
//console.log('appImManager included33!');
@ -445,10 +446,11 @@ export class AppImManager { @@ -445,10 +446,11 @@ export class AppImManager {
return;
} else if(e.code === 'ArrowUp') {
if(!chat.input.editMsgId && chat.input.isInputEmpty()) {
const history = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
if(history.history.length) {
const historyStorage = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
const slice = historyStorage.history.slice;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
let goodMid: number;
for(const mid of history.history.slice) {
for(const mid of slice) {
const message = chat.getMessage(mid);
const good = this.myId === chat.peerId ? message.fromId === this.myId : message.pFlags.out;

182
src/lib/appManagers/appMessagesManager.ts

@ -14,7 +14,7 @@ import ProgressivePreloader from "../../components/preloader"; @@ -14,7 +14,7 @@ import ProgressivePreloader from "../../components/preloader";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { copy, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string";
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer";
@ -168,7 +168,7 @@ export class AppMessagesManager { @@ -168,7 +168,7 @@ export class AppMessagesManager {
public migratedFromTo: {[peerId: number]: number} = {};
public migratedToFrom: {[peerId: number]: number} = {};
public newMessagesHandlePromise = 0;
public newMessagesHandleTimeout = 0;
public newMessagesToHandle: {[peerId: string]: Set<number>} = {};
public newDialogsHandlePromise: Promise<any>;
public newDialogsToHandle: {[peerId: string]: Dialog} = {};
@ -1301,14 +1301,17 @@ export class AppMessagesManager { @@ -1301,14 +1301,17 @@ export class AppMessagesManager {
/* if(options.threadId && this.threadsStorage[peerId]) {
delete this.threadsStorage[peerId][options.threadId];
} */
if(options.threadId) {
const historyStorage = this.getHistoryStorage(peerId, options.threadId);
historyStorage.history.unshift(messageId);
const storages: HistoryStorage[] = [
this.getHistoryStorage(peerId),
options.threadId ? this.getHistoryStorage(peerId, options.threadId) : undefined
];
for(const storage of storages) {
if(storage) {
storage.history.unshift(messageId);
}
}
const historyStorage = this.getHistoryStorage(peerId);
historyStorage.history.unshift(messageId);
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
setTimeout(() => {
@ -1514,7 +1517,6 @@ export class AppMessagesManager { @@ -1514,7 +1517,6 @@ export class AppMessagesManager {
if(pendingData) {
const {peerId, tempId, storage} = pendingData;
const historyStorage = this.getHistoryStorage(peerId);
const pos = historyStorage.history.findSlice(tempId);
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
@ -1524,9 +1526,7 @@ export class AppMessagesManager { @@ -1524,9 +1526,7 @@ export class AppMessagesManager {
}
});
if(pos) {
pos.slice.splice(pos.index, 1);
}
historyStorage.history.delete(tempId);
delete this.pendingByRandomId[randomId];
delete storage[tempId];
@ -1627,7 +1627,7 @@ export class AppMessagesManager { @@ -1627,7 +1627,7 @@ export class AppMessagesManager {
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
return apiManager.invokeApi('messages.getDialogs', {
return apiManager.invokeApiSingle('messages.getDialogs', {
folder_id: folderId,
offset_date: offsetDate,
offset_id: offsetId,
@ -3271,7 +3271,7 @@ export class AppMessagesManager { @@ -3271,7 +3271,7 @@ export class AppMessagesManager {
}
public getDiscussionMessage(peerId: number, mid: number) {
return apiManager.invokeApi('messages.getDiscussionMessage', {
return apiManager.invokeApiSingle('messages.getDiscussionMessage', {
peer: appPeersManager.getInputPeerById(peerId),
msg_id: this.getServerMessageId(mid)
}).then(result => {
@ -3295,9 +3295,20 @@ export class AppMessagesManager { @@ -3295,9 +3295,20 @@ export class AppMessagesManager {
});
}
private handleNewMessage(peerId: number, mid: number) {
if(this.newMessagesToHandle[peerId] === undefined) {
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(mid);
if(!this.newMessagesHandleTimeout) {
this.newMessagesHandleTimeout = window.setTimeout(this.handleNewMessages, 0);
}
}
handleNewMessages = () => {
clearTimeout(this.newMessagesHandlePromise);
this.newMessagesHandlePromise = 0;
clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0;
rootScope.broadcast('history_multiappend', this.newMessagesToHandle);
this.newMessagesToHandle = {};
@ -3404,6 +3415,7 @@ export class AppMessagesManager { @@ -3404,6 +3415,7 @@ export class AppMessagesManager {
return promise;
}
// TODO: cancel notification by peer when this function is being called
public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) {
//return Promise.resolve();
// console.trace('start read')
@ -3452,7 +3464,7 @@ export class AppMessagesManager { @@ -3452,7 +3464,7 @@ export class AppMessagesManager {
_: 'updateReadChannelInbox',
max_id: maxId,
channel_id: -peerId
}
} as Update.updateReadChannelInbox
});
} else {
if(!historyStorage.readPromise) {
@ -3477,21 +3489,10 @@ export class AppMessagesManager { @@ -3477,21 +3489,10 @@ export class AppMessagesManager {
_: 'updateReadHistoryInbox',
max_id: maxId,
peer: appPeersManager.getOutputPeer(peerId)
}
} as Update.updateReadHistoryInbox
});
}
if(!threadId && historyStorage && historyStorage.history.length) {
const slice = historyStorage.history.slice;
for(const mid of slice) {
const message = this.getMessageByPeer(peerId, mid);
if(message && !message.pFlags.out) {
message.pFlags.unread = false;
appNotificationsManager.cancel('msg' + mid);
}
}
}
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId));
if(historyStorage.readPromise) {
@ -3534,7 +3535,7 @@ export class AppMessagesManager { @@ -3534,7 +3535,7 @@ export class AppMessagesManager {
_: 'updateChannelReadMessagesContents',
channel_id: channelId,
messages: msgIds
}
} as Update.updateChannelReadMessagesContents
});
});
} else {
@ -3548,7 +3549,7 @@ export class AppMessagesManager { @@ -3548,7 +3549,7 @@ export class AppMessagesManager {
messages: msgIds,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
} as Update.updateReadMessagesContents
});
});
}
@ -3694,14 +3695,19 @@ export class AppMessagesManager { @@ -3694,14 +3695,19 @@ export class AppMessagesManager {
return false;
}
const history = historyStorage.history.slice;
const topMsgId = history[0];
history.unshift(message.mid);
if(message.mid < topMsgId) {
//this.log.error('this should\'nt have happenned!', message, history);
history.sort((a, b) => {
return b - a;
});
// * catch situation with disconnect. if message's id is lower than we already have (in bottom end slice), will sort it
const firstSlice = historyStorage.history.first;
if(firstSlice.isEnd(SliceEnd.Bottom)) {
let i = 0;
for(const length = firstSlice.length; i < length; ++i) {
if(message.mid > firstSlice[i]) {
break;
}
}
firstSlice.splice(i, 0, message.mid);
} else {
historyStorage.history.unshift(message.mid);
}
if(historyStorage.count !== null) {
@ -3717,14 +3723,7 @@ export class AppMessagesManager { @@ -3717,14 +3723,7 @@ export class AppMessagesManager {
}
if(!pendingMessage) {
if(this.newMessagesToHandle[peerId] === undefined) {
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(message.mid);
if(!this.newMessagesHandlePromise) {
this.newMessagesHandlePromise = window.setTimeout(this.handleNewMessages, 0);
}
this.handleNewMessage(peerId, message.mid);
}
if(isLocalThreadUpdate) {
@ -4471,7 +4470,7 @@ export class AppMessagesManager { @@ -4471,7 +4470,7 @@ export class AppMessagesManager {
return Promise.resolve(Object.keys(storage).map(id => +id));
}
return apiManager.invokeApi('messages.getScheduledHistory', {
return apiManager.invokeApiSingle('messages.getScheduledHistory', {
peer: appPeersManager.getInputPeerById(peerId),
hash: 0
}).then(historyResult => {
@ -4506,6 +4505,36 @@ export class AppMessagesManager { @@ -4506,6 +4505,36 @@ export class AppMessagesManager {
});
}
public isFetchIntervalNeeded(peerId: number) {
return peerId < 0 && !appChatsManager.isInChat(peerId);
}
public async getNewHistory(peerId: number, threadId?: number) {
if(!this.isFetchIntervalNeeded(peerId)) {
return;
}
const historyStorage = this.getHistoryStorage(peerId, threadId);
const slice = historyStorage.history.slice;
if(!slice.isEnd(SliceEnd.Bottom)) {
return;
}
delete historyStorage.maxId;
slice.unsetEnd(SliceEnd.Bottom);
let historyResult = this.getHistory(peerId, slice[0], 0, 50, threadId);
if(historyResult instanceof Promise) {
historyResult = await historyResult;
}
for(let i = 0, length = historyResult.history.length; i < length; ++i) {
this.handleNewMessage(peerId, historyResult.history[i]);
}
return historyStorage;
}
/**
* * https://core.telegram.org/api/offsets, offset_id is inclusive
*/
@ -4567,7 +4596,7 @@ export class AppMessagesManager { @@ -4567,7 +4596,7 @@ export class AppMessagesManager {
}
const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit);
if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both))) {
if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both) === SliceEnd.Both)) {
return {
count: historyStorage.count,
history: haveSlice.slice,
@ -4587,10 +4616,15 @@ export class AppMessagesManager { @@ -4587,10 +4616,15 @@ export class AppMessagesManager {
public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> {
return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => {
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length;
const {offset_id_offset, count, messages} = historyResult as MessagesMessages.messagesMessagesSlice;
historyStorage.count = count || messages.length;
const offsetIdOffset = offset_id_offset || 0;
const offsetIdOffset = (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0;
const isTopEnd = offsetIdOffset >= (historyStorage.count - limit) || historyStorage.count < (limit + add_offset);
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const isTopEnd = offsetIdOffset >= (historyStorage.count - topWasMeantToLoad) || historyStorage.count < topWasMeantToLoad;
const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
/* if(!maxId && historyResult.messages.length) {
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1);
@ -4598,13 +4632,14 @@ export class AppMessagesManager { @@ -4598,13 +4632,14 @@ export class AppMessagesManager {
const wasTotalCount = historyStorage.history.length; */
historyResult.messages.forEach((message) => {
const mids = messages.map((message) => {
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId});
}
return (message as MyMessage).mid;
});
const mids = historyResult.messages.map((message) => (message as MyMessage).mid);
// * add bound manually.
// * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id')
if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) {
@ -4618,10 +4653,16 @@ export class AppMessagesManager { @@ -4618,10 +4653,16 @@ export class AppMessagesManager {
mids.splice(i, 0, offset_id);
}
historyStorage.history.insertSlice(mids);
if(isTopEnd) {
historyStorage.history.last.setEnd(SliceEnd.Top);
const slice = historyStorage.history.insertSlice(mids);
if(slice) {
if(isTopEnd) {
slice.setEnd(SliceEnd.Top);
}
if(isBottomEnd) {
slice.setEnd(SliceEnd.Bottom);
historyStorage.maxId = slice[0]; // ! WARNING
}
}
/* const isBackLimit = offset < 0 && -offset !== fullLimit;
@ -4681,7 +4722,7 @@ export class AppMessagesManager { @@ -4681,7 +4722,7 @@ export class AppMessagesManager {
options.msg_id = this.getServerMessageId(threadId) || 0;
}
const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApi(threadId ? 'messages.getReplies' : 'messages.getHistory', options, {
const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApiSingle(threadId ? 'messages.getReplies' : 'messages.getHistory', options, {
//timeout: APITIMEOUT,
noErrorBox: true
}) as any;
@ -4699,21 +4740,24 @@ export class AppMessagesManager { @@ -4699,21 +4740,24 @@ export class AppMessagesManager {
apiUpdatesManager.addChannelState(-peerId, (historyResult as MessagesMessages.messagesChannelMessages).pts);
}
let length = historyResult.messages.length;
let length = historyResult.messages.length, count = (historyResult as MessagesMessages.messagesMessagesSlice).count;
if(length && historyResult.messages[length - 1].deleted) {
historyResult.messages.splice(length - 1, 1);
length--;
(historyResult as MessagesMessages.messagesMessagesSlice).count--;
count--;
}
// will load more history if last message is album grouped (because it can be not last item)
const historyStorage = this.getHistoryStorage(peerId, threadId);
// historyResult.messages: desc sorted
if(length && (historyResult.messages[length - 1] as Message.message).grouped_id
&& (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) {
return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0, offsetDate, threadId).then((_historyResult) => {
return historyResult;
});
const historyStorage = this.getHistoryStorage(peerId, threadId);
const oldestMessage: Message.message = historyResult.messages[length - 1] as any;
if(length && oldestMessage.grouped_id) {
const foundSlice = historyStorage.history.findSlice(oldestMessage.mid);
if(foundSlice && (foundSlice.slice.length + historyResult.messages.length) < count) {
return this.requestHistory(peerId, oldestMessage.mid, 10, 0, offsetDate, threadId).then((_historyResult) => {
return historyResult;
});
}
}
return historyResult;
@ -4760,12 +4804,12 @@ export class AppMessagesManager { @@ -4760,12 +4804,12 @@ export class AppMessagesManager {
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>;
if(+peerId < 0 && appPeersManager.isChannel(+peerId)) {
promise = apiManager.invokeApi('channels.getMessages', {
promise = apiManager.invokeApiSingle('channels.getMessages', {
channel: appChatsManager.getChannelInput(-+peerId),
id: msgIds
});
} else {
promise = apiManager.invokeApi('messages.getMessages', {
promise = apiManager.invokeApiSingle('messages.getMessages', {
id: msgIds
});
}

2
src/lib/appManagers/appPhotosManager.ts

@ -13,7 +13,7 @@ import type { DownloadOptions } from "../mtproto/apiFileManager"; @@ -13,7 +13,7 @@ import type { DownloadOptions } from "../mtproto/apiFileManager";
import { bytesFromHex } from "../../helpers/bytes";
import { CancellablePromise } from "../../helpers/cancellablePromise";
import { getFileNameByLocation } from "../../helpers/fileName";
import { safeReplaceArrayInObject, defineNotNumerableProperties, isObject } from "../../helpers/object";
import { safeReplaceArrayInObject, isObject } from "../../helpers/object";
import { isSafari } from "../../helpers/userAgent";
import { InputFileLocation, InputMedia, Photo, PhotoSize, PhotosPhotos } from "../../layer";
import apiManager from "../mtproto/mtprotoworker";

14
src/lib/appManagers/appUsersManager.ts

@ -431,7 +431,7 @@ export class AppUsersManager { @@ -431,7 +431,7 @@ export class AppUsersManager {
break;
default: {
if(this.isBot(userId)) {
key = 'Presence.bot';
key = 'Bot';
break;
}
@ -442,23 +442,23 @@ export class AppUsersManager { @@ -442,23 +442,23 @@ export class AppUsersManager {
}
if(user.pFlags.support) {
key = 'Presence.Support';
key = 'SupportStatus';
break;
}
switch(user.status?._) {
case 'userStatusRecently': {
key = 'Peer.Status.recently';
key = 'Lately';
break;
}
case 'userStatusLastWeek': {
key = 'Peer.Status.lastWeek';
key = 'WithinAWeek';
break;
}
case 'userStatusLastMonth': {
key = 'Peer.Status.lastMonth';
key = 'WithinAMonth';
break;
}
@ -487,12 +487,12 @@ export class AppUsersManager { @@ -487,12 +487,12 @@ export class AppUsersManager {
}
case 'userStatusOnline': {
key = 'Peer.Status.online';
key = 'Online';
break;
}
default: {
key = 'Peer.Status.longTimeAgo';
key = 'ALongTimeAgo';
break;
}
}

7
src/lib/storages/dialogs.ts

@ -25,6 +25,7 @@ import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array" @@ -25,6 +25,7 @@ import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array"
import rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object";
import { AppStateManager } from "../appManagers/appStateManager";
import { SliceEnd } from "../../helpers/slicedArray";
export default class DialogsStorage {
private storage: AppStateManager['storages']['dialogs'];
@ -490,13 +491,17 @@ export default class DialogsStorage { @@ -490,13 +491,17 @@ export default class DialogsStorage {
}
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId);
const slice = historyStorage.history.slice;
/* if(historyStorage === undefined) { // warning
historyStorage.history.push(mid);
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId});
}
} else */if(!historyStorage.history.slice.length) {
} else */if(!slice.length) {
historyStorage.history.unshift(mid);
} else if(!slice.isEnd(SliceEnd.Bottom)) { // * this will probably never happen, however, if it does, then it will fix slice with top_message
const slice = historyStorage.history.insertSlice([mid]);
slice.setEnd(SliceEnd.Bottom);
}
historyStorage.maxId = mid;

2
src/scss/partials/_button.scss

@ -384,7 +384,7 @@ @@ -384,7 +384,7 @@
--size: 54px;
border-radius: 50%;
height: var(--size);
width: var(--size) !important;
width: var(--size);
line-height: var(--size);
@include respond-to(handhelds) {

20
src/scss/partials/_chatBubble.scss

@ -995,6 +995,7 @@ $bubble-margin: .25rem; @@ -995,6 +995,7 @@ $bubble-margin: .25rem;
position: relative !important;
height: 0px !important;
visibility: hidden !important;
float: none;
.inner {
visibility: hidden !important;
@ -1250,6 +1251,8 @@ $bubble-margin: .25rem; @@ -1250,6 +1251,8 @@ $bubble-margin: .25rem;
/* display: inline-flex;
align-items: center; */
height: 12px;
direction: ltr;
float: right; // * rtl fix
i {
font-size: 1.15rem;
@ -1284,6 +1287,10 @@ $bubble-margin: .25rem; @@ -1284,6 +1287,10 @@ $bubble-margin: .25rem;
}
}
&.webpage .time {
float: none;
}
.video-time {
position: absolute;
top: 3px;
@ -1893,7 +1900,7 @@ $bubble-margin: .25rem; @@ -1893,7 +1900,7 @@ $bubble-margin: .25rem;
margin-left: -4px;
.inner {
color: var(--message-out-primary-color);
color: var(--message-out-status-color);
bottom: 4px;
}
@ -1902,9 +1909,14 @@ $bubble-margin: .25rem; @@ -1902,9 +1909,14 @@ $bubble-margin: .25rem;
//vertical-align: middle;
margin-left: 1px;
line-height: 16px; // of message
color: var(--message-out-primary-color);
}
}
&.is-message-empty .time .inner {
color: var(--message-out-primary-color);
}
/* &.is-message-empty .time:after {
margin-bottom: 1px;
} */
@ -1954,7 +1966,7 @@ $bubble-margin: .25rem; @@ -1954,7 +1966,7 @@ $bubble-margin: .25rem;
}
&-time, &-subtitle {
color: var(--message-out-primary-color);
color: var(--message-out-status-color);
}
&-toggle, &-download {
@ -2002,8 +2014,8 @@ $bubble-margin: .25rem; @@ -2002,8 +2014,8 @@ $bubble-margin: .25rem;
}
}
.audio-subtitle, .contact-number, .document-size {
color: var(--message-out-primary-color);
.contact-number, .document-size {
color: var(--message-out-status-color);
}
poll-element {

1
src/scss/partials/_selector.scss

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
}
.selector {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;

4
src/scss/partials/_sidebar.scss

@ -57,8 +57,8 @@ @@ -57,8 +57,8 @@
margin-top: 60px;
} */
> div {
/* > div {
width: 100%;
}
} */
}
}

2
src/scss/style.scss

@ -170,6 +170,7 @@ $chat-input-inner-padding-handhelds: .25rem; @@ -170,6 +170,7 @@ $chat-input-inner-padding-handhelds: .25rem;
@include splitColor(message-out-background-color, #eeffde, true, true);
--message-out-link-color: var(--link-color);
--message-out-primary-color: #4fae4e;
--message-out-status-color: var(--message-out-primary-color);
--message-out-audio-play-button-color: #fff;
// * Day theme end
@ -215,6 +216,7 @@ html.night { @@ -215,6 +216,7 @@ html.night {
@include splitColor(message-out-background-color, #8774E1, true, true);
--message-out-link-color: #fff;
--message-out-primary-color: #fff;
--message-out-status-color: rgba(255, 255, 255, .6);
--message-out-audio-play-button-color: var(--message-out-background-color);
// * Night theme end
}

121
src/tests/slicedArray.test.ts

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
import SlicedArray, { Slice } from "../helpers/slicedArray";
test('Slicing returns new Slice', () => {
const sliced = new SlicedArray();
const newSlice = sliced.slice.slice();
expect(newSlice.isEnd).toBeDefined();
});
describe('Inserting', () => {
const sliced = new SlicedArray();
// @ts-ignore
const slices = sliced.slices;
const arr = [100, 99, 98, 97, 96, 95];
const distantArr = arr.slice(-2).map(v => v - 2);
const missingArr = [arr[arr.length - 1], arr[arr.length - 1] - 1, distantArr[0]];
const startValue = 90;
const values: number[] = [];
const valuesPerArray = 3;
const totalArrays = 10;
for(let i = 0, length = valuesPerArray * totalArrays; i < length; ++i) {
values.push(startValue - i);
}
const arrays: number[][] = [];
for(let i = 0; i < totalArrays; ++i) {
arrays.push(values.slice(valuesPerArray * i, valuesPerArray * (i + 1)));
}
test('Insert & flatten', () => {
const idx = 2;
sliced.insertSlice(arr.slice(0, idx + 1));
sliced.insertSlice(arr.slice(idx));
expect([...sliced.first]).toEqual(arr);
});
test('Insert inner values', () => {
sliced.insertSlice(arr.slice(1, -1));
expect([...sliced.first]).toEqual(arr);
});
test('Insert distant slice', () => {
const length = slices.length;
sliced.insertSlice(distantArr);
expect(slices.length).toEqual(length + 1);
});
test('Insert intersection & join them', () => {
const length = slices.length;
sliced.insertSlice(missingArr);
expect(slices.length).toEqual(length - 1);
});
let returnedSlice: Slice;
test('Insert arrays with gap & join them', () => {
slices[0].length = 0;
for(const arr of arrays) {
sliced.insertSlice(arr);
}
expect(slices.length).toEqual(totalArrays);
returnedSlice = sliced.insertSlice(values.slice(0, -valuesPerArray + 1));
expect(slices.length).toEqual(1);
});
test('Return inserted & flattened slice', () => {
expect(slices[0]).toEqual(returnedSlice);
});
});
describe('Slicing', () => {
const sliced = new SlicedArray();
// @ts-ignore
const slices = sliced.slices;
const VALUES_LENGTH = 100;
const INCREMENTOR = 0xFFFF;
const values: number[] = [];
for(let i = 0; i < VALUES_LENGTH; ++i) {
values[i] = i + INCREMENTOR * i;
}
values.sort((a, b) => b - a);
sliced.insertSlice(values);
const addOffset = 40;
const limit = 40;
const r = (func: (idx: number) => void) => {
const max = VALUES_LENGTH * 3;
for(let i = 0; i < max; ++i) {
const idx = Math.random() * max | 0;
func(idx);
}
};
describe('Positive addOffset', () => {
test('From the start', () => {
const {slice} = sliced.sliceMe(0, addOffset, limit);
expect([...slice]).toEqual(values.slice(addOffset, addOffset + limit));
});
test('From existing offsetId', () => {
r((idx) => {
const value = values[idx] || 1;
idx += 1; // because is it inclusive
const {slice} = sliced.sliceMe(value, addOffset, limit);
expect([...slice]).toEqual(values.slice(idx + addOffset, idx + addOffset + limit));
});
});
});
});
Loading…
Cancel
Save