tweb-i2p/src/helpers/dom/getRichElementValue.ts
2022-01-14 19:26:29 +04:00

188 lines
5.3 KiB
TypeScript

/*
* 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 <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import { MessageEntity } from "../../layer";
export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link' | 'mentionName';
export type MarkdownTag = {
match: string,
entityName: 'messageEntityBold' | 'messageEntityUnderline' | 'messageEntityItalic' | 'messageEntityPre' | 'messageEntityStrike' | 'messageEntityTextUrl' | 'messageEntityMentionName';
};
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',
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',
entityName: 'messageEntityStrike'
},
link: {
match: 'A:not(.follow)',
entityName: 'messageEntityTextUrl'
},
mentionName: {
match: 'A.follow',
entityName: 'messageEntityMentionName'
}
};
const tabulationMatch = '[style*="table-cell"]';
/* 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',
]);
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');
}
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');
}
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;
}
}