Browse Source

Media aspecter container

Fix urls
Fix merging message entities
Decrease sticker size
master
morethanwords 3 years ago
parent
commit
1bb0f31026
  1. 2
      src/components/appMediaViewer.ts
  2. 12
      src/components/chat/bubbles.ts
  3. 6
      src/components/chat/markupTooltip.ts
  4. 44
      src/components/wrappers.ts
  5. 15
      src/helpers/mediaSizes.ts
  6. 38
      src/lib/appManagers/appPhotosManager.ts
  7. 80
      src/lib/richtextprocessor.ts
  8. 19
      src/scss/partials/_chatBubble.scss

2
src/components/appMediaViewer.ts

@ -956,7 +956,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
// TODO: const maxHeight = mediaSizes.isMobile ? appPhotosManager.windowH : appPhotosManager.windowH - 100; // TODO: const maxHeight = mediaSizes.isMobile ? appPhotosManager.windowH : appPhotosManager.windowH - 100;
const maxHeight = appPhotosManager.windowH - 100; const maxHeight = appPhotosManager.windowH - 100;
let thumbPromise: Promise<any> = Promise.resolve(); let thumbPromise: Promise<any> = Promise.resolve();
const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight, mediaSizes.isMobile ? false : true); const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight, mediaSizes.isMobile ? false : true).photoSize;
if(useContainerAsTarget) { if(useContainerAsTarget) {
const cacheContext = appDownloadManager.getCacheContext(media, size.type); const cacheContext = appDownloadManager.getCacheContext(media, size.type);
let img: HTMLImageElement; let img: HTMLImageElement;

12
src/components/chat/bubbles.ts

@ -859,9 +859,11 @@ export default class ChatBubbles {
str += '.attachment video, .attachment img'; str += '.attachment video, .attachment img';
} }
const hasAspecter = !!this.bubbles[id].querySelector('.media-container-aspecter');
let elements = this.bubbles[id].querySelectorAll(str) as NodeListOf<HTMLElement>; let elements = this.bubbles[id].querySelectorAll(str) as NodeListOf<HTMLElement>;
const parents: Set<HTMLElement> = new Set(); const parents: Set<HTMLElement> = new Set();
Array.from(elements).forEach((element: HTMLElement) => { Array.from(elements).forEach((element: HTMLElement) => {
if(hasAspecter && !findUpClassName(element, 'media-container-aspecter')) return;
let albumItem = findUpClassName(element, 'album-item'); let albumItem = findUpClassName(element, 'album-item');
const parent = albumItem || element.parentElement; const parent = albumItem || element.parentElement;
if(parents.has(parent)) return; if(parents.has(parent)) return;
@ -2330,6 +2332,7 @@ export default class ChatBubbles {
if(size.w === size.h && quoteTextDiv.childElementCount) { if(size.w === size.h && quoteTextDiv.childElementCount) {
bubble.classList.add('is-square-photo'); bubble.classList.add('is-square-photo');
isSquare = true; isSquare = true;
this.appPhotosManager.setAttachmentSize(webpage.photo, preview, 80, 80, false);
} else if(size.h > size.w) { } else if(size.h > size.w) {
bubble.classList.add('is-vertical-photo'); bubble.classList.add('is-vertical-photo');
} }
@ -2338,8 +2341,8 @@ export default class ChatBubbles {
photo: webpage.photo, photo: webpage.photo,
message, message,
container: preview, container: preview,
boxWidth: mediaSizes.active.webpage.width, boxWidth: isSquare ? 0 : mediaSizes.active.webpage.width,
boxHeight: mediaSizes.active.webpage.height, boxHeight: isSquare ? 0 : mediaSizes.active.webpage.height,
isOut, isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware(), middleware: this.getMiddleware(),
@ -2372,8 +2375,9 @@ export default class ChatBubbles {
bubble.classList.add('sticker-animated'); bubble.classList.add('sticker-animated');
} }
let size = bubble.classList.contains('emoji-big') ? 140 : 200; const sizes = mediaSizes.active;
this.appPhotosManager.setAttachmentSize(doc, attachmentDiv, size, size); const size = bubble.classList.contains('emoji-big') ? sizes.emojiSticker : (doc.animated ? sizes.animatedSticker : sizes.staticSticker);
this.appPhotosManager.setAttachmentSize(doc, attachmentDiv, size.width, size.height);
//let preloader = new ProgressivePreloader(attachmentDiv, false); //let preloader = new ProgressivePreloader(attachmentDiv, false);
bubbleContainer.style.height = attachmentDiv.style.height; bubbleContainer.style.height = attachmentDiv.style.height;
bubbleContainer.style.width = attachmentDiv.style.width; bubbleContainer.style.width = attachmentDiv.style.width;

6
src/components/chat/markupTooltip.ts

@ -170,7 +170,11 @@ export default class MarkupTooltip {
private applyLink(e: Event) { private applyLink(e: Event) {
cancelEvent(e); cancelEvent(e);
this.resetSelection(); this.resetSelection();
this.appImManager.chat.input.applyMarkdown('link', this.linkInput.value); let url = this.linkInput.value;
if(url && !RichTextProcessor.matchUrlProtocol(url)) {
url = 'https://' + url;
}
this.appImManager.chat.input.applyMarkdown('link', url);
setTimeout(() => { setTimeout(() => {
this.hide(); this.hide();
}, 0); }, 0);

44
src/components/wrappers.ts

@ -283,7 +283,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
} }
if(!video.parentElement && container) { if(!video.parentElement && container) {
container.append(video); (photoRes?.aspecter || container).append(video);
} }
const cacheContext = appDownloadManager.getCacheContext(doc); const cacheContext = appDownloadManager.getCacheContext(doc);
@ -669,7 +669,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
}) { }) {
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) { if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) {
if(boxWidth && boxHeight && !size && photo._ === 'document') { if(boxWidth && boxHeight && !size && photo._ === 'document') {
size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message && message.message); appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message);
} }
return { return {
@ -681,7 +681,8 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
thumb: null, thumb: null,
full: null full: null
}, },
preloader: null preloader: null,
aspecter: null
}; };
} }
@ -690,7 +691,11 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
if(boxHeight === undefined) boxHeight = mediaSizes.active.regular.height; if(boxHeight === undefined) boxHeight = mediaSizes.active.regular.height;
} }
let loadThumbPromise: Promise<any>; container.classList.add('media-container');
let aspecter = container;
let isFit = true;
let loadThumbPromise: Promise<any> = Promise.resolve();
let thumbImage: HTMLImageElement; let thumbImage: HTMLImageElement;
let image: HTMLImageElement; let image: HTMLImageElement;
// if(withTail) { // if(withTail) {
@ -699,15 +704,35 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
image = new Image(); image = new Image();
if(boxWidth && boxHeight && !size) { // !album if(boxWidth && boxHeight && !size) { // !album
size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message && message.message); const set = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message);
size = set.photoSize;
isFit = set.isFit;
if(!isFit) {
aspecter = document.createElement('div');
aspecter.classList.add('media-container-aspecter');
aspecter.style.width = set.size.width + 'px';
aspecter.style.height = set.size.height + 'px';
const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo, !noBlur, true);
if(gotThumb) {
loadThumbPromise = gotThumb.loadPromise;
const thumbImage = gotThumb.image; // local scope
thumbImage.classList.add('media-photo');
container.append(thumbImage);
}
container.classList.add('media-container-fitted');
container.append(aspecter);
}
} }
const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo, !noBlur); const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo, !noBlur);
if(gotThumb) { if(gotThumb) {
loadThumbPromise = gotThumb.loadPromise; loadThumbPromise = Promise.all([loadThumbPromise, gotThumb.loadPromise]);
thumbImage = gotThumb.image; thumbImage = gotThumb.image;
thumbImage.classList.add('media-photo'); thumbImage.classList.add('media-photo');
container.append(thumbImage); aspecter.append(thumbImage);
} }
// } // }
@ -754,7 +779,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
renderImageFromUrl(image, cacheContext.url, () => { renderImageFromUrl(image, cacheContext.url, () => {
sequentialDom.mutateElement(container, () => { sequentialDom.mutateElement(container, () => {
container.append(image); aspecter.append(image);
fastRaf(() => { fastRaf(() => {
resolve(); resolve();
@ -820,7 +845,8 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
thumb: thumbImage, thumb: thumbImage,
full: image full: image
}, },
preloader preloader,
aspecter
}; };
} }

15
src/helpers/mediaSizes.ts

@ -34,7 +34,10 @@ type MediaTypeSizes = {
regular: MediaSize, regular: MediaSize,
webpage: MediaSize, webpage: MediaSize,
album: MediaSize, album: MediaSize,
esgSticker: MediaSize esgSticker: MediaSize,
animatedSticker: MediaSize,
staticSticker: MediaSize,
emojiSticker: MediaSize
}; };
export enum ScreenSize { export enum ScreenSize {
@ -61,13 +64,19 @@ class MediaSizes extends EventListenerBase<{
regular: makeMediaSize(270, 270), regular: makeMediaSize(270, 270),
webpage: makeMediaSize(270, 200), webpage: makeMediaSize(270, 200),
album: makeMediaSize(270, 0), album: makeMediaSize(270, 0),
esgSticker: makeMediaSize(68, 68) esgSticker: makeMediaSize(68, 68),
animatedSticker: makeMediaSize(160, 160),
staticSticker: makeMediaSize(160, 160),
emojiSticker: makeMediaSize(112, 112)
}, },
desktop: { desktop: {
regular: makeMediaSize(400, 320), regular: makeMediaSize(400, 320),
webpage: makeMediaSize(400, 320), webpage: makeMediaSize(400, 320),
album: makeMediaSize(420, 0), album: makeMediaSize(420, 0),
esgSticker: makeMediaSize(80, 80) esgSticker: makeMediaSize(80, 80),
animatedSticker: makeMediaSize(200, 200),
staticSticker: makeMediaSize(200, 200),
emojiSticker: makeMediaSize(112, 112)
} }
}; };

38
src/lib/appManagers/appPhotosManager.ts

@ -216,7 +216,7 @@ export class AppPhotosManager {
return {image, loadPromise}; return {image, loadPromise};
} }
public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number, noZoom = true, hasText?: boolean) { public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number, noZoom = true, message?: any) {
const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight); const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight);
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div); //console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
@ -227,13 +227,29 @@ export class AppPhotosManager {
size = makeMediaSize('w' in photoSize ? photoSize.w : 100, 'h' in photoSize ? photoSize.h : 100); size = makeMediaSize('w' in photoSize ? photoSize.w : 100, 'h' in photoSize ? photoSize.h : 100);
} }
const boxSize = makeMediaSize(boxWidth, boxHeight); let boxSize = makeMediaSize(boxWidth, boxHeight);
size = size.aspect(boxSize, noZoom); boxSize = size = size.aspect(boxSize, noZoom);
// /* if(hasText) { let isFit = true;
// w = Math.max(boxWidth, w);
// } */ if(photo._ === 'photo' || ['video', 'gif'].includes(photo.type)) {
if(boxSize.width < 200 && boxSize.height < 200) { // make at least one side this big
boxSize = size = size.aspectCovered(makeMediaSize(200, 200));
}
if(message && (message.message || message.media.webpage || message.replies)) { // make sure that bubble block is human-readable
if(boxSize.width < 320) {
boxSize = makeMediaSize(320, boxSize.height);
isFit = false;
}
}
if(isFit && boxSize.width < 120) { // if image is too narrow
boxSize = makeMediaSize(120, boxSize.height);
isFit = false;
}
}
// if(element instanceof SVGForeignObjectElement) { // if(element instanceof SVGForeignObjectElement) {
// element.setAttributeNS(null, 'width', '' + w); // element.setAttributeNS(null, 'width', '' + w);
@ -241,16 +257,16 @@ export class AppPhotosManager {
// //console.log('set dimensions to svg element:', element, w, h); // //console.log('set dimensions to svg element:', element, w, h);
// } else { // } else {
element.style.width = size.width + 'px'; element.style.width = boxSize.width + 'px';
element.style.height = size.height + 'px'; element.style.height = boxSize.height + 'px';
// } // }
return photoSize; return {photoSize, size, isFit};
} }
public getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, useBlur: boolean): ReturnType<AppPhotosManager['getImageFromStrippedThumb']> { public getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, useBlur: boolean, ignoreCache = false): ReturnType<AppPhotosManager['getImageFromStrippedThumb']> {
const cacheContext = appDownloadManager.getCacheContext(photo); const cacheContext = appDownloadManager.getCacheContext(photo);
if(!cacheContext.downloaded || (photo as MyDocument).type === 'video' || (photo as MyDocument).type === 'gif') { if(!cacheContext.downloaded || (['video', 'gif'] as MyDocument['type'][]).includes((photo as MyDocument).type) || ignoreCache) {
if(photo._ === 'document' && cacheContext.downloaded) { if(photo._ === 'document' && cacheContext.downloaded) {
return null; return null;
} }

80
src/lib/richtextprocessor.ts

@ -54,17 +54,19 @@ const alphaCharsRegExp = 'a-z' +
const alphaNumericRegExp = '0-9\_' + alphaCharsRegExp; const alphaNumericRegExp = '0-9\_' + alphaCharsRegExp;
const domainAddChars = '\u00b7'; const domainAddChars = '\u00b7';
// Based on Regular Expression for URL validation by Diego Perini // Based on Regular Expression for URL validation by Diego Perini
const urlRegExp = '((?:https?|ftp)://|mailto:)?' + const urlAlphanumericRegExpPart = '[' + alphaCharsRegExp + '0-9]';
const urlProtocolRegExpPart = '((?:https?|ftp)://|mailto:)?';
const urlRegExp = urlProtocolRegExpPart +
// user:pass authentication // user:pass authentication
'(?:\\S{1,64}(?::\\S{0,64})?@)?' + '(?:' + urlAlphanumericRegExpPart + '{1,64}(?::' + urlAlphanumericRegExpPart + '{0,64})?@)?' +
'(?:' + '(?:' +
// sindresorhus/ip-regexp // sindresorhus/ip-regexp
'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}' + '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}' +
'|' + '|' +
// host name // host name
'[' + alphaCharsRegExp + '0-9][' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}' + urlAlphanumericRegExpPart + '[' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}' +
// domain name // domain name
'(?:\\.[' + alphaCharsRegExp + '0-9][' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}){0,10}' + '(?:\\.' + urlAlphanumericRegExpPart + '[' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}){0,10}' +
// TLD identifier // TLD identifier
'(?:\\.(xn--[0-9a-z]{2,16}|[' + alphaCharsRegExp + ']{2,24}))' + '(?:\\.(xn--[0-9a-z]{2,16}|[' + alphaCharsRegExp + ']{2,24}))' +
')' + ')' +
@ -72,9 +74,11 @@ const urlRegExp = '((?:https?|ftp)://|mailto:)?' +
'(?::\\d{2,5})?' + '(?::\\d{2,5})?' +
// resource path // resource path
'(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>"\'])?)?'; '(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>"\'])?)?';
const urlProtocolRegExp = new RegExp('^' + urlProtocolRegExpPart.slice(0, -1), 'i');
const urlAnyProtocolRegExp = /^((?:.+?):\/\/|mailto:)/;
const usernameRegExp = '[a-zA-Z\\d_]{5,32}'; const usernameRegExp = '[a-zA-Z\\d_]{5,32}';
const botCommandRegExp = '\\/([a-zA-Z\\d_]{1,32})(?:@(' + usernameRegExp + '))?(\\b|$)'; const botCommandRegExp = '\\/([a-zA-Z\\d_]{1,32})(?:@(' + usernameRegExp + '))?(\\b|$)';
const fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i') const fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i');
const emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
//const markdownTestRegExp = /[`_*@~]/; //const markdownTestRegExp = /[`_*@~]/;
const markdownRegExp = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s|\x01)(`|~~|\*\*|__|_-_)([^\n]+?)\7([\x01\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m; const markdownRegExp = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s|\x01)(`|~~|\*\*|__|_-_)([^\n]+?)\7([\x01\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m;
@ -91,7 +95,7 @@ const siteMentions: {[siteName: string]: string} = {
Instagram: 'https://instagram.com/{1}/', Instagram: 'https://instagram.com/{1}/',
GitHub: 'https://github.com/{1}' GitHub: 'https://github.com/{1}'
}; };
const markdownEntities: {[markdown: string]: any} = { const markdownEntities: {[markdown: string]: MessageEntity['_']} = {
'`': 'messageEntityCode', '`': 'messageEntityCode',
'``': 'messageEntityPre', '``': 'messageEntityPre',
'**': 'messageEntityBold', '**': 'messageEntityBold',
@ -100,6 +104,11 @@ const markdownEntities: {[markdown: string]: any} = {
'_-_': 'messageEntityUnderline' '_-_': 'messageEntityUnderline'
}; };
const passConflictingEntities: Set<MessageEntity['_']> = new Set();
for(let i in markdownEntities) {
passConflictingEntities.add(markdownEntities[i]);
}
namespace RichTextProcessor { namespace RichTextProcessor {
export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */; export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */;
@ -120,11 +129,11 @@ namespace RichTextProcessor {
} }
export function parseEntities(text: string) { export function parseEntities(text: string) {
var match; let match: any;
var raw = text, url; let raw = text;
const entities: MessageEntity[] = []; const entities: MessageEntity[] = [];
let matchIndex; let matchIndex;
var rawOffset = 0; let rawOffset = 0;
// var start = tsNow() // var start = tsNow()
while((match = raw.match(fullRegExp))) { while((match = raw.match(fullRegExp))) {
matchIndex = rawOffset + match.index; matchIndex = rawOffset + match.index;
@ -145,19 +154,19 @@ namespace RichTextProcessor {
length: match[4].length length: match[4].length
}); });
} else { } else {
var url: any = false; let url: string;
var protocol = match[5]; let protocol = match[5];
var tld = match[6]; const tld = match[6];
var excluded = ''; // let excluded = '';
if(tld) { // URL if(tld) { // URL
if(!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) { if(!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) {
protocol = 'http://'; protocol = 'http://';
} }
if(protocol) { if(protocol) {
var balanced = checkBrackets(match[4]); const balanced = checkBrackets(match[4]);
if (balanced.length !== match[4].length) { if(balanced.length !== match[4].length) {
excluded = match[4].substring(balanced.length); // excluded = match[4].substring(balanced.length);
match[4] = balanced; match[4] = balanced;
} }
@ -167,7 +176,7 @@ namespace RichTextProcessor {
url = (match[5] ? '' : 'http://') + match[4]; url = (match[5] ? '' : 'http://') + match[4];
} }
if (url) { if(url) {
entities.push({ entities.push({
_: 'messageEntityUrl', _: 'messageEntityUrl',
offset: matchIndex, offset: matchIndex,
@ -183,7 +192,7 @@ namespace RichTextProcessor {
}); });
} else if(match[8]) { // Emoji } else if(match[8]) { // Emoji
//console.log('hit', match[8]); //console.log('hit', match[8]);
let emojiCoords = getEmojiSpritesheetCoords(match[8]); const emojiCoords = getEmojiSpritesheetCoords(match[8]);
if(emojiCoords) { if(emojiCoords) {
entities.push({ entities.push({
_: 'messageEntityEmoji', _: 'messageEntityEmoji',
@ -233,7 +242,7 @@ namespace RichTextProcessor {
const entities: MessageEntity[] = []; const entities: MessageEntity[] = [];
let pushedEntity = false; let pushedEntity = false;
const pushEntity = (entity: MessageEntity) => !findSameEntity(currentEntities, entity) ? (entities.push(entity), pushedEntity = true) : pushedEntity = false; const pushEntity = (entity: MessageEntity) => !findConflictingEntity(currentEntities, entity) ? (entities.push(entity), pushedEntity = true) : pushedEntity = false;
let raw = text; let raw = text;
let match; let match;
@ -273,7 +282,7 @@ namespace RichTextProcessor {
const isSOH = match[6] === '\x01'; const isSOH = match[6] === '\x01';
entity = { entity = {
_: markdownEntities[match[7]], _: markdownEntities[match[7]] as (MessageEntity.messageEntityBold | MessageEntity.messageEntityCode | MessageEntity.messageEntityItalic)['_'],
//offset: matchIndex + match[6].length, //offset: matchIndex + match[6].length,
offset: matchIndex + (isSOH ? 0 : match[6].length), offset: matchIndex + (isSOH ? 0 : match[6].length),
length: text.length length: text.length
@ -341,17 +350,25 @@ namespace RichTextProcessor {
return newText; return newText;
} }
export function findSameEntity(currentEntities: MessageEntity[], newEntity: MessageEntity) { export function findConflictingEntity(currentEntities: MessageEntity[], newEntity: MessageEntity) {
return currentEntities.find(currentEntity => { return currentEntities.find(currentEntity => {
return newEntity._ === currentEntity._ && const isConflictingTypes = newEntity._ === currentEntity._ ||
newEntity.offset >= currentEntity.offset && (!passConflictingEntities.has(newEntity._) && !passConflictingEntities.has(currentEntity._));
if(!isConflictingTypes) {
return false;
}
const isConflictingOffset = newEntity.offset >= currentEntity.offset &&
(newEntity.length + newEntity.offset) <= (currentEntity.length + currentEntity.offset); (newEntity.length + newEntity.offset) <= (currentEntity.length + currentEntity.offset);
return isConflictingOffset;
}); });
} }
export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[]) { export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[]) {
const filtered = newEntities.filter(e => { const filtered = newEntities.filter(e => {
return !findSameEntity(currentEntities, e); return !findConflictingEntity(currentEntities, e);
}); });
currentEntities.push(...filtered); currentEntities.push(...filtered);
@ -536,7 +553,7 @@ namespace RichTextProcessor {
if(!(options.noLinks && !passEntities[entity._])) { if(!(options.noLinks && !passEntities[entity._])) {
const entityText = text.substr(entity.offset, entity.length); const entityText = text.substr(entity.offset, entity.length);
let inner: string; // let inner: string;
let url: string; let url: string;
let masked = false; let masked = false;
if(entity._ === 'messageEntityTextUrl') { if(entity._ === 'messageEntityTextUrl') {
@ -548,6 +565,9 @@ namespace RichTextProcessor {
nextEntity.length === entity.length && nextEntity.length === entity.length &&
nextEntity.offset === entity.offset) { nextEntity.offset === entity.offset) {
i++; i++;
}
if(url !== entityText) {
masked = true; masked = true;
} }
} else { } else {
@ -721,7 +741,7 @@ namespace RichTextProcessor {
} }
export function wrapUrl(url: string, unsafe?: number | boolean): string { export function wrapUrl(url: string, unsafe?: number | boolean): string {
if(!url.match(/^https?:\/\//i)) { if(!matchUrlProtocol(url)) {
url = 'https://' + url; url = 'https://' + url;
} }
@ -729,7 +749,7 @@ namespace RichTextProcessor {
let telescoPeMatch; let telescoPeMatch;
/* if(unsafe === 2) { /* if(unsafe === 2) {
url = 'tg://unsafe_url?url=' + encodeURIComponent(url); url = 'tg://unsafe_url?url=' + encodeURIComponent(url);
} else */if((tgMeMatch = url.match(/^https?:\/\/t(?:elegram)?\.me\/(.+)/))) { } else */if((tgMeMatch = url.match(/^(?:https?:\/\/)?t(?:elegram)?\.me\/(.+)/))) {
const fullPath = tgMeMatch[1]; const fullPath = tgMeMatch[1];
const path = fullPath.split('/'); const path = fullPath.split('/');
switch(path[0]) { switch(path[0]) {
@ -771,7 +791,7 @@ namespace RichTextProcessor {
break; break;
} }
} else if((telescoPeMatch = url.match(/^https?:\/\/telesco\.pe\/([^/?]+)\/(\d+)/))) { } else if((telescoPeMatch = url.match(/^(?:https?:\/\/)?telesco\.pe\/([^/?]+)\/(\d+)/))) {
url = 'tg://resolve?domain=' + telescoPeMatch[1] + '&post=' + telescoPeMatch[2]; url = 'tg://resolve?domain=' + telescoPeMatch[1] + '&post=' + telescoPeMatch[2];
}/* else if(unsafe) { }/* else if(unsafe) {
url = 'tg://unsafe_url?url=' + encodeURIComponent(url); url = 'tg://unsafe_url?url=' + encodeURIComponent(url);
@ -779,6 +799,10 @@ namespace RichTextProcessor {
return url; return url;
} }
export function matchUrlProtocol(text: string) {
return !text ? null : text.match(urlAnyProtocolRegExp);
}
export function matchUrl(text: string) { export function matchUrl(text: string) {
return !text ? null : text.match(urlRegExp); return !text ? null : text.match(urlRegExp);

19
src/scss/partials/_chatBubble.scss

@ -604,6 +604,21 @@ $bubble-margin: .25rem;
} }
} }
.media-container {
&-aspecter {
position: relative;
margin: 0 auto;
}
&-fitted {
background-color: transparent !important;
.thumbnail {
opacity: .8;
}
}
}
.preloader-container { .preloader-container {
z-index: 2; z-index: 2;
} }
@ -798,11 +813,11 @@ $bubble-margin: .25rem;
} }
} }
&.is-vertical-photo { /* &.is-vertical-photo {
.bubble-content { .bubble-content {
width: fit-content; width: fit-content;
} }
} } */
.reply { .reply {
padding: 4px; padding: 4px;

Loading…
Cancel
Save