Browse Source

Refactor wrapping rich text

master
Eduard Kuzmenko 3 years ago
parent
commit
4336260293
  1. 6
      src/components/appMediaPlaybackController.ts
  2. 2
      src/components/appMediaViewer.ts
  3. 2
      src/components/appMediaViewerBase.ts
  4. 14
      src/components/appSearchSuper..ts
  5. 10
      src/components/audio.ts
  6. 3
      src/components/call/index.ts
  7. 11
      src/components/chat/audio.ts
  8. 5
      src/components/chat/autocompletePeerHelper.ts
  9. 51
      src/components/chat/bubbles.ts
  10. 2
      src/components/chat/chat.ts
  11. 11
      src/components/chat/inlineHelper.ts
  12. 5
      src/components/chat/input.ts
  13. 4
      src/components/chat/messageRender.ts
  14. 3
      src/components/chat/replyKeyboard.ts
  15. 4
      src/components/emoticonsDropdown/tabs/emoji.ts
  16. 5
      src/components/emoticonsDropdown/tabs/stickers.ts
  17. 11
      src/components/inputField.ts
  18. 5
      src/components/peerProfile.ts
  19. 5
      src/components/peerTitle.ts
  20. 7
      src/components/poll.ts
  21. 3
      src/components/popups/joinChatInvite.ts
  22. 2
      src/components/popups/newMedia.ts
  23. 5
      src/components/popups/peer.ts
  24. 3
      src/components/popups/stickers.ts
  25. 2
      src/components/row.ts
  26. 3
      src/components/sidebarLeft/tabs/2fa/enterPassword.ts
  27. 3
      src/components/sidebarLeft/tabs/editFolder.ts
  28. 3
      src/components/sidebarLeft/tabs/includedChats.ts
  29. 5
      src/components/sidebarRight/tabs/pollResults.ts
  30. 8
      src/components/wrappers.ts
  31. 5
      src/helpers/dom/documentFragmentToHTML.ts
  32. 5
      src/helpers/dom/htmlToDocumentFragment.ts
  33. 5
      src/helpers/dom/htmlToSpan.ts
  34. 9
      src/helpers/dom/setInnerHTML.ts
  35. 23
      src/layer.d.ts
  36. 14
      src/lib/appManagers/appAvatarsManager.ts
  37. 2
      src/lib/appManagers/appChatsManager.ts
  38. 5
      src/lib/appManagers/appDialogsManager.ts
  39. 8
      src/lib/appManagers/appDocsManager.ts
  40. 3
      src/lib/appManagers/appDraftsManager.ts
  41. 13
      src/lib/appManagers/appMessagesManager.ts
  42. 12
      src/lib/appManagers/appPeersManager.ts
  43. 2
      src/lib/appManagers/appPollsManager.ts
  44. 3
      src/lib/appManagers/appUsersManager.ts
  45. 38
      src/lib/appManagers/appWebPagesManager.ts
  46. 326
      src/lib/richtextprocessor.ts
  47. 5
      src/pages/pageSignIn.ts
  48. 33
      src/scripts/in/schema_additional_params.json

6
src/components/appMediaPlaybackController.ts

