/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE * * Originally from: * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ import { MessageEntity } from "../../layer"; export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link' | 'mentionName' | 'spoiler'; export type MarkdownTag = { match: string, entityName: Extract; }; // https://core.telegram.org/bots/api#html-style export const markdownTags: {[type in MarkdownType]: MarkdownTag} = { bold: { match: '[style*="bold"], [style*="font-weight: 700"], [style*="font-weight: 600"], [style*="font-weight:700"], [style*="font-weight:600"], b, strong', entityName: 'messageEntityBold' }, underline: { match: '[style*="underline"], u, ins', entityName: 'messageEntityUnderline' }, italic: { match: '[style*="italic"], i, em', entityName: 'messageEntityItalic' }, monospace: { match: '[style*="monospace"], [face="monospace"], pre', entityName: 'messageEntityPre' }, strikethrough: { match: '[style*="line-through"], strike, del, s', entityName: 'messageEntityStrike' }, link: { match: 'A:not(.follow)', entityName: 'messageEntityTextUrl' }, mentionName: { match: 'A.follow', entityName: 'messageEntityMentionName' }, spoiler: { match: '[style*="spoiler"]', entityName: 'messageEntitySpoiler' } }; const tabulationMatch = '[style*="table-cell"], th, td'; /* export function getDepth(child: Node, container?: Node) { let depth = 0; do { if(child === container) { return depth; } ++depth; } while((child = child.parentNode) !== null); return depth; } */ const BLOCK_TAG_NAMES = new Set([ 'DIV', 'P', 'BR', 'LI', 'SECTION', 'H6', 'H5', 'H4', 'H3', 'H2', 'H1', 'TR' ]); export default function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number, entities?: MessageEntity[], offset = {offset: 0}) { if(node.nodeType === 3) { // TEXT let nodeValue = node.nodeValue; /* const tabulation = node.parentElement?.closest(tabulationMatch + ', [contenteditable]'); if(tabulation?.getAttribute('contenteditable') === null) { nodeValue += ' '; // line.push('\t'); // ++offset.offset; } */ if(selNode === node) { line.push(nodeValue.substr(0, selOffset) + '\x01' + nodeValue.substr(selOffset)); } else { line.push(nodeValue); } if(entities && nodeValue.length) { if(node.parentNode) { const parentElement = node.parentElement; // let closestTag: MarkdownTag, closestElementByTag: Element, closestDepth = Infinity; for(const type in markdownTags) { const tag = markdownTags[type as MarkdownType]; const closest = parentElement.closest(tag.match + ', [contenteditable]'); if(closest?.getAttribute('contenteditable') !== null) { /* const depth = getDepth(closest, parentElement.closest('[contenteditable]')); if(closestDepth > depth) { closestDepth = depth; closestTag = tag; closestElementByTag = closest; } */ continue; } if(tag.entityName === 'messageEntityTextUrl') { entities.push({ _: tag.entityName, url: (closest as HTMLAnchorElement).href, offset: offset.offset, length: nodeValue.length }); } else if(tag.entityName === 'messageEntityMentionName') { entities.push({ _: tag.entityName, offset: offset.offset, length: nodeValue.length, user_id: (closest as HTMLElement).dataset.follow.toUserId() }); } else { entities.push({ _: tag.entityName as any, offset: offset.offset, length: nodeValue.length }); } } } } offset.offset += nodeValue.length; return; } if(node.nodeType !== 1) { // NON-ELEMENT return; } const isSelected = selNode === node; const isBlock = BLOCK_TAG_NAMES.has(node.tagName); if(isBlock && line.length) { lines.push(line.join('')); line.splice(0, line.length); ++offset.offset; } else if(node instanceof HTMLImageElement) { const alt = node.alt; if(alt) { line.push(alt); offset.offset += alt.length; } } if(isSelected && !selOffset) { line.push('\x01'); } const isTableCell = node.matches(tabulationMatch); const wasEntitiesLength = entities?.length; let curChild = node.firstChild as HTMLElement; while(curChild) { getRichElementValue(curChild, lines, line, selNode, selOffset, entities, offset); curChild = curChild.nextSibling as any; } if(isSelected && selOffset) { line.push('\x01'); } if(isTableCell && node.nextSibling) { line.push(' '); ++offset.offset; // * combine entities such as url after adding space if(wasEntitiesLength !== undefined) { for(let i = wasEntitiesLength, length = entities.length; i < length; ++i) { ++entities[i].length; } } } const wasLength = line.length; if(isBlock && wasLength) { lines.push(line.join('')); line.splice(0, wasLength); ++offset.offset; } if(wasLength && node.tagName === 'P' && node.nextSibling) { lines.push(''); ++offset.offset; } }