Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
5.5 KiB
163 lines
5.5 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
// Thanks to https://stackoverflow.com/a/49349813 |
|
import { clamp } from "../helpers/number"; |
|
|
|
/** |
|
* Attibute modifier to create middle ellipsis |
|
* When the attribute value is left blank the ellipsis will be in the middle |
|
* When positive the attribute value will be used as a percentage |
|
* When negative the attribute value will be used as character index counted from the end |
|
* @example |
|
* <div data-middle-ellipsis>A Javascript solution to middle ellipsis</div> |
|
* <div data-middle-ellipsis="20">A Javascript solution to middle ellipsis</div> |
|
* <div data-middle-ellipsis="-3">A Javascript solution to middle ellipsis</div> |
|
*/ |
|
const ellipsis = '…'; |
|
const map: Map<HTMLElement, { |
|
text: string, |
|
textLength: number, |
|
from: number, |
|
multiplier: number, |
|
font: string, |
|
textWidth: number, |
|
elementWidth: number |
|
}> = new Map(); |
|
|
|
const testQueue: Set<HTMLElement> = new Set(); |
|
export const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif'; |
|
const fontSize = '16px'; |
|
let timeoutId: number; |
|
|
|
const setTestQueue = () => { |
|
cancelAnimationFrame(timeoutId); |
|
timeoutId = window.requestAnimationFrame(testQueueElements); |
|
}; |
|
|
|
const testQueueElements = () => { |
|
testQueue.forEach(testElement); |
|
testQueue.clear(); |
|
}; |
|
|
|
window.addEventListener('resize', () => { |
|
for(const [key] of map) { |
|
testQueue.add(key); |
|
} |
|
|
|
setTestQueue(); |
|
}, {capture: true, passive: true}); |
|
|
|
const testElement = (element: HTMLElement) => { |
|
//const perf = performance.now(); |
|
// do not recalculate variables a second time |
|
let mapped = map.get(element); |
|
const firstTime = !mapped; |
|
|
|
let {text, textLength, from, multiplier, font, textWidth, elementWidth} = mapped || {}; |
|
//console.log('[MEE] testElement got mapped', mapped); |
|
|
|
if(firstTime) { |
|
text = element.textContent; |
|
textLength = text.length; |
|
from = /* parseFloat(element.getAttribute(attributeName)) || */50; |
|
multiplier = from > 0 && from / 100; |
|
|
|
//const perf = performance.now(); |
|
font = `${element.dataset.fontWeight || 400} ${fontSize} ${fontFamily}`; |
|
/* const computedStyle = window.getComputedStyle(elm, null); |
|
font = `${computedStyle.getPropertyValue('font-weight')} ${computedStyle.getPropertyValue('font-size')} ${computedStyle.getPropertyValue('font-family')}`; */ |
|
//console.log('testMiddleEllipsis get computed style:', performance.now() - perf, font); |
|
|
|
textWidth = getTextWidth(text, font); |
|
//const perf = performance.now(); |
|
elementWidth = element.getBoundingClientRect().width; |
|
//console.log('testMiddleEllipsis get offsetWidth:', performance.now() - perf, font); |
|
mapped = {text, textLength, from, multiplier, font, textWidth, elementWidth}; |
|
map.set(element, mapped); |
|
|
|
//console.log('[MEE] testElement map set', element); |
|
} |
|
|
|
const newElementWidth = element.getBoundingClientRect().width; |
|
const widthChanged = firstTime || elementWidth !== newElementWidth; |
|
!firstTime && widthChanged && (mapped.elementWidth = elementWidth = newElementWidth); |
|
|
|
if(widthChanged) { |
|
if(textWidth > elementWidth) { |
|
element.setAttribute('title', text); |
|
let smallerText = text; |
|
let smallerWidth = elementWidth; |
|
while(smallerText.length > 3) { |
|
let smallerTextLength = smallerText.length; |
|
const half = multiplier && |
|
clamp(multiplier * smallerTextLength << 0, 1, smallerTextLength - 2) || |
|
Math.max(smallerTextLength + from - 1, 1); |
|
const half1 = smallerText.substr(0, half).replace(/\s*$/,''); |
|
const half2 = smallerText.substr(half + 1).replace(/^\s*/,''); |
|
smallerText = half1 + half2; |
|
smallerWidth = getTextWidth(smallerText + ellipsis, font); |
|
if(smallerWidth < elementWidth) { |
|
element.textContent = half1 + ellipsis + half2; |
|
break; |
|
} |
|
} |
|
|
|
// * set new width after cutting text |
|
mapped.elementWidth = element.getBoundingClientRect().width; |
|
//mapped.textWidth = smallerWidth; |
|
} else { |
|
element.removeAttribute('title'); |
|
} |
|
} |
|
|
|
//console.log('testMiddleEllipsis for element:', elm, performance.now() - perf); |
|
}; |
|
|
|
let context: CanvasRenderingContext2D; |
|
/** |
|
* Get the text width |
|
* @param {string} text |
|
* @param {string} font |
|
*/ |
|
function getTextWidth(text: string, font: string) { |
|
//const perf = performance.now(); |
|
if(!context) { |
|
const canvas = document.createElement('canvas'); |
|
context = canvas.getContext('2d'); |
|
context.font = font; |
|
} |
|
|
|
//context.font = font; |
|
const metrics = context.measureText(text); |
|
//console.log('getTextWidth perf:', performance.now() - perf); |
|
return metrics.width; |
|
//return Math.round(metrics.width); |
|
} |
|
|
|
export class MiddleEllipsisElement extends HTMLElement { |
|
constructor() { |
|
super(); |
|
} |
|
|
|
connectedCallback() { |
|
//console.log('[MEE]: connectedCallback before', map.has(this), testQueue.has(this), map.size, this.textContent, map); |
|
|
|
map.set(this, null); |
|
testQueue.add(this); |
|
setTestQueue(); |
|
//testElement(this); |
|
|
|
//console.log('[MEE]: connectedCallback after', map.has(this), map.size, testQueue.has(this), testQueue.size); |
|
} |
|
|
|
disconnectedCallback() { |
|
const deleted = map.delete(this); |
|
//console.log('[MEE]: disconnectedCallback', deleted, map.has(this), map.size, this.textContent, map); |
|
} |
|
} |
|
|
|
customElements.define("middle-ellipsis-element", MiddleEllipsisElement);
|
|
|