@ -467,8 +467,8 @@ export class AppMediaPlaybackController {
if(!isVoice) { if(!isVoice) {
const attribute = doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio; const attribute = doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio;
title = attribute && attribute.title || doc.file_name; title = attribute?.title ?? doc.file_name;
artist = attribute && attribute.performer; artist = attribute?.performer;
} }
if(!artwork.length) { if(!artwork.length) {
@ -529,7 +529,7 @@ export class AppMediaPlaybackController {
const message = this.getMessageByMedia(playingMedia); const message = this.getMessageByMedia(playingMedia);
return { return {
doc: appMessagesManager.getMediaFromMessage(message), doc: appMessagesManager.getMediaFromMessage(message) as MyDocument,
message, message,
media: playingMedia, media: playingMedia,
playbackParams: this.getPlaybackParams() playbackParams: this.getPlaybackParams()

2
src/components/appMediaViewer.ts

@ -236,7 +236,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
private setCaption(message: MyMessage) { private setCaption(message: MyMessage) {
const caption = (message as Message.message).message; const caption = (message as Message.message).message;
let html = ''; let html: Parameters<typeof setInnerHTML>[1] = '';
if(caption) { if(caption) {
html = RichTextProcessor.wrapRichText(caption, { html = RichTextProcessor.wrapRichText(caption, {
entities: (message as Message.message).totalEntities entities: (message as Message.message).totalEntities

2
src/components/appMediaViewerBase.ts

@ -1165,7 +1165,7 @@ export default class AppMediaViewerBase<
}).element; }).element;
} else { } else {
title = document.createElement('span'); title = document.createElement('span');
title.innerHTML = RichTextProcessor.wrapEmojiText(fromId); title.append(RichTextProcessor.wrapEmojiText(fromId));
title.classList.add('peer-title'); title.classList.add('peer-title');
} }

14
src/components/appSearchSuper..ts

@ -56,6 +56,8 @@ import escapeRegExp from "../helpers/string/escapeRegExp";
import limitSymbols from "../helpers/string/limitSymbols"; import limitSymbols from "../helpers/string/limitSymbols";
import findAndSplice from "../helpers/array/findAndSplice"; import findAndSplice from "../helpers/array/findAndSplice";
import { ScrollStartCallbackDimensions } from "../helpers/fastSmoothScroll"; import { ScrollStartCallbackDimensions } from "../helpers/fastSmoothScroll";
import setInnerHTML from "../helpers/dom/setInnerHTML";
import appWebPagesManager from "../lib/appManagers/appWebPagesManager";
//const testScroll = false; //const testScroll = false;
@ -699,7 +701,6 @@ export default class AppSearchSuper {
if(!same) { if(!same) {
webpage.description = message.message; webpage.description = message.message;
webpage.rDescription = RichTextProcessor.wrapRichText(limitSymbols(message.message, 150, 180));
} }
} }
@ -724,13 +725,12 @@ export default class AppSearchSuper {
}); });
} else { } else {
previewDiv.classList.add('empty'); previewDiv.classList.add('empty');
previewDiv.innerHTML = RichTextProcessor.getAbbreviation(webpage.title || webpage.display_url || webpage.description || webpage.url, true); setInnerHTML(previewDiv, RichTextProcessor.getAbbreviation(webpage.title || webpage.display_url || webpage.description || webpage.url, true));
} }
let title = webpage.rTitle || ''; let title = appWebPagesManager.wrapTitle(webpage);
let subtitle = webpage.rDescription || '';
const subtitleFragment = htmlToDocumentFragment(subtitle); const subtitleFragment = appWebPagesManager.wrapDescription(webpage);
const aFragment = htmlToDocumentFragment(RichTextProcessor.wrapRichText(webpage.url || '')); const aFragment = htmlToDocumentFragment(RichTextProcessor.wrapRichText(webpage.url || ''));
const a = aFragment.firstElementChild; const a = aFragment.firstElementChild;
if(a instanceof HTMLAnchorElement) { if(a instanceof HTMLAnchorElement) {
@ -751,9 +751,9 @@ export default class AppSearchSuper {
subtitleFragment.append('\n', appMessagesManager.wrapSenderToPeer(message)); subtitleFragment.append('\n', appMessagesManager.wrapSenderToPeer(message));
} }
if(!title) { if(!title.textContent) {
//title = new URL(webpage.url).hostname; //title = new URL(webpage.url).hostname;
title = RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]); title.append(RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]));
} }
const row = new Row({ const row = new Row({

10
src/components/audio.ts

@ -32,6 +32,8 @@ import { animateSingle } from "../helpers/animation";
import clamp from "../helpers/number/clamp"; import clamp from "../helpers/number/clamp";
import toHHMMSS from "../helpers/string/toHHMMSS"; import toHHMMSS from "../helpers/string/toHHMMSS";
import MediaProgressLine from "./mediaProgressLine"; import MediaProgressLine from "./mediaProgressLine";
import RichTextProcessor from "../lib/richtextprocessor";
import setInnerHTML from "../helpers/dom/setInnerHTML";
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => { rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
mids.forEach(mid => { mids.forEach(mid => {
@ -265,10 +267,12 @@ function wrapAudio(audioEl: AudioElement) {
const descriptionEl = document.createElement('div'); const descriptionEl = document.createElement('div');
descriptionEl.classList.add('audio-description'); descriptionEl.classList.add('audio-description');
const audioAttribute = doc.attributes.find((attr) => attr._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio;
if(!isVoice) { if(!isVoice) {
const parts: (Node | string)[] = []; const parts: (Node | string)[] = [];
if(doc.audioPerformer) { if(audioAttribute?.performer) {
parts.push(htmlToSpan(doc.audioPerformer)); parts.push(RichTextProcessor.wrapEmojiText(audioAttribute.performer));
} }
if(withTime) { if(withTime) {
@ -299,7 +303,7 @@ function wrapAudio(audioEl: AudioElement) {
if(isVoice) { if(isVoice) {
middleEllipsisEl.append(appMessagesManager.wrapSenderToPeer(message)); middleEllipsisEl.append(appMessagesManager.wrapSenderToPeer(message));
} else { } else {
middleEllipsisEl.innerHTML = doc.audioTitle || doc.fileName; setInnerHTML(middleEllipsisEl, RichTextProcessor.wrapEmojiText(audioAttribute?.title ?? doc.file_name));
} }
titleEl.append(middleEllipsisEl); titleEl.append(middleEllipsisEl);

3
src/components/call/index.ts

@ -11,7 +11,6 @@ import ControlsHover from "../../helpers/dom/controlsHover";
import findUpClassName from "../../helpers/dom/findUpClassName"; import findUpClassName from "../../helpers/dom/findUpClassName";
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../../helpers/dom/fullScreen"; import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../../helpers/dom/fullScreen";
import { onMediaLoad } from "../../helpers/files"; import { onMediaLoad } from "../../helpers/files";
import { MediaSize } from "../../helpers/mediaSizes";
import MovablePanel from "../../helpers/movablePanel"; import MovablePanel from "../../helpers/movablePanel";
import safeAssign from "../../helpers/object/safeAssign"; import safeAssign from "../../helpers/object/safeAssign";
import toggleClassName from "../../helpers/toggleClassName"; import toggleClassName from "../../helpers/toggleClassName";
@ -457,7 +456,7 @@ export default class PopupCall extends PopupElement {
if(!this.emojisSubtitle.textContent && connectionState < CALL_STATE.EXCHANGING_KEYS) { if(!this.emojisSubtitle.textContent && connectionState < CALL_STATE.EXCHANGING_KEYS) {
Promise.resolve(instance.getEmojisFingerprint()).then(emojis => { Promise.resolve(instance.getEmojisFingerprint()).then(emojis => {
this.emojisSubtitle.innerHTML = RichTextProcessor.wrapEmojiText(emojis.join('')); this.emojisSubtitle.append(RichTextProcessor.wrapEmojiText(emojis.join('')));
}); });
} }

11
src/components/chat/audio.ts

@ -18,10 +18,10 @@ import PeerTitle from "../peerTitle";
import { i18n } from "../../lib/langPack"; import { i18n } from "../../lib/langPack";
import { formatFullSentTime } from "../../helpers/date"; import { formatFullSentTime } from "../../helpers/date";
import ButtonIcon from "../buttonIcon"; import ButtonIcon from "../buttonIcon";
import { MyDocument } from "../../lib/appManagers/appDocsManager"; import { DocumentAttribute } from "../../layer";
import { Message } from "../../layer";
import MediaProgressLine from "../mediaProgressLine"; import MediaProgressLine from "../mediaProgressLine";
import VolumeSelector from "../volumeSelector"; import VolumeSelector from "../volumeSelector";
import RichTextProcessor from "../../lib/richtextprocessor";
export default class ChatAudio extends PinnedContainer { export default class ChatAudio extends PinnedContainer {
private toggleEl: HTMLElement; private toggleEl: HTMLElement;
@ -149,7 +149,7 @@ export default class ChatAudio extends PinnedContainer {
}; };
private onMediaPlay = ({doc, message, media, playbackParams}: ReturnType<AppMediaPlaybackController['getPlayingDetails']>) => { private onMediaPlay = ({doc, message, media, playbackParams}: ReturnType<AppMediaPlaybackController['getPlayingDetails']>) => {
let title: string | HTMLElement, subtitle: string | HTMLElement | DocumentFragment; let title: string | HTMLElement | DocumentFragment, subtitle: string | HTMLElement | DocumentFragment;
const isMusic = doc.type !== 'voice' && doc.type !== 'round'; const isMusic = doc.type !== 'voice' && doc.type !== 'round';
if(!isMusic) { if(!isMusic) {
title = new PeerTitle({peerId: message.fromId, fromName: message.fwd_from?.from_name}).element; title = new PeerTitle({peerId: message.fromId, fromName: message.fwd_from?.from_name}).element;
@ -157,8 +157,9 @@ export default class ChatAudio extends PinnedContainer {
//subtitle = 'Voice message'; //subtitle = 'Voice message';
subtitle = formatFullSentTime(message.date); subtitle = formatFullSentTime(message.date);
} else { } else {
title = doc.audioTitle || doc.fileName; const audioAttribute = doc.attributes.find((attr) => attr._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio;
subtitle = doc.audioPerformer || i18n('AudioUnknownArtist'); title = RichTextProcessor.wrapEmojiText(audioAttribute?.title ?? doc.file_name);
subtitle = audioAttribute?.performer ? RichTextProcessor.wrapEmojiText(audioAttribute.performer) : i18n('AudioUnknownArtist');
} }
this.fasterEl.classList.toggle('hide', isMusic); this.fasterEl.classList.toggle('hide', isMusic);

5
src/components/chat/autocompletePeerHelper.ts

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import RichTextProcessor from "../../lib/richtextprocessor"; import RichTextProcessor from "../../lib/richtextprocessor";
import AvatarElement from "../avatar"; import AvatarElement from "../avatar";
import PeerTitle from "../peerTitle"; import PeerTitle from "../peerTitle";
@ -106,7 +107,7 @@ export default class AutocompletePeerHelper extends AutocompleteHelper {
plainText: false plainText: false
}).element); }).element);
} else { } else {
name.innerHTML = RichTextProcessor.wrapEmojiText(options.name); setInnerHTML(name, RichTextProcessor.wrapEmojiText(options.name));
} }
div.append(avatar, name); div.append(avatar, name);
@ -114,7 +115,7 @@ export default class AutocompletePeerHelper extends AutocompleteHelper {
if(options.description) { if(options.description) {
const description = document.createElement('div'); const description = document.createElement('div');
description.classList.add(BASE + '-description', options.className + '-description'); description.classList.add(BASE + '-description', options.className + '-description');
description.innerHTML = RichTextProcessor.wrapEmojiText(options.description); setInnerHTML(description, RichTextProcessor.wrapEmojiText(options.description));
div.append(description); div.append(description);
} }

51
src/components/chat/bubbles.ts

@ -16,6 +16,7 @@ import type { AppChatsManager } from "../../lib/appManagers/appChatsManager";
import type { AppProfileManager } from "../../lib/appManagers/appProfileManager"; import type { AppProfileManager } from "../../lib/appManagers/appProfileManager";
import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager"; import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager"; import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager";
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
import type Chat from "./chat"; import type Chat from "./chat";
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport"; import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport";
@ -98,9 +99,6 @@ import findAndSplice from "../../helpers/array/findAndSplice";
import getViewportSlice from "../../helpers/dom/getViewportSlice"; import getViewportSlice from "../../helpers/dom/getViewportSlice";
import SuperIntersectionObserver from "../../helpers/dom/superIntersectionObserver"; import SuperIntersectionObserver from "../../helpers/dom/superIntersectionObserver";
import generateFakeIcon from "../generateFakeIcon"; import generateFakeIcon from "../generateFakeIcon";
import selectElementContents from "../../helpers/dom/selectElementContents";
import cancelSelection from "../../helpers/dom/cancelSelection";
import SelectionSaver from "../../helpers/selectionSaver";
import copyFromElement from "../../helpers/dom/copyFromElement"; import copyFromElement from "../../helpers/dom/copyFromElement";
const USE_MEDIA_TAILS = false; const USE_MEDIA_TAILS = false;
@ -230,7 +228,8 @@ export default class ChatBubbles {
private appDraftsManager: AppDraftsManager, private appDraftsManager: AppDraftsManager,
private appMessagesIdsManager: AppMessagesIdsManager, private appMessagesIdsManager: AppMessagesIdsManager,
private appChatsManager: AppChatsManager, private appChatsManager: AppChatsManager,
private appReactionsManager: AppReactionsManager private appReactionsManager: AppReactionsManager,
private appWebPagesManager: AppWebPagesManager
) { ) {
//this.chat.log.error('Bubbles construction'); //this.chat.log.error('Bubbles construction');
@ -3133,7 +3132,7 @@ export default class ChatBubbles {
let attachmentDiv = document.createElement('div'); let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment'); attachmentDiv.classList.add('attachment');
attachmentDiv.innerHTML = richText; setInnerHTML(attachmentDiv, richText);
bubble.classList.add('emoji-' + emojiEntities.length + 'x'); bubble.classList.add('emoji-' + emojiEntities.length + 'x');
@ -3259,7 +3258,11 @@ export default class ChatBubbles {
} }
buttonEl.classList.add('reply-markup-button', 'rp'); buttonEl.classList.add('reply-markup-button', 'rp');
buttonEl.insertAdjacentHTML('beforeend', text); if(typeof(text) === 'string') {
buttonEl.insertAdjacentHTML('beforeend', text);
} else {
buttonEl.append(text);
}
ripple(buttonEl); ripple(buttonEl);
@ -3477,20 +3480,22 @@ export default class ChatBubbles {
t = a; t = a;
} }
if(webpage.rTitle) { const title = this.appWebPagesManager.wrapTitle(webpage);
if(title.textContent) {
let titleDiv = document.createElement('div'); let titleDiv = document.createElement('div');
titleDiv.classList.add('title'); titleDiv.classList.add('title');
const strong = document.createElement('strong'); const strong = document.createElement('strong');
setInnerHTML(strong, webpage.rTitle); setInnerHTML(strong, title);
titleDiv.append(strong); titleDiv.append(strong);
quoteTextDiv.append(titleDiv); quoteTextDiv.append(titleDiv);
t = titleDiv; t = titleDiv;
} }
if(webpage.rDescription) { const description = this.appWebPagesManager.wrapDescription(webpage);
if(description.textContent) {
let textDiv = document.createElement('div'); let textDiv = document.createElement('div');
textDiv.classList.add('text'); textDiv.classList.add('text');
setInnerHTML(textDiv, webpage.rDescription); setInnerHTML(textDiv, description);
quoteTextDiv.append(textDiv); quoteTextDiv.append(textDiv);
t = textDiv; t = textDiv;
} }
@ -3738,15 +3743,23 @@ export default class ChatBubbles {
processingWebPage = true; processingWebPage = true;
const texts = []; const contactDetails = document.createElement('div');
if(contact.first_name) texts.push(RichTextProcessor.wrapEmojiText(contact.first_name)); contactDetails.className = 'contact-details';
if(contact.last_name) texts.push(RichTextProcessor.wrapEmojiText(contact.last_name)); const contactNameDiv = document.createElement('div');
contactNameDiv.className = 'contact-name';
contactNameDiv.append(
RichTextProcessor.wrapEmojiText([
contact.first_name,
contact.last_name
].filter(Boolean).join(' '))
);
const contactNumberDiv = document.createElement('div');
contactNumberDiv.className = 'contact-number';
contactNumberDiv.textContent = contact.phone_number ? '+' + formatPhoneNumber(contact.phone_number).formatted : 'Unknown phone number';
contactDiv.innerHTML = ` contactDiv.append(contactDetails);
<div class="contact-details"> contactDetails.append(contactNameDiv, contactNumberDiv);
<div class="contact-name">${texts.join(' ')}</div>
<div class="contact-number">${contact.phone_number ? '+' + formatPhoneNumber(contact.phone_number).formatted : 'Unknown phone number'}</div>
</div>`;
const avatarElem = new AvatarElement(); const avatarElem = new AvatarElement();
avatarElem.updateWithOptions({ avatarElem.updateWithOptions({
@ -3823,7 +3836,7 @@ export default class ChatBubbles {
if(isHidden) { if(isHidden) {
///////this.log('message to render hidden', message); ///////this.log('message to render hidden', message);
title = document.createElement('span'); title = document.createElement('span');
title.innerHTML = RichTextProcessor.wrapEmojiText(fwdFrom.from_name); setInnerHTML(title, RichTextProcessor.wrapEmojiText(fwdFrom.from_name));
title.classList.add('peer-title'); title.classList.add('peer-title');
//title = fwdFrom.from_name; //title = fwdFrom.from_name;
bubble.classList.add('hidden-profile'); bubble.classList.add('hidden-profile');

2
src/components/chat/chat.ts

@ -323,7 +323,7 @@ export default class Chat extends EventListenerBase<{
// this.initPeerId = peerId; // this.initPeerId = peerId;
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager, this.appGroupCallsManager); this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager, this.appGroupCallsManager);
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager, this.appChatsManager, this.appReactionsManager); this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager, this.appChatsManager, this.appReactionsManager, this.appWebPagesManager);
this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager, this.appProfileManager); this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager, this.appProfileManager);
this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager); this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager, this.appReactionsManager); this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager, this.appReactionsManager);

11
src/components/chat/inlineHelper.ts

@ -25,6 +25,7 @@ import GifsMasonry from "../gifsMasonry";
import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers"; import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
import mediaSizes from "../../helpers/mediaSizes"; import mediaSizes from "../../helpers/mediaSizes";
import readBlobAsDataURL from "../../helpers/blob/readBlobAsDataURL"; import readBlobAsDataURL from "../../helpers/blob/readBlobAsDataURL";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
const ANIMATION_GROUP = 'INLINE-HELPER'; const ANIMATION_GROUP = 'INLINE-HELPER';
// const GRID_ITEMS = 5; // const GRID_ITEMS = 5;
@ -131,18 +132,18 @@ export default class InlineHelper extends AutocompleteHelper {
if(!isGallery) { if(!isGallery) {
preview.classList.add('empty'); preview.classList.add('empty');
preview.innerHTML = RichTextProcessor.wrapEmojiText([...item.title.trim()][0]); setInnerHTML(preview, RichTextProcessor.wrapEmojiText([...item.title.trim()][0]));
const title = document.createElement('div'); const title = document.createElement('div');
title.classList.add('inline-helper-result-title'); title.classList.add('inline-helper-result-title');
title.innerHTML = RichTextProcessor.wrapEmojiText(item.title); setInnerHTML(title, RichTextProcessor.wrapEmojiText(item.title));
const description = document.createElement('div'); const description = document.createElement('div');
description.classList.add('inline-helper-result-description'); description.classList.add('inline-helper-result-description');
description.innerHTML = RichTextProcessor.wrapRichText(item.description, { setInnerHTML(description, RichTextProcessor.wrapRichText(item.description, {
noCommands: true, noCommands: true,
noLinks: true noLinks: true
}); }));
container.append(title, description); container.append(title, description);
@ -239,7 +240,7 @@ export default class InlineHelper extends AutocompleteHelper {
parent.textContent = ''; parent.textContent = '';
if(botResults.switch_pm) { if(botResults.switch_pm) {
const btnSwitchToPM = Button('btn-primary btn-secondary btn-primary-transparent primary'); const btnSwitchToPM = Button('btn-primary btn-secondary btn-primary-transparent primary');
btnSwitchToPM.insertAdjacentHTML('beforeend', RichTextProcessor.wrapEmojiText(botResults.switch_pm.text)); setInnerHTML(btnSwitchToPM, RichTextProcessor.wrapEmojiText(botResults.switch_pm.text));
attachClickEvent(btnSwitchToPM, (e) => { attachClickEvent(btnSwitchToPM, (e) => {
this.appInlineBotsManager.switchToPM(peerId, peer.id, botResults.switch_pm.start_param); this.appInlineBotsManager.switchToPM(peerId, peer.id, botResults.switch_pm.start_param);
}); });

5
src/components/chat/input.ts

@ -93,6 +93,7 @@ import ChatBotCommands from './botCommands';
import copy from '../../helpers/object/copy'; import copy from '../../helpers/object/copy';
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice'; import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import toHHMMSS from '../../helpers/string/toHHMMSS'; import toHHMMSS from '../../helpers/string/toHHMMSS';
import documentFragmentToHTML from '../../helpers/dom/documentFragmentToHTML';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -2053,7 +2054,7 @@ export default class ChatInput {
//const saveExecuted = this.prepareDocumentExecute(); //const saveExecuted = this.prepareDocumentExecute();
// can't exec .value here because it will instantly check for autocomplete // can't exec .value here because it will instantly check for autocomplete
const value = RichTextProcessor.wrapDraftText(newValue, {entities}); const value = documentFragmentToHTML(RichTextProcessor.wrapDraftText(newValue, {entities}));
this.messageInputField.setValueSilently(value, true); this.messageInputField.setValueSilently(value, true);
const caret = this.messageInput.querySelector('.composer-sel'); const caret = this.messageInput.querySelector('.composer-sel');
@ -2615,7 +2616,7 @@ export default class ChatInput {
public initMessageEditing(mid: number) { public initMessageEditing(mid: number) {
const message: Message.message = this.chat.getMessage(mid); const message: Message.message = this.chat.getMessage(mid);
let input = RichTextProcessor.wrapDraftText(message.message, {entities: message.totalEntities}); let input = documentFragmentToHTML(RichTextProcessor.wrapDraftText(message.message, {entities: message.totalEntities}));
const f = () => { const f = () => {
const replyFragment = this.appMessagesManager.wrapMessageForReply(message, undefined, [message.mid]); const replyFragment = this.appMessagesManager.wrapMessageForReply(message, undefined, [message.mid]);
this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message); this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message);

4
src/components/chat/messageRender.ts

@ -5,6 +5,7 @@
*/ */
import { formatTime, getFullDate } from "../../helpers/date"; import { formatTime, getFullDate } from "../../helpers/date";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import formatNumber from "../../helpers/number/formatNumber"; import formatNumber from "../../helpers/number/formatNumber";
import { Message } from "../../layer"; import { Message } from "../../layer";
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
@ -62,7 +63,8 @@ export namespace MessageRender {
args.push(postViewsSpan, channelViews); args.push(postViewsSpan, channelViews);
if(postAuthor) { if(postAuthor) {
const span = document.createElement('span'); const span = document.createElement('span');
span.innerHTML = RichTextProcessor.wrapEmojiText(postAuthor) + ',' + NBSP; setInnerHTML(span, RichTextProcessor.wrapEmojiText(postAuthor));
span.insertAdjacentHTML('beforeend', ',' + NBSP)
args.push(span); args.push(span);
} }
} }

3
src/components/chat/replyKeyboard.ts

@ -18,6 +18,7 @@ import cancelEvent from "../../helpers/dom/cancelEvent";
import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck"; import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
import confirmationPopup from "../confirmationPopup"; import confirmationPopup from "../confirmationPopup";
import safeAssign from "../../helpers/object/safeAssign"; import safeAssign from "../../helpers/object/safeAssign";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
export default class ReplyKeyboard extends DropdownHover { export default class ReplyKeyboard extends DropdownHover {
private static BASE_CLASS = 'reply-keyboard'; private static BASE_CLASS = 'reply-keyboard';
@ -141,7 +142,7 @@ export default class ReplyKeyboard extends DropdownHover {
for(const button of row.buttons) { for(const button of row.buttons) {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.classList.add(ReplyKeyboard.BASE_CLASS + '-button', 'btn'); btn.classList.add(ReplyKeyboard.BASE_CLASS + '-button', 'btn');
btn.innerHTML = RichTextProcessor.wrapEmojiText(button.text); setInnerHTML(btn, RichTextProcessor.wrapEmojiText(button.text));
btn.dataset.text = button.text; btn.dataset.text = button.text;
btn.dataset.type = button._; btn.dataset.type = button._;
div.append(btn); div.append(btn);

4
src/components/emoticonsDropdown/tabs/emoji.ts

@ -32,7 +32,7 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
const spanEmoji = document.createElement('span'); const spanEmoji = document.createElement('span');
spanEmoji.classList.add('super-emoji'); spanEmoji.classList.add('super-emoji');
let kek: string; let kek: DocumentFragment;
if(unify && !IS_EMOJI_SUPPORTED) { if(unify && !IS_EMOJI_SUPPORTED) {
kek = RichTextProcessor.wrapSingleEmoji(emoji); kek = RichTextProcessor.wrapSingleEmoji(emoji);
} else { } else {
@ -47,7 +47,7 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
//console.log(kek); //console.log(kek);
spanEmoji.innerHTML = kek; spanEmoji.append(kek);
if(spanEmoji.children.length > 1) { if(spanEmoji.children.length > 1) {
const first = spanEmoji.firstElementChild; const first = spanEmoji.firstElementChild;

5
src/components/emoticonsDropdown/tabs/stickers.ts

@ -151,7 +151,7 @@ export default class StickersTab implements EmoticonsTab {
private superStickerRenderer: SuperStickerRenderer; private superStickerRenderer: SuperStickerRenderer;
categoryPush(categoryDiv: HTMLElement, categoryTitle: string = '', promise: Promise<MyDocument[]>, prepend?: boolean) { categoryPush(categoryDiv: HTMLElement, categoryTitle: DocumentFragment | string = '', promise: Promise<MyDocument[]>, prepend?: boolean) {
//if((docs.length % 5) !== 0) categoryDiv.classList.add('not-full'); //if((docs.length % 5) !== 0) categoryDiv.classList.add('not-full');
const itemsDiv = document.createElement('div'); const itemsDiv = document.createElement('div');
@ -161,7 +161,8 @@ export default class StickersTab implements EmoticonsTab {
titleDiv.classList.add('category-title'); titleDiv.classList.add('category-title');
if(categoryTitle) { if(categoryTitle) {
titleDiv.innerHTML = categoryTitle; if(typeof(categoryTitle) === 'string') titleDiv.innerHTML = categoryTitle;
else titleDiv.append(categoryTitle);
} }
categoryDiv.append(titleDiv, itemsDiv); categoryDiv.append(titleDiv, itemsDiv);

11
src/components/inputField.ts

@ -5,10 +5,12 @@
*/ */
import simulateEvent from "../helpers/dom/dispatchEvent"; import simulateEvent from "../helpers/dom/dispatchEvent";
import documentFragmentToHTML from "../helpers/dom/documentFragmentToHTML";
import findUpAttribute from "../helpers/dom/findUpAttribute"; import findUpAttribute from "../helpers/dom/findUpAttribute";
import getRichValue from "../helpers/dom/getRichValue"; import getRichValue from "../helpers/dom/getRichValue";
import isInputEmpty from "../helpers/dom/isInputEmpty"; import isInputEmpty from "../helpers/dom/isInputEmpty";
import selectElementContents from "../helpers/dom/selectElementContents"; import selectElementContents from "../helpers/dom/selectElementContents";
import setInnerHTML from "../helpers/dom/setInnerHTML";
import { MessageEntity } from "../layer"; import { MessageEntity } from "../layer";
import { i18n, LangPackKey, _i18n } from "../lib/langPack"; import { i18n, LangPackKey, _i18n } from "../lib/langPack";
import RichTextProcessor from "../lib/richtextprocessor"; import RichTextProcessor from "../lib/richtextprocessor";
@ -71,7 +73,8 @@ let init = () => {
entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak'); entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
} }
text = RichTextProcessor.wrapDraftText(text, {entities}); const fragment = RichTextProcessor.wrapDraftText(text, {entities});
text = documentFragmentToHTML(fragment);
window.document.execCommand('insertHTML', false, text); window.document.execCommand('insertHTML', false, text);
}); });
@ -106,7 +109,7 @@ export type InputFieldOptions = {
placeholder?: LangPackKey, placeholder?: LangPackKey,
label?: LangPackKey, label?: LangPackKey,
labelOptions?: any[], labelOptions?: any[],
labelText?: string, labelText?: string | DocumentFragment,
name?: string, name?: string,
maxLength?: number, maxLength?: number,
showLengthOn?: number, showLengthOn?: number,
@ -266,7 +269,7 @@ class InputField {
public setLabel() { public setLabel() {
this.label.textContent = ''; this.label.textContent = '';
if(this.options.labelText) { if(this.options.labelText) {
this.label.innerHTML = this.options.labelText; setInnerHTML(this.label, this.options.labelText);
} else { } else {
this.label.append(i18n(this.options.label, this.options.labelOptions)); this.label.append(i18n(this.options.label, this.options.labelOptions));
} }
@ -345,7 +348,7 @@ class InputField {
public setDraftValue(value = '', silent = false) { public setDraftValue(value = '', silent = false) {
if(!this.options.plainText) { if(!this.options.plainText) {
value = RichTextProcessor.wrapDraftText(value); value = documentFragmentToHTML(RichTextProcessor.wrapDraftText(value));
} }
if(silent) { if(silent) {

5
src/components/peerProfile.ts

@ -8,6 +8,7 @@ import IS_PARALLAX_SUPPORTED from "../environment/parallaxSupport";
import callbackify from "../helpers/callbackify"; import callbackify from "../helpers/callbackify";
import { copyTextToClipboard } from "../helpers/clipboard"; import { copyTextToClipboard } from "../helpers/clipboard";
import replaceContent from "../helpers/dom/replaceContent"; import replaceContent from "../helpers/dom/replaceContent";
import setInnerHTML from "../helpers/dom/setInnerHTML";
import ListenerSetter from "../helpers/listenerSetter"; import ListenerSetter from "../helpers/listenerSetter";
import { fastRaf } from "../helpers/schedulers"; import { fastRaf } from "../helpers/schedulers";
import { Chat, ChatFull, User } from "../layer"; import { Chat, ChatFull, User } from "../layer";
@ -31,9 +32,9 @@ import Scrollable from "./scrollable";
import { SettingSection, generateDelimiter } from "./sidebarLeft"; import { SettingSection, generateDelimiter } from "./sidebarLeft";
import { toast } from "./toast"; import { toast } from "./toast";
let setText = (text: string, row: Row) => { let setText = (text: Parameters<typeof setInnerHTML>[1], row: Row) => {
//fastRaf(() => { //fastRaf(() => {
row.title.innerHTML = text || ''; setInnerHTML(row.title, text || '');
row.container.style.display = text ? '' : 'none'; row.container.style.display = text ? '' : 'none';
//}); //});
}; };

5
src/components/peerTitle.ts

@ -13,6 +13,7 @@ import appUsersManager from "../lib/appManagers/appUsersManager";
import RichTextProcessor from "../lib/richtextprocessor"; import RichTextProcessor from "../lib/richtextprocessor";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config"; import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
import limitSymbols from "../helpers/string/limitSymbols"; import limitSymbols from "../helpers/string/limitSymbols";
import setInnerHTML from "../helpers/dom/setInnerHTML";
export type PeerTitleOptions = { export type PeerTitleOptions = {
peerId?: PeerId, peerId?: PeerId,
@ -73,7 +74,7 @@ export default class PeerTitle {
fromName = limitSymbols(fromName, this.limitSymbols, this.limitSymbols); fromName = limitSymbols(fromName, this.limitSymbols, this.limitSymbols);
} }
this.element.innerHTML = RichTextProcessor.wrapEmojiText(fromName); setInnerHTML(this.element, RichTextProcessor.wrapEmojiText(fromName));
return; return;
} }
@ -85,7 +86,7 @@ export default class PeerTitle {
if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) { if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName')); replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName'));
} else { } else {
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName, this.limitSymbols); setInnerHTML(this.element, appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName, this.limitSymbols));
} }
} else { } else {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages')); replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages'));

7
src/components/poll.ts

@ -25,6 +25,7 @@ import windowSize from "../helpers/windowSize";
import { Poll, PollResults } from "../layer"; import { Poll, PollResults } from "../layer";
import toHHMMSS from "../helpers/string/toHHMMSS"; import toHHMMSS from "../helpers/string/toHHMMSS";
import StackedAvatars from "./stackedAvatars"; import StackedAvatars from "./stackedAvatars";
import setInnerHTML from "../helpers/dom/setInnerHTML";
let lineTotalLength = 0; let lineTotalLength = 0;
const tailLength = 9; const tailLength = 9;
@ -152,7 +153,7 @@ const setQuizHint = (solution: string, solution_entities: any[], onHide: () => v
container.append(textEl); container.append(textEl);
element.append(container); element.append(container);
textEl.innerHTML = RichTextProcessor.wrapRichText(solution, {entities: solution_entities}); setInnerHTML(textEl, RichTextProcessor.wrapRichText(solution, {entities: solution_entities}));
appImManager.chat.bubbles.bubblesContainer.append(element); appImManager.chat.bubbles.bubblesContainer.append(element);
void element.offsetLeft; // reflow void element.offsetLeft; // reflow
@ -283,13 +284,15 @@ export default class PollElement extends HTMLElement {
}).join(''); }).join('');
this.innerHTML = ` this.innerHTML = `
<div class="poll-title">${poll.rQuestion}</div> <div class="poll-title"></div>
<div class="poll-desc"> <div class="poll-desc">
<div class="poll-type"></div> <div class="poll-type"></div>
<div class="poll-avatars"></div> <div class="poll-avatars"></div>
</div> </div>
${votes}`; ${votes}`;
setInnerHTML(this.firstElementChild, RichTextProcessor.wrapEmojiText(poll.question));
this.descDiv = this.firstElementChild.nextElementSibling as HTMLElement; this.descDiv = this.firstElementChild.nextElementSibling as HTMLElement;
this.typeDiv = this.descDiv.firstElementChild as HTMLElement; this.typeDiv = this.descDiv.firstElementChild as HTMLElement;
this.avatarsDiv = this.descDiv.lastElementChild as HTMLElement; this.avatarsDiv = this.descDiv.lastElementChild as HTMLElement;

3
src/components/popups/joinChatInvite.ts

@ -5,6 +5,7 @@
*/ */
import PopupElement, { addCancelButton } from "."; import PopupElement, { addCancelButton } from ".";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import numberThousandSplitter from "../../helpers/number/numberThousandSplitter"; import numberThousandSplitter from "../../helpers/number/numberThousandSplitter";
import { ChatInvite, Updates } from "../../layer"; import { ChatInvite, Updates } from "../../layer";
import apiUpdatesManager from "../../lib/appManagers/apiUpdatesManager"; import apiUpdatesManager from "../../lib/appManagers/apiUpdatesManager";
@ -75,7 +76,7 @@ export default class PopupJoinChatInvite extends PopupElement {
const title = document.createElement('div'); const title = document.createElement('div');
title.classList.add('chat-title'); title.classList.add('chat-title');
title.innerHTML = RichTextProcessor.wrapEmojiText(chatInvite.title); setInnerHTML(title, RichTextProcessor.wrapEmojiText(chatInvite.title));
//avatarElem.setAttribute('peer', '' + -fakeChat.id); //avatarElem.setAttribute('peer', '' + -fakeChat.id);
const isBroadcast = chatInvite.pFlags.broadcast; const isBroadcast = chatInvite.pFlags.broadcast;

2
src/components/popups/newMedia.ts

@ -19,7 +19,6 @@ import appDownloadManager from "../../lib/appManagers/appDownloadManager";
import calcImageInBox from "../../helpers/calcImageInBox"; import calcImageInBox from "../../helpers/calcImageInBox";
import placeCaretAtEnd from "../../helpers/dom/placeCaretAtEnd"; import placeCaretAtEnd from "../../helpers/dom/placeCaretAtEnd";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import RichTextProcessor from "../../lib/richtextprocessor";
import { MediaSize } from "../../helpers/mediaSizes"; import { MediaSize } from "../../helpers/mediaSizes";
import { attachClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent";
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport'; import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
@ -358,7 +357,6 @@ export default class PopupNewMedia extends PopupElement {
_: 'document', _: 'document',
file: file, file: file,
file_name: file.name || '', file_name: file.name || '',
fileName: file.name ? RichTextProcessor.wrapEmojiText(file.name) : '',
size: file.size, size: file.size,
type: isPhoto ? 'photo' : 'doc' type: isPhoto ? 'photo' : 'doc'
} as MyDocument; } as MyDocument;

5
src/components/popups/peer.ts

@ -8,6 +8,7 @@ import AvatarElement from "../avatar";
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from "."; import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
import { i18n, LangPackKey } from "../../lib/langPack"; import { i18n, LangPackKey } from "../../lib/langPack";
import CheckboxField, { CheckboxFieldOptions } from "../checkboxField"; import CheckboxField, { CheckboxFieldOptions } from "../checkboxField";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
export type PopupPeerButton = Omit<PopupButton, 'callback'> & Partial<{callback: PopupPeerButtonCallback}>; export type PopupPeerButton = Omit<PopupButton, 'callback'> & Partial<{callback: PopupPeerButtonCallback}>;
export type PopupPeerButtonCallbackCheckboxes = Set<LangPackKey>; export type PopupPeerButtonCallbackCheckboxes = Set<LangPackKey>;
@ -20,7 +21,7 @@ export type PopupPeerOptions = PopupOptions & Partial<{
titleLangKey?: LangPackKey, titleLangKey?: LangPackKey,
titleLangArgs?: any[], titleLangArgs?: any[],
noTitle?: boolean, noTitle?: boolean,
description: string, description: string | DocumentFragment,
descriptionLangKey?: LangPackKey, descriptionLangKey?: LangPackKey,
descriptionLangArgs?: any[], descriptionLangArgs?: any[],
buttons?: Array<PopupPeerButton>, buttons?: Array<PopupPeerButton>,
@ -55,7 +56,7 @@ export default class PopupPeer extends PopupElement {
const p = this.description = document.createElement('p'); const p = this.description = document.createElement('p');
p.classList.add('popup-description'); p.classList.add('popup-description');
if(options.descriptionLangKey) p.append(i18n(options.descriptionLangKey, options.descriptionLangArgs)); if(options.descriptionLangKey) p.append(i18n(options.descriptionLangKey, options.descriptionLangArgs));
else if(options.description) p.innerHTML = options.description; else if(options.description) setInnerHTML(p, options.description);
fragment.append(p); fragment.append(p);
} }

3
src/components/popups/stickers.ts

@ -21,6 +21,7 @@ import findUpClassName from "../../helpers/dom/findUpClassName";
import toggleDisability from "../../helpers/dom/toggleDisability"; import toggleDisability from "../../helpers/dom/toggleDisability";
import { attachClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent";
import { toastNew } from "../toast"; import { toastNew } from "../toast";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
const ANIMATION_GROUP = 'STICKERS-POPUP'; const ANIMATION_GROUP = 'STICKERS-POPUP';
@ -98,7 +99,7 @@ export default class PopupStickers extends PopupElement {
animationIntersector.setOnlyOnePlayableGroup(ANIMATION_GROUP); animationIntersector.setOnlyOnePlayableGroup(ANIMATION_GROUP);
this.h6.innerHTML = RichTextProcessor.wrapEmojiText(set.set.title); setInnerHTML(this.h6, RichTextProcessor.wrapEmojiText(set.set.title));
this.stickersFooter.classList.toggle('add', !set.set.installed_date); this.stickersFooter.classList.toggle('add', !set.set.installed_date);
let button: HTMLElement; let button: HTMLElement;

2
src/components/row.ts

@ -33,7 +33,7 @@ export default class Row {
radioField: Row['radioField'], radioField: Row['radioField'],
checkboxField: Row['checkboxField'], checkboxField: Row['checkboxField'],
noCheckboxSubtitle: boolean, noCheckboxSubtitle: boolean,
title: string | HTMLElement, title: string | HTMLElement | DocumentFragment,
titleLangKey: LangPackKey, titleLangKey: LangPackKey,
titleRight: string | HTMLElement, titleRight: string | HTMLElement,
titleRightSecondary: string | HTMLElement, titleRightSecondary: string | HTMLElement,

3
src/components/sidebarLeft/tabs/2fa/enterPassword.ts

@ -10,6 +10,7 @@ import cancelEvent from "../../../../helpers/dom/cancelEvent";
import { canFocus } from "../../../../helpers/dom/canFocus"; import { canFocus } from "../../../../helpers/dom/canFocus";
import { attachClickEvent } from "../../../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../../../helpers/dom/clickEvent";
import replaceContent from "../../../../helpers/dom/replaceContent"; import replaceContent from "../../../../helpers/dom/replaceContent";
import setInnerHTML from "../../../../helpers/dom/setInnerHTML";
import { AccountPassword } from "../../../../layer"; import { AccountPassword } from "../../../../layer";
import I18n, { i18n } from "../../../../lib/langPack"; import I18n, { i18n } from "../../../../lib/langPack";
import passwordManager from "../../../../lib/mtproto/passwordManager"; import passwordManager from "../../../../lib/mtproto/passwordManager";
@ -92,7 +93,7 @@ export default class AppTwoStepVerificationEnterPasswordTab extends SliderSuperT
this.state = _state; this.state = _state;
if(this.state.hint) { if(this.state.hint) {
passwordInputField.label.innerHTML = RichTextProcessor.wrapEmojiText(this.state.hint); setInnerHTML(passwordInputField.label, RichTextProcessor.wrapEmojiText(this.state.hint));
} else { } else {
replaceContent(passwordInputField.label, i18n('LoginPassword')); replaceContent(passwordInputField.label, i18n('LoginPassword'));
} }

3
src/components/sidebarLeft/tabs/editFolder.ts

@ -25,6 +25,7 @@ import copy from "../../../helpers/object/copy";
import deepEqual from "../../../helpers/object/deepEqual"; import deepEqual from "../../../helpers/object/deepEqual";
import appUsersManager from "../../../lib/appManagers/appUsersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager";
import forEachReverse from "../../../helpers/array/forEachReverse"; import forEachReverse from "../../../helpers/array/forEachReverse";
import documentFragmentToHTML from "../../../helpers/dom/documentFragmentToHTML";
const MAX_FOLDER_NAME_LENGTH = 12; const MAX_FOLDER_NAME_LENGTH = 12;
@ -283,7 +284,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
} }
const filter = this.filter; const filter = this.filter;
this.nameInputField.value = RichTextProcessor.wrapDraftText(filter.title); this.nameInputField.value = documentFragmentToHTML(RichTextProcessor.wrapDraftText(filter.title));
for(const flag in this.flags) { for(const flag in this.flags) {
this.flags[flag as keyof AppEditFolderTab['flags']].style.display = !!filter.pFlags[flag as keyof AppEditFolderTab['flags']] ? '' : 'none'; this.flags[flag as keyof AppEditFolderTab['flags']].style.display = !!filter.pFlags[flag as keyof AppEditFolderTab['flags']] ? '' : 'none';

3
src/components/sidebarLeft/tabs/includedChats.ts

@ -21,6 +21,7 @@ import { toast } from "../../toast";
import appPeersManager from "../../../lib/appManagers/appPeersManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager";
import copy from "../../../helpers/object/copy"; import copy from "../../../helpers/object/copy";
import forEachReverse from "../../../helpers/array/forEachReverse"; import forEachReverse from "../../../helpers/array/forEachReverse";
import setInnerHTML from "../../../helpers/dom/setInnerHTML";
export default class AppIncludedChatsTab extends SliderSuperTab { export default class AppIncludedChatsTab extends SliderSuperTab {
private editFolderTab: AppEditFolderTab; private editFolderTab: AppEditFolderTab;
@ -149,7 +150,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.dialogsByFilters.forEach((dialogs, filter) => { this.dialogsByFilters.forEach((dialogs, filter) => {
if(dialogs.has(peerId)) { if(dialogs.has(peerId)) {
const span = document.createElement('span'); const span = document.createElement('span');
span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); setInnerHTML(span, RichTextProcessor.wrapEmojiText(filter.title));
foundInFilters.push(span); foundInFilters.push(span);
} }
}); });

5
src/components/sidebarRight/tabs/pollResults.ts

@ -12,6 +12,7 @@ import { RichTextProcessor } from "../../../lib/richtextprocessor";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import ripple from "../../ripple"; import ripple from "../../ripple";
import { i18n } from "../../../lib/langPack"; import { i18n } from "../../../lib/langPack";
import setInnerHTML from "../../../helpers/dom/setInnerHTML";
export default class AppPollResultsTab extends SliderSuperTab { export default class AppPollResultsTab extends SliderSuperTab {
private resultsDiv: HTMLElement; private resultsDiv: HTMLElement;
@ -32,7 +33,7 @@ export default class AppPollResultsTab extends SliderSuperTab {
this.setTitle(poll.poll.pFlags.quiz ? 'PollResults.Title.Quiz' : 'PollResults.Title.Poll'); this.setTitle(poll.poll.pFlags.quiz ? 'PollResults.Title.Quiz' : 'PollResults.Title.Poll');
const title = document.createElement('h3'); const title = document.createElement('h3');
title.innerHTML = poll.poll.rQuestion; setInnerHTML(title, RichTextProcessor.wrapEmojiText(poll.poll.question));
const percents = poll.results.results.map(v => v.voters / poll.results.total_voters * 100); const percents = poll.results.results.map(v => v.voters / poll.results.total_voters * 100);
roundPercents(percents); roundPercents(percents);
@ -50,7 +51,7 @@ export default class AppPollResultsTab extends SliderSuperTab {
answerEl.classList.add('poll-results-answer'); answerEl.classList.add('poll-results-answer');
const answerTitle = document.createElement('div'); const answerTitle = document.createElement('div');
answerTitle.innerHTML = RichTextProcessor.wrapEmojiText(answer.text); setInnerHTML(answerTitle, RichTextProcessor.wrapEmojiText(answer.text));
const answerPercents = document.createElement('div'); const answerPercents = document.createElement('div');
answerPercents.innerText = Math.round(percents[idx]) + '%'; answerPercents.innerText = Math.round(percents[idx]) + '%';

8
src/components/wrappers.ts

@ -59,6 +59,7 @@ import { ChatAutoDownloadSettings } from '../helpers/autoDownload';
import formatBytes from '../helpers/formatBytes'; import formatBytes from '../helpers/formatBytes';
import toHHMMSS from '../helpers/string/toHHMMSS'; import toHHMMSS from '../helpers/string/toHHMMSS';
import createVideo from '../helpers/dom/createVideo'; import createVideo from '../helpers/dom/createVideo';
import setInnerHTML from '../helpers/dom/setInnerHTML';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -652,7 +653,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
} }
//let fileName = stringMiddleOverflow(doc.file_name || 'Unknown.file', 26); //let fileName = stringMiddleOverflow(doc.file_name || 'Unknown.file', 26);
let fileName = doc.fileName || 'Unknown.file'; let fileName = doc.file_name ? RichTextProcessor.wrapPlainText(doc.file_name) : 'Unknown.file';
const descriptionEl = document.createElement('div'); const descriptionEl = document.createElement('div');
descriptionEl.classList.add('document-description'); descriptionEl.classList.add('document-description');
const descriptionParts: (HTMLElement | string | DocumentFragment)[] = [formatBytes(doc.size)]; const descriptionParts: (HTMLElement | string | DocumentFragment)[] = [formatBytes(doc.size)];
@ -675,7 +676,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
const middleEllipsisEl = new MiddleEllipsisElement(); const middleEllipsisEl = new MiddleEllipsisElement();
middleEllipsisEl.dataset.fontWeight = '' + fontWeight; middleEllipsisEl.dataset.fontWeight = '' + fontWeight;
middleEllipsisEl.dataset.sizeType = sizeType; middleEllipsisEl.dataset.sizeType = sizeType;
middleEllipsisEl.innerHTML = fileName; middleEllipsisEl.textContent = fileName;
// setInnerHTML(middleEllipsisEl, fileName);
nameDiv.append(middleEllipsisEl); nameDiv.append(middleEllipsisEl);
@ -2082,7 +2084,7 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
entities: message.totalEntities entities: message.totalEntities
}); });
messageDiv.innerHTML = richText; setInnerHTML(messageDiv, richText);
wrapper.append(messageDiv); wrapper.append(messageDiv);
} }

5
src/helpers/dom/documentFragmentToHTML.ts

@ -0,0 +1,5 @@
export default function documentFragmentToHTML(fragment: DocumentFragment) {
return Array.from(fragment.childNodes).map((node) => {
return node.nodeType === 3 ? node.textContent : (node as Element).outerHTML + '\n';
}).join('');
}

5
src/helpers/dom/htmlToDocumentFragment.ts

@ -4,8 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
export default function htmlToDocumentFragment(html: string) { export default function htmlToDocumentFragment(html: string | DocumentFragment) {
var template = document.createElement('template'); if(html instanceof DocumentFragment) return html;
const template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html; template.innerHTML = html;
return template.content; return template.content;

5
src/helpers/dom/htmlToSpan.ts

@ -4,8 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
export default function htmlToSpan(html: string) { export default function htmlToSpan(html: string | DocumentFragment) {
const span = document.createElement('span'); const span = document.createElement('span');
span.innerHTML = html; if(typeof(html) === 'string') span.innerHTML = html;
else span.append(html);
return span; return span;
} }

9
src/helpers/dom/setInnerHTML.ts

@ -4,7 +4,12 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
export default function setInnerHTML(elem: Element, html: string) { export default function setInnerHTML(elem: Element, html: string | DocumentFragment) {
elem.setAttribute('dir', 'auto'); elem.setAttribute('dir', 'auto');
elem.innerHTML = html; if(typeof(html) === 'string') {
elem.innerHTML = html;
} else {
elem.textContent = '';
elem.append(html);
}
} }

23
src/layer.d.ts vendored

@ -511,7 +511,6 @@ export namespace User {
restriction_reason?: Array<RestrictionReason>, restriction_reason?: Array<RestrictionReason>,
bot_inline_placeholder?: string, bot_inline_placeholder?: string,
lang_code?: string, lang_code?: string,
initials?: string,
sortName?: string sortName?: string
}; };
} }
@ -601,15 +600,13 @@ export namespace Chat {
version: number, version: number,
migrated_to?: InputChannel, migrated_to?: InputChannel,
admin_rights?: ChatAdminRights, admin_rights?: ChatAdminRights,
default_banned_rights?: ChatBannedRights, default_banned_rights?: ChatBannedRights
initials?: string
}; };
export type chatForbidden = { export type chatForbidden = {
_: 'chatForbidden', _: 'chatForbidden',
id: string | number, id: string | number,
title: string, title: string
initials?: string
}; };
export type channel = { export type channel = {
@ -646,8 +643,7 @@ export namespace Chat {
admin_rights?: ChatAdminRights, admin_rights?: ChatAdminRights,
banned_rights?: ChatBannedRights, banned_rights?: ChatBannedRights,
default_banned_rights?: ChatBannedRights, default_banned_rights?: ChatBannedRights,
participants_count?: number, participants_count?: number
initials?: string
}; };
export type channelForbidden = { export type channelForbidden = {
@ -660,8 +656,7 @@ export namespace Chat {
id: string | number, id: string | number,
access_hash: string | number, access_hash: string | number,
title: string, title: string,
until_date?: number, until_date?: number
initials?: string
}; };
} }
@ -3281,13 +3276,9 @@ export namespace Document {
h?: number, h?: number,
w?: number, w?: number,
file_name?: string, file_name?: string,
fileName?: string,
file?: File, file?: File,
duration?: number, duration?: number,
audioTitle?: string,
audioPerformer?: string,
sticker?: 1 | 2 | 3, sticker?: 1 | 2 | 3,
stickerEmoji?: string,
stickerEmojiRaw?: string, stickerEmojiRaw?: string,
stickerSetInput?: InputStickerSet.inputStickerSetID, stickerSetInput?: InputStickerSet.inputStickerSetID,
pFlags?: Partial<{ pFlags?: Partial<{
@ -3788,9 +3779,7 @@ export namespace WebPage {
author?: string, author?: string,
document?: Document, document?: Document,
cached_page?: Page, cached_page?: Page,
attributes?: Array<WebPageAttribute>, attributes?: Array<WebPageAttribute>
rTitle?: string,
rDescription?: string
}; };
export type webPageNotModified = { export type webPageNotModified = {
@ -7632,8 +7621,6 @@ export namespace Poll {
answers: Array<PollAnswer>, answers: Array<PollAnswer>,
close_period?: number, close_period?: number,
close_date?: number, close_date?: number,
rQuestion?: string,
rReply?: string,
chosenIndexes?: number[] chosenIndexes?: number[]
}; };
} }

14
src/lib/appManagers/appAvatarsManager.ts

@ -7,6 +7,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import replaceContent from "../../helpers/dom/replaceContent"; import replaceContent from "../../helpers/dom/replaceContent";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import sequentialDom from "../../helpers/sequentialDom"; import sequentialDom from "../../helpers/sequentialDom";
import { UserProfilePhoto, ChatPhoto, InputFileLocation } from "../../layer"; import { UserProfilePhoto, ChatPhoto, InputFileLocation } from "../../layer";
import { DownloadOptions } from "../mtproto/apiFileManager"; import { DownloadOptions } from "../mtproto/apiFileManager";
@ -163,8 +164,8 @@ export class AppAvatarsManager {
}; };
} }
public s(div: HTMLElement, innerHTML: string, color: string, icon: string) { public s(div: HTMLElement, innerHTML: Parameters<typeof setInnerHTML>[1], color: string, icon: string) {
div.innerHTML = innerHTML; setInnerHTML(div, innerHTML);
div.dataset.color = color; div.dataset.color = color;
div.classList.remove('tgico-saved', 'tgico-deletedaccount', 'tgico-reply_filled'); div.classList.remove('tgico-saved', 'tgico-deletedaccount', 'tgico-reply_filled');
icon && div.classList.add(icon); icon && div.classList.add(icon);
@ -202,14 +203,7 @@ export class AppAvatarsManager {
return; return;
} }
let abbr: string; const abbr = title ? RichTextProcessor.getAbbreviation(title) : appPeersManager.getPeerInitials(peerId);
if(!title) {
const peer = appPeersManager.getPeer(peerId);
abbr = peer.initials ?? '';
} else {
abbr = RichTextProcessor.getAbbreviation(title);
}
this.s(div, abbr, color, ''); this.s(div, abbr, color, '');
//return Promise.resolve(true); //return Promise.resolve(true);
} }

2
src/lib/appManagers/appChatsManager.ts

@ -144,8 +144,6 @@ export class AppChatsManager {
return; return;
} }
chat.initials = RichTextProcessor.getAbbreviation(chat.title);
if(chat._ === 'channel' && if(chat._ === 'channel' &&
chat.participants_count === undefined && chat.participants_count === undefined &&
oldChat !== undefined && oldChat !== undefined &&

5
src/lib/appManagers/appDialogsManager.ts

@ -62,6 +62,7 @@ import appNavigationController, { NavigationItem } from "../../components/appNav
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import generateTitleIcons from "../../components/generateTitleIcons"; import generateTitleIcons from "../../components/generateTitleIcons";
import appMediaPlaybackController from "../../components/appMediaPlaybackController"; import appMediaPlaybackController from "../../components/appMediaPlaybackController";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
export type DialogDom = { export type DialogDom = {
avatarEl: AvatarElement, avatarEl: AvatarElement,
@ -517,7 +518,7 @@ export class AppDialogsManager {
} }
const elements = this.filtersRendered[filter.id]; const elements = this.filtersRendered[filter.id];
elements.title.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); setInnerHTML(elements.title, RichTextProcessor.wrapEmojiText(filter.title));
}); });
rootScope.addEventListener('filter_delete', (filter) => { rootScope.addEventListener('filter_delete', (filter) => {
@ -779,7 +780,7 @@ export class AppDialogsManager {
const titleSpan = document.createElement('span'); const titleSpan = document.createElement('span');
titleSpan.classList.add('text-super'); titleSpan.classList.add('text-super');
if(filter.titleEl) titleSpan.append(filter.titleEl); if(filter.titleEl) titleSpan.append(filter.titleEl);
else titleSpan.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); else setInnerHTML(titleSpan, RichTextProcessor.wrapEmojiText(filter.title));
const unreadSpan = document.createElement('div'); const unreadSpan = document.createElement('div');
unreadSpan.classList.add('badge', 'badge-20', 'badge-primary'); unreadSpan.classList.add('badge', 'badge-20', 'badge-primary');
const i = document.createElement('i'); const i = document.createElement('i');

8
src/lib/appManagers/appDocsManager.ts

@ -105,13 +105,10 @@ export class AppDocsManager {
switch(attribute._) { switch(attribute._) {
case 'documentAttributeFilename': case 'documentAttributeFilename':
doc.file_name = RichTextProcessor.wrapPlainText(attribute.file_name); doc.file_name = RichTextProcessor.wrapPlainText(attribute.file_name);
doc.fileName = RichTextProcessor.wrapEmojiText(attribute.file_name);
break; break;
case 'documentAttributeAudio': case 'documentAttributeAudio':
doc.duration = attribute.duration; doc.duration = attribute.duration;
doc.audioTitle = RichTextProcessor.wrapEmojiText(attribute.title);
doc.audioPerformer = RichTextProcessor.wrapEmojiText(attribute.performer);
doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio'; doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio';
/* if(apiDoc.type === 'audio') { /* if(apiDoc.type === 'audio') {
apiDoc.supportsStreaming = true; apiDoc.supportsStreaming = true;
@ -133,7 +130,6 @@ export class AppDocsManager {
case 'documentAttributeSticker': case 'documentAttributeSticker':
if(attribute.alt !== undefined) { if(attribute.alt !== undefined) {
doc.stickerEmojiRaw = attribute.alt; doc.stickerEmojiRaw = attribute.alt;
doc.stickerEmoji = RichTextProcessor.wrapRichText(doc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
} }
if(attribute.stickerset) { if(attribute.stickerset) {
@ -210,7 +206,7 @@ export class AppDocsManager {
if(doc.type === 'voice' || doc.type === 'round') { if(doc.type === 'voice' || doc.type === 'round') {
// browser will identify extension // browser will identify extension
doc.file_name = doc.fileName = doc.type + '_' + getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_'); doc.file_name = doc.type + '_' + getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_');
} }
if(apiManager.isServiceWorkerOnline()) { if(apiManager.isServiceWorkerOnline()) {
@ -229,7 +225,7 @@ export class AppDocsManager {
// doc.url = ''; // * this will break upload urls // doc.url = ''; // * this will break upload urls
if(!doc.file_name) { if(!doc.file_name) {
doc.file_name = doc.fileName = ''; doc.file_name = '';
} }
if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') { if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') {

3
src/lib/appManagers/appDraftsManager.ts

@ -24,6 +24,7 @@ import appMessagesIdsManager from "./appMessagesIdsManager";
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import isObject from "../../helpers/object/isObject"; import isObject from "../../helpers/object/isObject";
import deepEqual from "../../helpers/object/deepEqual"; import deepEqual from "../../helpers/object/deepEqual";
import documentFragmentToHTML from "../../helpers/dom/documentFragmentToHTML";
export type MyDraftMessage = DraftMessage.draftMessage; export type MyDraftMessage = DraftMessage.draftMessage;
@ -174,7 +175,7 @@ export class AppDraftsManager {
const apiEntities = draft.entities || []; const apiEntities = draft.entities || [];
const totalEntities = RichTextProcessor.mergeEntities(apiEntities.slice(), myEntities); // ! only in this order, otherwise bold and emoji formatting won't work const totalEntities = RichTextProcessor.mergeEntities(apiEntities.slice(), myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
draft.rMessage = RichTextProcessor.wrapDraftText(draft.message, {entities: totalEntities}); draft.rMessage = documentFragmentToHTML(RichTextProcessor.wrapDraftText(draft.message, {entities: totalEntities}));
//draft.rReply = appMessagesManager.getRichReplyText(draft); //draft.rReply = appMessagesManager.getRichReplyText(draft);
if(draft.reply_to_msg_id) { if(draft.reply_to_msg_id) {
draft.reply_to_msg_id = appMessagesIdsManager.generateMessageId(draft.reply_to_msg_id); draft.reply_to_msg_id = appMessagesIdsManager.generateMessageId(draft.reply_to_msg_id);

13
src/lib/appManagers/appMessagesManager.ts

@ -71,6 +71,7 @@ import escapeRegExp from "../../helpers/string/escapeRegExp";
import limitSymbols from "../../helpers/string/limitSymbols"; import limitSymbols from "../../helpers/string/limitSymbols";
import splitStringByLength from "../../helpers/string/splitStringByLength"; import splitStringByLength from "../../helpers/string/splitStringByLength";
import debounce from "../../helpers/schedulers/debounce"; import debounce from "../../helpers/schedulers/debounce";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
//console.trace('include'); //console.trace('include');
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках // TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -2905,7 +2906,7 @@ export class AppMessagesManager {
const parts: (Node | string)[] = []; const parts: (Node | string)[] = [];
let hasAlbumKey = false; let hasAlbumKey = false;
const addPart = (langKey: LangPackKey, part?: string | HTMLElement) => { const addPart = (langKey: LangPackKey, part?: string | HTMLElement | DocumentFragment) => {
if(langKey) { if(langKey) {
if(part === undefined && hasAlbumKey) { if(part === undefined && hasAlbumKey) {
return; return;
@ -2980,7 +2981,8 @@ export class AppMessagesManager {
addPart('AttachLiveLocation'); addPart('AttachLiveLocation');
break; break;
case 'messageMediaPoll': case 'messageMediaPoll':
addPart(undefined, plain ? '📊' + ' ' + (media.poll.question || 'poll') : media.poll.rReply); const f = '📊' + ' ' + (media.poll.question || 'poll');
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f));
break; break;
case 'messageMediaContact': case 'messageMediaContact':
addPart('AttachContact'); addPart('AttachContact');
@ -3004,7 +3006,8 @@ export class AppMessagesManager {
} else if(document.type === 'sticker') { } else if(document.type === 'sticker') {
const i = parts.length; const i = parts.length;
if(document.stickerEmojiRaw) { if(document.stickerEmojiRaw) {
addPart(undefined, (plain ? document.stickerEmojiRaw : document.stickerEmoji) + ' '); const f = document.stickerEmojiRaw + ' ';
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f));
} }
addPart('AttachSticker'); addPart('AttachSticker');
@ -3099,7 +3102,7 @@ export class AppMessagesManager {
noTextFormat: true noTextFormat: true
}); });
parts.push(htmlToDocumentFragment(messageWrapped) as any); parts.push(htmlToDocumentFragment(messageWrapped));
} }
} }
@ -3179,7 +3182,7 @@ export class AppMessagesManager {
if(plain) { if(plain) {
return RichTextProcessor.wrapPlainText(unsafeMessage); return RichTextProcessor.wrapPlainText(unsafeMessage);
} else { } else {
element.innerHTML = RichTextProcessor.wrapRichText(unsafeMessage, {noLinebreaks: true}); setInnerHTML(element, RichTextProcessor.wrapRichText(unsafeMessage, {noLinebreaks: true}));
return element; return element;
} }
} else { } else {

12
src/lib/appManagers/appPeersManager.ts

@ -74,7 +74,10 @@ export class AppPeersManager {
return false; return false;
} }
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false, _limitSymbols?: number) { public getPeerTitle(peerId: PeerId, plainText: true, onlyFirstName?: boolean, _limitSymbols?: number): string;
public getPeerTitle(peerId: PeerId, plainText?: false, onlyFirstName?: boolean, _limitSymbols?: number): DocumentFragment;
public getPeerTitle(peerId: PeerId, plainText: boolean, onlyFirstName?: boolean, _limitSymbols?: number): DocumentFragment | string;
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false, _limitSymbols?: number): DocumentFragment | string {
if(!peerId) { if(!peerId) {
peerId = rootScope.myId; peerId = rootScope.myId;
} }
@ -133,6 +136,13 @@ export class AppPeersManager {
: appChatsManager.getChat(peerId.toChatId()); : appChatsManager.getChat(peerId.toChatId());
} }
public getPeerInitials(peerId: PeerId) {
const peer: Chat | User = this.getPeer(peerId);
return RichTextProcessor.getAbbreviation(
(peer as Chat.chat).title ?? [(peer as User.user).first_name, (peer as User.user).last_name].filter(Boolean).join(' ')
);
}
public getPeerId(peerId: {user_id: UserId} | {channel_id: ChatId} | {chat_id: ChatId} | InputPeer | PeerId | string): PeerId { public getPeerId(peerId: {user_id: UserId} | {channel_id: ChatId} | {chat_id: ChatId} | InputPeer | PeerId | string): PeerId {
if(peerId !== undefined && ((peerId as string).isPeerId ? (peerId as string).isPeerId() : false)) return peerId as PeerId; if(peerId !== undefined && ((peerId as string).isPeerId ? (peerId as string).isPeerId() : false)) return peerId as PeerId;
// if(typeof(peerId) === 'string' && /^[uc]/.test(peerId)) return peerId as PeerId; // if(typeof(peerId) === 'string' && /^[uc]/.test(peerId)) return peerId as PeerId;

2
src/lib/appManagers/appPollsManager.ts

@ -56,8 +56,6 @@ export class AppPollsManager {
} else { } else {
this.polls[id] = poll; this.polls[id] = poll;
poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question);
poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll');
poll.chosenIndexes = []; poll.chosenIndexes = [];
results = this.saveResults(poll, results); results = this.saveResults(poll, results);
} }

3
src/lib/appManagers/appUsersManager.ts

@ -431,17 +431,14 @@ export class AppUsersManager {
this.setUserNameToCache(user, oldUser); this.setUserNameToCache(user, oldUser);
if(!oldUser if(!oldUser
|| oldUser.initials === undefined
|| oldUser.sortName === undefined || oldUser.sortName === undefined
|| oldUser.first_name !== user.first_name || oldUser.first_name !== user.first_name
|| oldUser.last_name !== user.last_name) { || oldUser.last_name !== user.last_name) {
const fullName = user.first_name + (user.last_name ? ' ' + user.last_name : ''); const fullName = user.first_name + (user.last_name ? ' ' + user.last_name : '');
user.sortName = user.pFlags.deleted ? '' : cleanSearchText(fullName, false); user.sortName = user.pFlags.deleted ? '' : cleanSearchText(fullName, false);
user.initials = RichTextProcessor.getAbbreviation(fullName);
} else { } else {
user.sortName = oldUser.sortName; user.sortName = oldUser.sortName;
user.initials = oldUser.initials;
} }
if(user.status) { if(user.status) {

38
src/lib/appManagers/appWebPagesManager.ts

@ -71,23 +71,7 @@ export class AppWebPagesManager {
delete apiWebPage.site_name; delete apiWebPage.site_name;
} }
shortTitle = limitSymbols(shortTitle, 80, 100);
apiWebPage.rTitle = RichTextProcessor.wrapRichText(shortTitle, {noLinks: true, noLinebreaks: true});
let contextHashtag = '';
if(siteName === 'GitHub') {
const matches = apiWebPage.url.match(/(https?:\/\/github\.com\/[^\/]+\/[^\/]+)/);
if(matches) {
contextHashtag = matches[0] + '/issues/{1}';
}
}
// delete apiWebPage.description // delete apiWebPage.description
const shortDescriptionText = limitSymbols(apiWebPage.description || '', 150, 180);
apiWebPage.rDescription = RichTextProcessor.wrapRichText(shortDescriptionText, {
contextSite: siteName || 'external',
contextHashtag: contextHashtag
});
if(!photoTypeSet.has(apiWebPage.type) && if(!photoTypeSet.has(apiWebPage.type) &&
!apiWebPage.description && !apiWebPage.description &&
@ -128,6 +112,28 @@ export class AppWebPagesManager {
return apiWebPage; return apiWebPage;
} }
public wrapTitle(webPage: WebPage.webPage) {
let shortTitle = webPage.title || webPage.author || webPage.site_name || '';
shortTitle = limitSymbols(shortTitle, 80, 100);
return RichTextProcessor.wrapRichText(shortTitle, {noLinks: true, noLinebreaks: true});
}
public wrapDescription(webPage: WebPage.webPage) {
const shortDescriptionText = limitSymbols(webPage.description || '', 150, 180);
// const siteName = webPage.site_name;
// let contextHashtag = '';
// if(siteName === 'GitHub') {
// const matches = apiWebPage.url.match(/(https?:\/\/github\.com\/[^\/]+\/[^\/]+)/);
// if(matches) {
// contextHashtag = matches[0] + '/issues/{1}';
// }
// }
return RichTextProcessor.wrapRichText(shortDescriptionText/* , {
contextSite: siteName || 'external',
contextHashtag: contextHashtag
} */);
}
public getMessageKeyForPendingWebPage(peerId: PeerId, mid: number, isScheduled?: boolean): WebPageMessageKey { public getMessageKeyForPendingWebPage(peerId: PeerId, mid: number, isScheduled?: boolean): WebPageMessageKey {
return peerId + '_' + mid + (isScheduled ? '_s' : '') as any; return peerId + '_' + mid + (isScheduled ? '_s' : '') as any;
} }

326
src/lib/richtextprocessor.ts

@ -11,7 +11,7 @@
import emojiRegExp from '../vendor/emoji/regex'; import emojiRegExp from '../vendor/emoji/regex';
import { encodeEmoji, toCodePoints } from '../vendor/emoji'; import { encodeEmoji, toCodePoints } from '../vendor/emoji';
import { MessageEntity } from '../layer'; import { Message, MessageEntity } from '../layer';
import { IS_SAFARI } from '../environment/userAgent'; import { IS_SAFARI } from '../environment/userAgent';
import { MOUNT_CLASS_TO } from '../config/debug'; import { MOUNT_CLASS_TO } from '../config/debug';
import IS_EMOJI_SUPPORTED from '../environment/emojiSupport'; import IS_EMOJI_SUPPORTED from '../environment/emojiSupport';
@ -463,10 +463,16 @@ namespace RichTextProcessor {
}); });
} }
function setBlankToAnchor(anchor: HTMLAnchorElement) {
anchor.target = '_blank';
anchor.rel = 'noopener noreferrer';
return anchor;
}
/** /**
* * Expecting correctly sorted nested entities (RichTextProcessor.sortEntities) * * Expecting correctly sorted nested entities (RichTextProcessor.sortEntities)
*/ */
export function wrapRichText(text: string, options: Partial<{ export function wrapRichText(text: string, options: Partial<{
entities: MessageEntity[], entities: MessageEntity[],
contextSite: string, contextSite: string,
highlightUsername: string, highlightUsername: string,
@ -483,57 +489,33 @@ namespace RichTextProcessor {
noEncoding: boolean, noEncoding: boolean,
contextHashtag?: string, contextHashtag?: string,
nasty?: {
i: number,
usedLength: number,
lastEntity?: MessageEntity
},
voodoo?: boolean
}> = {}) { }> = {}) {
const fragment = document.createDocumentFragment();
if(!text) { if(!text) {
return ''; return fragment;
} }
const lol: { const entities = options.entities ??= parseEntities(text);
part: string,
offset: number,
// priority: number
}[] = [];
const entities = options.entities || parseEntities(text);
const passEntities: typeof options.passEntities = options.passEntities || {}; const passEntities = options.passEntities ??= {};
const contextSite = options.contextSite || 'Telegram'; const contextSite = options.contextSite ??= 'Telegram';
const contextExternal = contextSite !== 'Telegram'; const contextExternal = contextSite !== 'Telegram';
const nasty = options.nasty ??= {
const insertPart = (entity: MessageEntity, startPart: string, endPart?: string/* , priority = 0 */) => { i: 0,
const startOffset = entity.offset, endOffset = endPart ? entity.offset + entity.length : undefined; usedLength: 0
let startIndex: number, endIndex: number, length = lol.length;
for(let i = length - 1; i >= 0; --i) {
const offset = lol[i].offset;
if(startIndex === undefined && startOffset >= offset) {
startIndex = i + 1;
}
if(endOffset !== undefined) {
if(endOffset <= offset) {
endIndex = i;
}
}
if(startOffset > offset && (endOffset === undefined || endOffset < offset)) {
break;
}
}
startIndex ??= 0;
lol.splice(startIndex, 0, {part: startPart, offset: entity.offset/* , priority */});
if(endOffset !== undefined) {
endIndex ??= startIndex;
++endIndex;
lol.splice(endIndex, 0, {part: endPart, offset: entity.offset + entity.length/* , priority */});
}
}; };
const pushPartsAfterSort: typeof lol = [];
const textLength = text.length; const textLength = text.length;
for(let i = 0, length = entities.length; i < length; ++i) { const length = entities.length;
let entity = entities[i]; let lastElement: HTMLElement | DocumentFragment;
for(; nasty.i < length; ++nasty.i) {
let entity = entities[nasty.i];
// * check whether text was sliced // * check whether text was sliced
// TODO: consider about moving it to other function // TODO: consider about moving it to other function
@ -546,13 +528,40 @@ namespace RichTextProcessor {
entity.length = entity.offset + entity.length - textLength; entity.length = entity.offset + entity.length - textLength;
} }
if(entity.length) {
nasty.lastEntity = entity;
}
let nextEntity = entities[nasty.i + 1];
const startOffset = entity.offset;
const endOffset = startOffset + entity.length;
const endPartOffset = Math.min(endOffset, nextEntity?.offset ?? 0xFFFF);
const fullEntityText = text.slice(startOffset, endOffset);
const sliced = text.slice(startOffset, endPartOffset);
const partText = sliced;
if(nasty.usedLength < startOffset) {
(lastElement || fragment).append(text.slice(nasty.usedLength, startOffset));
}
if(lastElement) {
lastElement = fragment;
}
nasty.usedLength = endPartOffset;
let element: HTMLElement,
property: 'textContent' | 'alt' = 'textContent',
usedText = false;
switch(entity._) { switch(entity._) {
case 'messageEntityBold': { case 'messageEntityBold': {
if(!options.noTextFormat) { if(!options.noTextFormat) {
if(options.wrappingDraft) { if(options.wrappingDraft) {
insertPart(entity, '<span style="font-weight: bold;">', '</span>'); element = document.createElement('span');
element.style.fontWeight = 'bold';
} else { } else {
insertPart(entity, '<strong>', '</strong>'); element = document.createElement('strong');
} }
} }
@ -562,9 +571,10 @@ namespace RichTextProcessor {
case 'messageEntityItalic': { case 'messageEntityItalic': {
if(!options.noTextFormat) { if(!options.noTextFormat) {
if(options.wrappingDraft) { if(options.wrappingDraft) {
insertPart(entity, '<span style="font-style: italic;">', '</span>'); element = document.createElement('span');
element.style.fontStyle = 'italic';
} else { } else {
insertPart(entity, '<em>', '</em>'); element = document.createElement('em');
} }
} }
@ -574,9 +584,10 @@ namespace RichTextProcessor {
case 'messageEntityStrike': { case 'messageEntityStrike': {
if(options.wrappingDraft) { if(options.wrappingDraft) {
const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line'; const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line';
insertPart(entity, `<span style="${styleName}: line-through;">`, '</span>'); element = document.createElement('span');
element.style.cssText = `${styleName}: line-through;`;
} else if(!options.noTextFormat) { } else if(!options.noTextFormat) {
insertPart(entity, '<del>', '</del>'); element = document.createElement('del');
} }
break; break;
@ -585,54 +596,68 @@ namespace RichTextProcessor {
case 'messageEntityUnderline': { case 'messageEntityUnderline': {
if(options.wrappingDraft) { if(options.wrappingDraft) {
const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line'; const styleName = IS_SAFARI ? 'text-decoration' : 'text-decoration-line';
insertPart(entity, `<span style="${styleName}: underline;">`, '</span>'); element = document.createElement('span');
element.style.cssText = `${styleName}: underline;`;
} else if(!options.noTextFormat) { } else if(!options.noTextFormat) {
insertPart(entity, '<u>', '</u>'); element = document.createElement('u');
} }
break; break;
} }
case 'messageEntityPre':
case 'messageEntityCode': { case 'messageEntityCode': {
if(options.wrappingDraft) { if(options.wrappingDraft) {
insertPart(entity, '<span style="font-family: var(--font-monospace);">', '</span>'); element = document.createElement('span');
element.style.fontFamily = 'var(--font-monospace)';
} else if(!options.noTextFormat) { } else if(!options.noTextFormat) {
insertPart(entity, '<code>', '</code>'); element = document.createElement('code');
} }
break; break;
} }
case 'messageEntityPre': { // case 'messageEntityPre': {
if(options.wrappingDraft) { // if(options.wrappingDraft) {
insertPart(entity, '<span style="font-family: var(--font-monospace);">', '</span>'); // element = document.createElement('span');
} else if(!options.noTextFormat) { // element.style.fontFamily = 'var(--font-monospace)';
insertPart(entity, `<pre><code${entity.language ? ' class="language-' + encodeEntities(entity.language) + '"' : ''}>`, '</code></pre>'); // } else if(!options.noTextFormat) {
} // element = document.createElement('pre');
// const inner = document.createElement('code');
break; // if(entity.language) {
} // inner.className = 'language-' + entity.language;
// inner.textContent = entityText;
// usedText = true;
// }
// }
// break;
// }
case 'messageEntityHighlight': { case 'messageEntityHighlight': {
insertPart(entity, '<i class="text-highlight">', '</i>'); element = document.createElement('i');
element.className = 'text-highlight';
break; break;
} }
case 'messageEntityBotCommand': { case 'messageEntityBotCommand': {
// if(!(options.noLinks || options.noCommands || contextExternal)/* && !entity.unsafe */) { // if(!(options.noLinks || options.noCommands || contextExternal)/* && !entity.unsafe */) {
if(!options.noLinks && passEntities[entity._]) { if(!options.noLinks && passEntities[entity._]) {
const entityText = text.substr(entity.offset, entity.length); let command = fullEntityText.slice(1);
let command = entityText.substr(1);
let bot: string | boolean; let bot: string | boolean;
let atPos: number; let atPos: number;
if((atPos = command.indexOf('@')) !== -1) { if((atPos = command.indexOf('@')) !== -1) {
bot = command.substr(atPos + 1); bot = command.slice(atPos + 1);
command = command.substr(0, atPos); command = command.slice(0, atPos);
} else { } else {
bot = options.fromBot; bot = options.fromBot;
} }
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" ${contextExternal ? '' : 'onclick="execBotCommand(this)"'}>`, `</a>`); element = document.createElement('a');
(element as HTMLAnchorElement).href = encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''));
if(!contextExternal) {
element.setAttribute('onclick', 'execBotCommand(this)');
}
} }
break; break;
@ -657,11 +682,15 @@ namespace RichTextProcessor {
// if(isSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера // if(isSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
// insertPart(entity, '<span class="emoji">', '</span>'); // insertPart(entity, '<span class="emoji">', '</span>');
// } else { // } else {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`); element = document.createElement('img');
(element as HTMLImageElement).src = `assets/img/emoji/${entity.unicode}.png`;
property = 'alt';
element.className = 'emoji';
// } // }
//} else if(options.mustWrapEmoji) { //} else if(options.mustWrapEmoji) {
} else if(!options.wrappingDraft) { } else if(!options.wrappingDraft) {
insertPart(entity, '<span class="emoji">', '</span>'); element = document.createElement('span');
element.className = 'emoji';
}/* else if(!IS_SAFARI) { }/* else if(!IS_SAFARI) {
insertPart(entity, '<span class="emoji" contenteditable="false">', '</span>'); insertPart(entity, '<span class="emoji" contenteditable="false">', '</span>');
} */ } */
@ -673,32 +702,28 @@ namespace RichTextProcessor {
} }
case 'messageEntityCaret': { case 'messageEntityCaret': {
const html = '<span class="composer-sel"></span>'; element = document.createElement('span');
// const html = '<span class="composer-sel" contenteditable="false"></span>'; element.className = 'composer-sel';
// insertPart(entity, '<span class="composer-sel" contenteditable="true"></span>'); // const html = '<span class="composer-sel"></span>';
// insertPart(entity, '<span class="composer-sel"></span>'); // pushPartsAfterSort.push({part: html, offset: entity.offset});
pushPartsAfterSort.push({part: html, offset: entity.offset});
// insertPart(entity, html/* , undefined, 1 */);
break; break;
} }
/* case 'messageEntityLinebreak': { // /* case 'messageEntityLinebreak': {
if(options.noLinebreaks) { // if(options.noLinebreaks) {
insertPart(entity, ' '); // insertPart(entity, ' ');
} else { // } else {
insertPart(entity, '<br/>'); // insertPart(entity, '<br/>');
} // }
break; // break;
} */ // } */
case 'messageEntityUrl': case 'messageEntityUrl':
case 'messageEntityTextUrl': { case 'messageEntityTextUrl': {
if(!(options.noLinks && !passEntities[entity._])) { if(!(options.noLinks && !passEntities[entity._])) {
const entityText = text.substr(entity.offset, entity.length);
// let inner: string; // let inner: string;
let url: string = (entity as MessageEntity.messageEntityTextUrl).url || entityText; let url: string = (entity as MessageEntity.messageEntityTextUrl).url || fullEntityText;
let masked = false; let masked = false;
let onclick: string; let onclick: string;
@ -707,14 +732,13 @@ namespace RichTextProcessor {
onclick = wrapped.onclick; onclick = wrapped.onclick;
if(entity._ === 'messageEntityTextUrl') { if(entity._ === 'messageEntityTextUrl') {
const nextEntity = entities[i + 1];
if(nextEntity?._ === 'messageEntityUrl' && if(nextEntity?._ === 'messageEntityUrl' &&
nextEntity.length === entity.length && nextEntity.length === entity.length &&
nextEntity.offset === entity.offset) { nextEntity.offset === entity.offset) {
i++; nasty.i++;
} }
if(url !== entityText) { if(url !== fullEntityText) {
masked = true; masked = true;
} }
} else { } else {
@ -734,10 +758,17 @@ namespace RichTextProcessor {
? encodeEntities(url) ? encodeEntities(url)
: `javascript:electronHelpers.openExternal('${encodeEntities(url)}');`; : `javascript:electronHelpers.openExternal('${encodeEntities(url)}');`;
const target = (currentContext || typeof electronHelpers !== 'undefined') element = document.createElement('a');
? '' : ' target="_blank" rel="noopener noreferrer"'; element.className = 'anchor-url';
(element as HTMLAnchorElement).href = href;
if(!(currentContext || typeof electronHelpers !== 'undefined')) {
setBlankToAnchor(element as HTMLAnchorElement);
}
insertPart(entity, `<a class="anchor-url" href="${href}"${target}${onclick ? `onclick="${onclick}(this)"` : ''}>`, '</a>'); if(onclick) {
element.setAttribute('onclick', onclick + '(this)');
}
} }
break; break;
@ -745,8 +776,9 @@ namespace RichTextProcessor {
case 'messageEntityEmail': { case 'messageEntityEmail': {
if(!options.noLinks) { if(!options.noLinks) {
const entityText = text.substr(entity.offset, entity.length); element = document.createElement('a');
insertPart(entity, `<a href="${encodeEntities('mailto:' + entityText)}" target="_blank" rel="noopener noreferrer">`, '</a>'); (element as HTMLAnchorElement).href = encodeEntities('mailto:' + fullEntityText);
setBlankToAnchor(element as HTMLAnchorElement);
} }
break; break;
@ -755,9 +787,15 @@ namespace RichTextProcessor {
case 'messageEntityHashtag': { case 'messageEntityHashtag': {
const contextUrl = !options.noLinks && siteHashtags[contextSite]; const contextUrl = !options.noLinks && siteHashtags[contextSite];
if(contextUrl) { if(contextUrl) {
const entityText = text.substr(entity.offset, entity.length); const hashtag = fullEntityText.slice(1);
const hashtag = entityText.substr(1); element = document.createElement('a');
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ' onclick="searchByHashtag(this)"'}>`, '</a>'); element.className = 'anchor-hashtag';
(element as HTMLAnchorElement).href = contextUrl.replace('{1}', encodeURIComponent(hashtag));
if(contextExternal) {
setBlankToAnchor(element as HTMLAnchorElement);
} else {
element.setAttribute('onclick', 'searchByHashtag(this)');
}
} }
break; break;
@ -765,7 +803,10 @@ namespace RichTextProcessor {
case 'messageEntityMentionName': { case 'messageEntityMentionName': {
if(!(options.noLinks && !passEntities[entity._])) { if(!(options.noLinks && !passEntities[entity._])) {
insertPart(entity, `<a href="#/im?p=${encodeURIComponent(entity.user_id)}" class="follow" data-follow="${entity.user_id}">`, '</a>'); element = document.createElement('a');
(element as HTMLAnchorElement).href = `#/im?p=${encodeURIComponent(entity.user_id)}`;
element.className = 'follow';
element.dataset.follow = '' + entity.user_id;
} }
break; break;
@ -774,13 +815,18 @@ namespace RichTextProcessor {
case 'messageEntityMention': { case 'messageEntityMention': {
// const contextUrl = !options.noLinks && siteMentions[contextSite]; // const contextUrl = !options.noLinks && siteMentions[contextSite];
if(!options.noLinks) { if(!options.noLinks) {
const entityText = text.substr(entity.offset, entity.length); const username = fullEntityText.slice(1);
const username = entityText.substr(1);
const {url, onclick} = wrapUrl('t.me/' + username); const {url, onclick} = wrapUrl('t.me/' + username);
element = document.createElement('a');
element.className = 'mention';
(element as HTMLAnchorElement).href = url;
if(onclick) {
element.setAttribute('onclick', `${onclick}(this)`);
}
// insertPart(entity, `<a class="mention" href="${contextUrl.replace('{1}', encodeURIComponent(username))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ''}>`, '</a>'); // insertPart(entity, `<a class="mention" href="${contextUrl.replace('{1}', encodeURIComponent(username))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ''}>`, '</a>');
insertPart(entity, `<a class="mention" href="${url}" ${onclick ? `onclick=${onclick}(this)` : ''}>`, '</a>');
} }
break; break;
@ -793,53 +839,63 @@ namespace RichTextProcessor {
const after = text.slice(entity.offset + entity.length); const after = text.slice(entity.offset + entity.length);
text = before + spoiler(spoilerBefore)/* '▚'.repeat(entity.length) */ + after; text = before + spoiler(spoilerBefore)/* '▚'.repeat(entity.length) */ + after;
} else if(options.wrappingDraft) { } else if(options.wrappingDraft) {
insertPart(entity, '<span style="font-family: spoiler;">', '</span>'); element = document.createElement('span');
element.style.fontFamily = 'spoiler';
} else { } else {
insertPart(entity, '<span class="spoiler"><span class="spoiler-text">', '</span></span>'); const container = document.createElement('span');
container.className = 'spoiler';
element = document.createElement('span');
element.className = 'spoiler-text';
element.textContent = partText;
usedText = true;
container.append(element);
fragment.append(container);
} }
break; break;
} }
} }
}
// lol.sort((a, b) => (a.offset - b.offset) || (a.priority - b.priority)); if(element && !usedText) {
// lol.sort((a, b) => a.offset - b.offset); // have to sort because of nested entities // @ts-ignore
element[property] = partText;
}
let partsLength = lol.length, pushPartsAfterSortLength = pushPartsAfterSort.length; while(nextEntity && nextEntity.offset < (endOffset - 1)) {
for(let i = 0; i < pushPartsAfterSortLength; ++i) { ++nasty.i;
const part = pushPartsAfterSort[i];
let insertAt = 0; (element || fragment).append(wrapRichText(text, {
while(insertAt < partsLength) { ...options,
if(lol[insertAt++].offset >= part.offset) { voodoo: true
break; }));
}
nextEntity = entities[nasty.i + 1];
} }
lol.splice(insertAt, 0, part); if(!element?.parentElement) {
} (lastElement || fragment).append(element ?? partText);
}
partsLength += pushPartsAfterSortLength; if(entity.length > partText.length && element) {
lastElement = element;
} else {
lastElement = fragment;
}
const arr: string[] = []; if(options.voodoo) {
let usedLength = 0; return fragment;
for(let i = 0; i < partsLength; ++i) {
const {part, offset} = lol[i];
if(offset > usedLength) {
const sliced = text.slice(usedLength, offset);
arr.push(options.noEncoding ? sliced : encodeEntities(sliced));
usedLength = offset;
} }
}
arr.push(part); if(nasty.lastEntity) {
nasty.usedLength = nasty.lastEntity.offset + nasty.lastEntity.length;
} }
if(usedLength < text.length) { if(nasty.usedLength < textLength) {
const sliced = text.slice(usedLength); (lastElement || fragment).append(text.slice(nasty.usedLength));
arr.push(options.noEncoding ? sliced : encodeEntities(sliced));
} }
return arr.join(''); return fragment;
} }
export function fixEmoji(text: string, entities?: MessageEntity[]) { export function fixEmoji(text: string, entities?: MessageEntity[]) {
@ -874,7 +930,7 @@ namespace RichTextProcessor {
entities: MessageEntity[] entities: MessageEntity[]
}> = {}) { }> = {}) {
if(!text) { if(!text) {
return ''; return wrapRichText('');
} }
return wrapRichText(text, { return wrapRichText(text, {
@ -941,11 +997,11 @@ namespace RichTextProcessor {
noTextFormat: true, noTextFormat: true,
noLinebreaks: true, noLinebreaks: true,
noLinks: true noLinks: true
}); }).textContent;
} }
export function wrapEmojiText(text: string, isDraft = false) { export function wrapEmojiText(text: string, isDraft = false) {
if(!text) return ''; if(!text) return wrapRichText('');
let entities = parseEntities(text).filter(e => e._ === 'messageEntityEmoji'); let entities = parseEntities(text).filter(e => e._ === 'messageEntityEmoji');
return wrapRichText(text, {entities, wrappingDraft: isDraft}); return wrapRichText(text, {entities, wrappingDraft: isDraft});

5
src/pages/pageSignIn.ts

@ -40,6 +40,7 @@ import stateStorage from "../lib/stateStorage";
import rootScope from "../lib/rootScope"; import rootScope from "../lib/rootScope";
import TelInputField from "../components/telInputField"; import TelInputField from "../components/telInputField";
import IS_EMOJI_SUPPORTED from "../environment/emojiSupport"; import IS_EMOJI_SUPPORTED from "../environment/emojiSupport";
import setInnerHTML from "../helpers/dom/setInnerHTML";
//import _countries from '../countries_pretty.json'; //import _countries from '../countries_pretty.json';
let btnNext: HTMLButtonElement = null, btnQr: HTMLButtonElement; let btnNext: HTMLButtonElement = null, btnQr: HTMLButtonElement;
@ -118,10 +119,10 @@ let onFirstMount = () => {
let wrapped = RichTextProcessor.wrapEmojiText(emoji); let wrapped = RichTextProcessor.wrapEmojiText(emoji);
if(IS_EMOJI_SUPPORTED) { if(IS_EMOJI_SUPPORTED) {
const spanEmoji = document.createElement('span'); const spanEmoji = document.createElement('span');
spanEmoji.innerHTML = wrapped; setInnerHTML(spanEmoji, wrapped);
li.append(spanEmoji); li.append(spanEmoji);
} else { } else {
li.innerHTML = wrapped; setInnerHTML(li, wrapped);
} }
const el = i18n(c.default_name as any); const el = i18n(c.default_name as any);

33
src/scripts/in/schema_additional_params.json

@ -6,13 +6,9 @@
{"name": "h", "type": "number"}, {"name": "h", "type": "number"},
{"name": "w", "type": "number"}, {"name": "w", "type": "number"},
{"name": "file_name", "type": "string"}, {"name": "file_name", "type": "string"},
{"name": "fileName", "type": "string"},
{"name": "file", "type": "File"}, {"name": "file", "type": "File"},
{"name": "duration", "type": "number"}, {"name": "duration", "type": "number"},
{"name": "audioTitle", "type": "string"},
{"name": "audioPerformer", "type": "string"},
{"name": "sticker", "type": "1 | 2 | 3"}, {"name": "sticker", "type": "1 | 2 | 3"},
{"name": "stickerEmoji", "type": "string"},
{"name": "stickerEmojiRaw", "type": "string"}, {"name": "stickerEmojiRaw", "type": "string"},
{"name": "stickerSetInput", "type": "InputStickerSet.inputStickerSetID"}, {"name": "stickerSetInput", "type": "InputStickerSet.inputStickerSetID"},
{"name": "stickerThumbConverted", "type": "true"}, {"name": "stickerThumbConverted", "type": "true"},
@ -149,7 +145,6 @@
}, { }, {
"predicate": "user", "predicate": "user",
"params": [ "params": [
{"name": "initials", "type": "string"},
{"name": "sortName", "type": "string"} {"name": "sortName", "type": "string"}
] ]
}, { }, {
@ -163,26 +158,6 @@
{"name": "rReply", "type": "string"}, {"name": "rReply", "type": "string"},
{"name": "rMessage", "type": "string"} {"name": "rMessage", "type": "string"}
] ]
}, {
"predicate": "chat",
"params": [
{"name": "initials", "type": "string"}
]
}, {
"predicate": "chatForbidden",
"params": [
{"name": "initials", "type": "string"}
]
}, {
"predicate": "channel",
"params": [
{"name": "initials", "type": "string"}
]
}, {
"predicate": "channelForbidden",
"params": [
{"name": "initials", "type": "string"}
]
}, { }, {
"predicate": "messageActionDiscussionStarted", "predicate": "messageActionDiscussionStarted",
"params": [], "params": [],
@ -317,12 +292,6 @@
"params": [ "params": [
{"name": "checkedReference", "type": "boolean"} {"name": "checkedReference", "type": "boolean"}
] ]
}, {
"predicate": "webPage",
"params": [
{"name": "rTitle", "type": "string"},
{"name": "rDescription", "type": "string"}
]
}, { }, {
"predicate": "inputMediaContact", "predicate": "inputMediaContact",
"params": [ "params": [
@ -331,8 +300,6 @@
}, { }, {
"predicate": "poll", "predicate": "poll",
"params": [ "params": [
{"name": "rQuestion", "type": "string"},
{"name": "rReply", "type": "string"},
{"name": "chosenIndexes", "type": "number[]"} {"name": "chosenIndexes", "type": "number[]"}
] ]
}, { }, {

Loading…
Cancel
Save