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

2
src/components/appMediaViewer.ts

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

2
src/components/appMediaViewerBase.ts

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

14
src/components/appSearchSuper..ts

@ -56,6 +56,8 @@ import escapeRegExp from "../helpers/string/escapeRegExp"; @@ -56,6 +56,8 @@ import escapeRegExp from "../helpers/string/escapeRegExp";
import limitSymbols from "../helpers/string/limitSymbols";
import findAndSplice from "../helpers/array/findAndSplice";
import { ScrollStartCallbackDimensions } from "../helpers/fastSmoothScroll";
import setInnerHTML from "../helpers/dom/setInnerHTML";
import appWebPagesManager from "../lib/appManagers/appWebPagesManager";
//const testScroll = false;
@ -699,7 +701,6 @@ export default class AppSearchSuper { @@ -699,7 +701,6 @@ export default class AppSearchSuper {
if(!same) {
webpage.description = message.message;
webpage.rDescription = RichTextProcessor.wrapRichText(limitSymbols(message.message, 150, 180));
}
}
@ -724,13 +725,12 @@ export default class AppSearchSuper { @@ -724,13 +725,12 @@ export default class AppSearchSuper {
});
} else {
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 subtitle = webpage.rDescription || '';
let title = appWebPagesManager.wrapTitle(webpage);
const subtitleFragment = htmlToDocumentFragment(subtitle);
const subtitleFragment = appWebPagesManager.wrapDescription(webpage);
const aFragment = htmlToDocumentFragment(RichTextProcessor.wrapRichText(webpage.url || ''));
const a = aFragment.firstElementChild;
if(a instanceof HTMLAnchorElement) {
@ -751,9 +751,9 @@ export default class AppSearchSuper { @@ -751,9 +751,9 @@ export default class AppSearchSuper {
subtitleFragment.append('\n', appMessagesManager.wrapSenderToPeer(message));
}
if(!title) {
if(!title.textContent) {
//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({

10
src/components/audio.ts

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

3
src/components/call/index.ts

@ -11,7 +11,6 @@ import ControlsHover from "../../helpers/dom/controlsHover"; @@ -11,7 +11,6 @@ import ControlsHover from "../../helpers/dom/controlsHover";
import findUpClassName from "../../helpers/dom/findUpClassName";
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../../helpers/dom/fullScreen";
import { onMediaLoad } from "../../helpers/files";
import { MediaSize } from "../../helpers/mediaSizes";
import MovablePanel from "../../helpers/movablePanel";
import safeAssign from "../../helpers/object/safeAssign";
import toggleClassName from "../../helpers/toggleClassName";
@ -457,7 +456,7 @@ export default class PopupCall extends PopupElement { @@ -457,7 +456,7 @@ export default class PopupCall extends PopupElement {
if(!this.emojisSubtitle.textContent && connectionState < CALL_STATE.EXCHANGING_KEYS) {
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"; @@ -18,10 +18,10 @@ import PeerTitle from "../peerTitle";
import { i18n } from "../../lib/langPack";
import { formatFullSentTime } from "../../helpers/date";
import ButtonIcon from "../buttonIcon";
import { MyDocument } from "../../lib/appManagers/appDocsManager";
import { Message } from "../../layer";
import { DocumentAttribute } from "../../layer";
import MediaProgressLine from "../mediaProgressLine";
import VolumeSelector from "../volumeSelector";
import RichTextProcessor from "../../lib/richtextprocessor";
export default class ChatAudio extends PinnedContainer {
private toggleEl: HTMLElement;
@ -149,7 +149,7 @@ export default class ChatAudio extends PinnedContainer { @@ -149,7 +149,7 @@ export default class ChatAudio extends PinnedContainer {
};
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';
if(!isMusic) {
title = new PeerTitle({peerId: message.fromId, fromName: message.fwd_from?.from_name}).element;
@ -157,8 +157,9 @@ export default class ChatAudio extends PinnedContainer { @@ -157,8 +157,9 @@ export default class ChatAudio extends PinnedContainer {
//subtitle = 'Voice message';
subtitle = formatFullSentTime(message.date);
} else {
title = doc.audioTitle || doc.fileName;
subtitle = doc.audioPerformer || i18n('AudioUnknownArtist');
const audioAttribute = doc.attributes.find((attr) => attr._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio;
title = RichTextProcessor.wrapEmojiText(audioAttribute?.title ?? doc.file_name);
subtitle = audioAttribute?.performer ? RichTextProcessor.wrapEmojiText(audioAttribute.performer) : i18n('AudioUnknownArtist');
}
this.fasterEl.classList.toggle('hide', isMusic);

5
src/components/chat/autocompletePeerHelper.ts

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

51
src/components/chat/bubbles.ts

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

2
src/components/chat/chat.ts

@ -323,7 +323,7 @@ export default class Chat extends EventListenerBase<{ @@ -323,7 +323,7 @@ export default class Chat extends EventListenerBase<{
// this.initPeerId = peerId;
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.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);

11
src/components/chat/inlineHelper.ts

@ -25,6 +25,7 @@ import GifsMasonry from "../gifsMasonry"; @@ -25,6 +25,7 @@ import GifsMasonry from "../gifsMasonry";
import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
import mediaSizes from "../../helpers/mediaSizes";
import readBlobAsDataURL from "../../helpers/blob/readBlobAsDataURL";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
const ANIMATION_GROUP = 'INLINE-HELPER';
// const GRID_ITEMS = 5;
@ -131,18 +132,18 @@ export default class InlineHelper extends AutocompleteHelper { @@ -131,18 +132,18 @@ export default class InlineHelper extends AutocompleteHelper {
if(!isGallery) {
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');
title.classList.add('inline-helper-result-title');
title.innerHTML = RichTextProcessor.wrapEmojiText(item.title);
setInnerHTML(title, RichTextProcessor.wrapEmojiText(item.title));
const description = document.createElement('div');
description.classList.add('inline-helper-result-description');
description.innerHTML = RichTextProcessor.wrapRichText(item.description, {
setInnerHTML(description, RichTextProcessor.wrapRichText(item.description, {
noCommands: true,
noLinks: true
});
}));
container.append(title, description);
@ -239,7 +240,7 @@ export default class InlineHelper extends AutocompleteHelper { @@ -239,7 +240,7 @@ export default class InlineHelper extends AutocompleteHelper {
parent.textContent = '';
if(botResults.switch_pm) {
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) => {
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'; @@ -93,6 +93,7 @@ import ChatBotCommands from './botCommands';
import copy from '../../helpers/object/copy';
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import toHHMMSS from '../../helpers/string/toHHMMSS';
import documentFragmentToHTML from '../../helpers/dom/documentFragmentToHTML';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -2053,7 +2054,7 @@ export default class ChatInput { @@ -2053,7 +2054,7 @@ export default class ChatInput {
//const saveExecuted = this.prepareDocumentExecute();
// 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);
const caret = this.messageInput.querySelector('.composer-sel');
@ -2615,7 +2616,7 @@ export default class ChatInput { @@ -2615,7 +2616,7 @@ export default class ChatInput {
public initMessageEditing(mid: number) {
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 replyFragment = this.appMessagesManager.wrapMessageForReply(message, undefined, [message.mid]);
this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message);

4
src/components/chat/messageRender.ts

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

3
src/components/chat/replyKeyboard.ts

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

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

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

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

@ -151,7 +151,7 @@ export default class StickersTab implements EmoticonsTab { @@ -151,7 +151,7 @@ export default class StickersTab implements EmoticonsTab {
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');
const itemsDiv = document.createElement('div');
@ -161,7 +161,8 @@ export default class StickersTab implements EmoticonsTab { @@ -161,7 +161,8 @@ export default class StickersTab implements EmoticonsTab {
titleDiv.classList.add('category-title');
if(categoryTitle) {
titleDiv.innerHTML = categoryTitle;
if(typeof(categoryTitle) === 'string') titleDiv.innerHTML = categoryTitle;
else titleDiv.append(categoryTitle);
}
categoryDiv.append(titleDiv, itemsDiv);

11
src/components/inputField.ts

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

5
src/components/peerProfile.ts

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

5
src/components/peerTitle.ts

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

7
src/components/poll.ts

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

3
src/components/popups/joinChatInvite.ts

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

2
src/components/popups/newMedia.ts

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

5
src/components/popups/peer.ts

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

3
src/components/popups/stickers.ts

@ -21,6 +21,7 @@ import findUpClassName from "../../helpers/dom/findUpClassName"; @@ -21,6 +21,7 @@ import findUpClassName from "../../helpers/dom/findUpClassName";
import toggleDisability from "../../helpers/dom/toggleDisability";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import { toastNew } from "../toast";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
const ANIMATION_GROUP = 'STICKERS-POPUP';
@ -98,7 +99,7 @@ export default class PopupStickers extends PopupElement { @@ -98,7 +99,7 @@ export default class PopupStickers extends PopupElement {
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);
let button: HTMLElement;

2
src/components/row.ts

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

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

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

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

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

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

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

8
src/components/wrappers.ts

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

5
src/helpers/dom/documentFragmentToHTML.ts

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

5
src/helpers/dom/htmlToSpan.ts

@ -4,8 +4,9 @@ @@ -4,8 +4,9 @@
* 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');
span.innerHTML = html;
if(typeof(html) === 'string') span.innerHTML = html;
else span.append(html);
return span;
}

9
src/helpers/dom/setInnerHTML.ts

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

14
src/lib/appManagers/appAvatarsManager.ts

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

2
src/lib/appManagers/appChatsManager.ts

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

5
src/lib/appManagers/appDialogsManager.ts

@ -62,6 +62,7 @@ import appNavigationController, { NavigationItem } from "../../components/appNav @@ -62,6 +62,7 @@ import appNavigationController, { NavigationItem } from "../../components/appNav
import assumeType from "../../helpers/assumeType";
import generateTitleIcons from "../../components/generateTitleIcons";
import appMediaPlaybackController from "../../components/appMediaPlaybackController";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
export type DialogDom = {
avatarEl: AvatarElement,
@ -517,7 +518,7 @@ export class AppDialogsManager { @@ -517,7 +518,7 @@ export class AppDialogsManager {
}
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) => {
@ -779,7 +780,7 @@ export class AppDialogsManager { @@ -779,7 +780,7 @@ export class AppDialogsManager {
const titleSpan = document.createElement('span');
titleSpan.classList.add('text-super');
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');
unreadSpan.classList.add('badge', 'badge-20', 'badge-primary');
const i = document.createElement('i');

8
src/lib/appManagers/appDocsManager.ts

@ -105,13 +105,10 @@ export class AppDocsManager { @@ -105,13 +105,10 @@ export class AppDocsManager {
switch(attribute._) {
case 'documentAttributeFilename':
doc.file_name = RichTextProcessor.wrapPlainText(attribute.file_name);
doc.fileName = RichTextProcessor.wrapEmojiText(attribute.file_name);
break;
case 'documentAttributeAudio':
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';
/* if(apiDoc.type === 'audio') {
apiDoc.supportsStreaming = true;
@ -133,7 +130,6 @@ export class AppDocsManager { @@ -133,7 +130,6 @@ export class AppDocsManager {
case 'documentAttributeSticker':
if(attribute.alt !== undefined) {
doc.stickerEmojiRaw = attribute.alt;
doc.stickerEmoji = RichTextProcessor.wrapRichText(doc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
}
if(attribute.stickerset) {
@ -210,7 +206,7 @@ export class AppDocsManager { @@ -210,7 +206,7 @@ export class AppDocsManager {
if(doc.type === 'voice' || doc.type === 'round') {
// 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()) {
@ -229,7 +225,7 @@ export class AppDocsManager { @@ -229,7 +225,7 @@ export class AppDocsManager {
// doc.url = ''; // * this will break upload urls
if(!doc.file_name) {
doc.file_name = doc.fileName = '';
doc.file_name = '';
}
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"; @@ -24,6 +24,7 @@ import appMessagesIdsManager from "./appMessagesIdsManager";
import assumeType from "../../helpers/assumeType";
import isObject from "../../helpers/object/isObject";
import deepEqual from "../../helpers/object/deepEqual";
import documentFragmentToHTML from "../../helpers/dom/documentFragmentToHTML";
export type MyDraftMessage = DraftMessage.draftMessage;
@ -174,7 +175,7 @@ export class AppDraftsManager { @@ -174,7 +175,7 @@ export class AppDraftsManager {
const apiEntities = draft.entities || [];
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);
if(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"; @@ -71,6 +71,7 @@ import escapeRegExp from "../../helpers/string/escapeRegExp";
import limitSymbols from "../../helpers/string/limitSymbols";
import splitStringByLength from "../../helpers/string/splitStringByLength";
import debounce from "../../helpers/schedulers/debounce";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
//console.trace('include');
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -2905,7 +2906,7 @@ export class AppMessagesManager { @@ -2905,7 +2906,7 @@ export class AppMessagesManager {
const parts: (Node | string)[] = [];
let hasAlbumKey = false;
const addPart = (langKey: LangPackKey, part?: string | HTMLElement) => {
const addPart = (langKey: LangPackKey, part?: string | HTMLElement | DocumentFragment) => {
if(langKey) {
if(part === undefined && hasAlbumKey) {
return;
@ -2980,7 +2981,8 @@ export class AppMessagesManager { @@ -2980,7 +2981,8 @@ export class AppMessagesManager {
addPart('AttachLiveLocation');
break;
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;
case 'messageMediaContact':
addPart('AttachContact');
@ -3004,7 +3006,8 @@ export class AppMessagesManager { @@ -3004,7 +3006,8 @@ export class AppMessagesManager {
} else if(document.type === 'sticker') {
const i = parts.length;
if(document.stickerEmojiRaw) {
addPart(undefined, (plain ? document.stickerEmojiRaw : document.stickerEmoji) + ' ');
const f = document.stickerEmojiRaw + ' ';
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f));
}
addPart('AttachSticker');
@ -3099,7 +3102,7 @@ export class AppMessagesManager { @@ -3099,7 +3102,7 @@ export class AppMessagesManager {
noTextFormat: true
});
parts.push(htmlToDocumentFragment(messageWrapped) as any);
parts.push(htmlToDocumentFragment(messageWrapped));
}
}
@ -3179,7 +3182,7 @@ export class AppMessagesManager { @@ -3179,7 +3182,7 @@ export class AppMessagesManager {
if(plain) {
return RichTextProcessor.wrapPlainText(unsafeMessage);
} else {
element.innerHTML = RichTextProcessor.wrapRichText(unsafeMessage, {noLinebreaks: true});
setInnerHTML(element, RichTextProcessor.wrapRichText(unsafeMessage, {noLinebreaks: true}));
return element;
}
} else {

12
src/lib/appManagers/appPeersManager.ts

@ -74,7 +74,10 @@ export class AppPeersManager { @@ -74,7 +74,10 @@ export class AppPeersManager {
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) {
peerId = rootScope.myId;
}
@ -133,6 +136,13 @@ export class AppPeersManager { @@ -133,6 +136,13 @@ export class AppPeersManager {
: 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 {
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;

2
src/lib/appManagers/appPollsManager.ts

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

3
src/lib/appManagers/appUsersManager.ts

@ -431,17 +431,14 @@ export class AppUsersManager { @@ -431,17 +431,14 @@ export class AppUsersManager {
this.setUserNameToCache(user, oldUser);
if(!oldUser
|| oldUser.initials === undefined
|| oldUser.sortName === undefined
|| oldUser.first_name !== user.first_name
|| oldUser.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.initials = RichTextProcessor.getAbbreviation(fullName);
} else {
user.sortName = oldUser.sortName;
user.initials = oldUser.initials;
}
if(user.status) {

38
src/lib/appManagers/appWebPagesManager.ts

@ -71,23 +71,7 @@ export class AppWebPagesManager { @@ -71,23 +71,7 @@ export class AppWebPagesManager {
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
const shortDescriptionText = limitSymbols(apiWebPage.description || '', 150, 180);
apiWebPage.rDescription = RichTextProcessor.wrapRichText(shortDescriptionText, {
contextSite: siteName || 'external',
contextHashtag: contextHashtag
});
if(!photoTypeSet.has(apiWebPage.type) &&
!apiWebPage.description &&
@ -128,6 +112,28 @@ export class AppWebPagesManager { @@ -128,6 +112,28 @@ export class AppWebPagesManager {
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 {
return peerId + '_' + mid + (isScheduled ? '_s' : '') as any;
}

326
src/lib/richtextprocessor.ts

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

5
src/pages/pageSignIn.ts

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

33
src/scripts/in/schema_additional_params.json

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

Loading…
Cancel
Save