Browse Source

Support pasting styled text

master
morethanwords 2 years ago
parent
commit
318aaf6f4e
  1. 5
      src/components/chat/bubbles.ts
  2. 4
      src/components/chat/input.ts
  3. 37
      src/components/inputField.ts
  4. 109
      src/helpers/dom/getRichElementValue.ts
  5. 10
      src/helpers/dom/getRichValue.ts
  6. 7
      src/lib/richtextprocessor.ts

5
src/components/chat/bubbles.ts

@ -2918,7 +2918,10 @@ export default class ChatBubbles { @@ -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 { @@ -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;

4
src/components/chat/input.ts

@ -2405,6 +2405,10 @@ export default class ChatInput { @@ -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();

37
src/components/inputField.ts

@ -9,6 +9,7 @@ import findUpAttribute from "../helpers/dom/findUpAttribute"; @@ -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 = () => { @@ -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 text: string, entities: MessageEntity[];
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});
// @ts-ignore
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;
// console.log('messageInput paste after', 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');
// @ts-ignore
//let html = (e.originalEvent || e).clipboardData.getData('text/html');
entities = RichTextProcessor.parseEntities(text);
entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
}
// @ts-ignore
//console.log('paste text', text, );
text = RichTextProcessor.wrapDraftText(text, {entities});
window.document.execCommand('insertHTML', false, text);
});

109
src/helpers/dom/getRichElementValue.ts

@ -18,7 +18,7 @@ export type MarkdownTag = { @@ -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} = { @@ -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[], @@ -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[], @@ -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[], @@ -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;
}
}

10
src/helpers/dom/getRichValue.ts

@ -27,7 +27,15 @@ export default function getRichValue(field: HTMLElement, withEntities = true) { @@ -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);
}

7
src/lib/richtextprocessor.ts

@ -345,9 +345,10 @@ namespace RichTextProcessor { @@ -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…
Cancel
Save