Support pasting styled text
This commit is contained in:
parent
135e8cdc34
commit
318aaf6f4e
@ -2918,7 +2918,10 @@ export default class ChatBubbles {
|
||||
const html = RichTextProcessor.wrapRichText(webpage.url);
|
||||
const a: HTMLAnchorElement = htmlToDocumentFragment(html).firstElementChild as any;
|
||||
a.classList.add('webpage-name');
|
||||
// const b = document.createElement('b');
|
||||
setInnerHTML(a, RichTextProcessor.wrapEmojiText(webpage.site_name));
|
||||
// a.textContent = '';
|
||||
// a.append(b);
|
||||
quoteTextDiv.append(a);
|
||||
t = a;
|
||||
}
|
||||
@ -2926,6 +2929,8 @@ export default class ChatBubbles {
|
||||
if(webpage.rTitle) {
|
||||
let titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('title');
|
||||
// const b = document.createElement('b');
|
||||
// titleDiv.append(b);
|
||||
setInnerHTML(titleDiv, webpage.rTitle);
|
||||
quoteTextDiv.append(titleDiv);
|
||||
t = titleDiv;
|
||||
|
@ -2405,6 +2405,10 @@ export default class ChatInput {
|
||||
replyParent.insertBefore(newReply, replyParent.lastElementChild);
|
||||
}
|
||||
|
||||
if(type === 'webpage') {
|
||||
newReply.style.cursor = 'default';
|
||||
}
|
||||
|
||||
if(!this.chat.container.classList.contains('is-helper-active')) {
|
||||
this.chat.container.classList.add('is-helper-active');
|
||||
this.t();
|
||||
|
@ -9,6 +9,7 @@ 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 { MessageEntity } from "../layer";
|
||||
import { i18n, LangPackKey, _i18n } from "../lib/langPack";
|
||||
import RichTextProcessor from "../lib/richtextprocessor";
|
||||
import SetTransition from "./singleTransition";
|
||||
@ -18,27 +19,33 @@ let init = () => {
|
||||
if(!findUpAttribute(e.target, 'contenteditable="true"')) {
|
||||
return;
|
||||
}
|
||||
//console.log('document paste');
|
||||
|
||||
//console.log('messageInput paste');
|
||||
|
||||
e.preventDefault();
|
||||
// @ts-ignore
|
||||
let text = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
|
||||
let entities = RichTextProcessor.parseEntities(text);
|
||||
//console.log('messageInput paste', text, entities);
|
||||
entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
|
||||
//text = RichTextProcessor.wrapEmojiText(text);
|
||||
text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true, wrappingDraft: true});
|
||||
|
||||
// console.log('messageInput paste after', text);
|
||||
let text: string, entities: MessageEntity[];
|
||||
|
||||
// @ts-ignore
|
||||
//let html = (e.originalEvent || e).clipboardData.getData('text/html');
|
||||
const html: string = (e.originalEvent || e).clipboardData.getData('text/html');
|
||||
if(html.trim()) {
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = html;
|
||||
|
||||
const richValue = getRichValue(span, true);
|
||||
text = richValue.value;
|
||||
entities = richValue.entities;
|
||||
|
||||
// @ts-ignore
|
||||
//console.log('paste text', text, );
|
||||
let entities2 = RichTextProcessor.parseEntities(text);
|
||||
entities2 = entities2.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
|
||||
RichTextProcessor.mergeEntities(entities, entities2);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
text = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
|
||||
entities = RichTextProcessor.parseEntities(text);
|
||||
entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
|
||||
}
|
||||
|
||||
text = RichTextProcessor.wrapDraftText(text, {entities});
|
||||
|
||||
window.document.execCommand('insertHTML', false, text);
|
||||
});
|
||||
|
||||
|
@ -18,7 +18,7 @@ export type MarkdownTag = {
|
||||
};
|
||||
export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
|
||||
bold: {
|
||||
match: '[style*="font-weight"], b',
|
||||
match: '[style*="bold"], [style*="font-weight: 700"], [style*="font-weight: 600"], [style*="font-weight:700"], [style*="font-weight:600"], b, strong',
|
||||
entityName: 'messageEntityBold'
|
||||
},
|
||||
underline: {
|
||||
@ -47,9 +47,46 @@ export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
const nodeValue = node.nodeValue;
|
||||
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));
|
||||
@ -57,35 +94,44 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
|
||||
line.push(nodeValue);
|
||||
}
|
||||
|
||||
if(entities && nodeValue.trim()) {
|
||||
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 && closest.getAttribute('contenteditable') === null) {
|
||||
if(tag.entityName === 'messageEntityTextUrl') {
|
||||
entities.push({
|
||||
_: tag.entityName,
|
||||
url: (parentElement 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: parentElement.dataset.follow.toUserId()
|
||||
});
|
||||
} else {
|
||||
entities.push({
|
||||
_: tag.entityName as any,
|
||||
offset: offset.offset,
|
||||
length: nodeValue.length
|
||||
});
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,11 +146,12 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
|
||||
return;
|
||||
}
|
||||
|
||||
const isSelected = (selNode === node);
|
||||
const isBlock = node.tagName === 'DIV' || node.tagName === 'P';
|
||||
if(isBlock && line.length || node.tagName === 'BR') {
|
||||
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) {
|
||||
@ -130,5 +177,11 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
|
||||
if(isBlock && line.length) {
|
||||
lines.push(line.join(''));
|
||||
line.splice(0, line.length);
|
||||
++offset.offset;
|
||||
}
|
||||
|
||||
if(node.tagName === 'P') {
|
||||
lines.push('');
|
||||
++offset.offset;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,15 @@ export default function getRichValue(field: HTMLElement, withEntities = true) {
|
||||
let value = lines.join('\n');
|
||||
value = value.replace(/\u00A0/g, ' ');
|
||||
|
||||
if(entities) {
|
||||
if(entities?.length) {
|
||||
// ! cannot do that here because have the same check before the sending in RichTextProcessor.parseMarkdown
|
||||
/* const entity = entities[entities.length - 1];
|
||||
const length = value.length;
|
||||
const trimmedLength = value.trimRight().length;
|
||||
if(length !== trimmedLength) {
|
||||
entity.length -= length - trimmedLength;
|
||||
} */
|
||||
|
||||
RichTextProcessor.combineSameEntities(entities);
|
||||
}
|
||||
|
||||
|
@ -345,9 +345,10 @@ namespace RichTextProcessor {
|
||||
entities.splice(0, entities.length);
|
||||
}
|
||||
|
||||
if(!entities.length && !noTrim) {
|
||||
newText = newText.trim();
|
||||
}
|
||||
// ! idk what it was here for
|
||||
// if(!entities.length && !noTrim) {
|
||||
// newText = newText.trim();
|
||||
// }
|
||||
|
||||
mergeEntities(currentEntities, entities);
|
||||
combineSameEntities(currentEntities);
|
||||
|
Loading…
Reference in New Issue
Block a user