Browse Source

Support pasting styled text

master
morethanwords 3 years ago
parent
commit
318aaf6f4e
  1. 5
      src/components/chat/bubbles.ts
  2. 4
      src/components/chat/input.ts
  3. 35
      src/components/inputField.ts
  4. 73
      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 {
const html = RichTextProcessor.wrapRichText(webpage.url); const html = RichTextProcessor.wrapRichText(webpage.url);
const a: HTMLAnchorElement = htmlToDocumentFragment(html).firstElementChild as any; const a: HTMLAnchorElement = htmlToDocumentFragment(html).firstElementChild as any;
a.classList.add('webpage-name'); a.classList.add('webpage-name');
// const b = document.createElement('b');
setInnerHTML(a, RichTextProcessor.wrapEmojiText(webpage.site_name)); setInnerHTML(a, RichTextProcessor.wrapEmojiText(webpage.site_name));
// a.textContent = '';
// a.append(b);
quoteTextDiv.append(a); quoteTextDiv.append(a);
t = a; t = a;
} }
@ -2926,6 +2929,8 @@ export default class ChatBubbles {
if(webpage.rTitle) { if(webpage.rTitle) {
let titleDiv = document.createElement('div'); let titleDiv = document.createElement('div');
titleDiv.classList.add('title'); titleDiv.classList.add('title');
// const b = document.createElement('b');
// titleDiv.append(b);
setInnerHTML(titleDiv, webpage.rTitle); setInnerHTML(titleDiv, webpage.rTitle);
quoteTextDiv.append(titleDiv); quoteTextDiv.append(titleDiv);
t = titleDiv; t = titleDiv;

4
src/components/chat/input.ts

@ -2405,6 +2405,10 @@ export default class ChatInput {
replyParent.insertBefore(newReply, replyParent.lastElementChild); replyParent.insertBefore(newReply, replyParent.lastElementChild);
} }
if(type === 'webpage') {
newReply.style.cursor = 'default';
}
if(!this.chat.container.classList.contains('is-helper-active')) { if(!this.chat.container.classList.contains('is-helper-active')) {
this.chat.container.classList.add('is-helper-active'); this.chat.container.classList.add('is-helper-active');
this.t(); this.t();

35
src/components/inputField.ts

@ -9,6 +9,7 @@ import findUpAttribute from "../helpers/dom/findUpAttribute";
import getRichValue from "../helpers/dom/getRichValue"; import getRichValue from "../helpers/dom/getRichValue";
import isInputEmpty from "../helpers/dom/isInputEmpty"; import isInputEmpty from "../helpers/dom/isInputEmpty";
import selectElementContents from "../helpers/dom/selectElementContents"; import selectElementContents from "../helpers/dom/selectElementContents";
import { MessageEntity } from "../layer";
import { i18n, LangPackKey, _i18n } from "../lib/langPack"; import { i18n, LangPackKey, _i18n } from "../lib/langPack";
import RichTextProcessor from "../lib/richtextprocessor"; import RichTextProcessor from "../lib/richtextprocessor";
import SetTransition from "./singleTransition"; import SetTransition from "./singleTransition";
@ -18,27 +19,33 @@ let init = () => {
if(!findUpAttribute(e.target, 'contenteditable="true"')) { if(!findUpAttribute(e.target, 'contenteditable="true"')) {
return; return;
} }
//console.log('document paste');
//console.log('messageInput paste');
e.preventDefault(); e.preventDefault();
let text: string, entities: MessageEntity[];
// @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;
let entities2 = RichTextProcessor.parseEntities(text);
entities2 = entities2.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak');
RichTextProcessor.mergeEntities(entities, entities2);
} else {
// @ts-ignore // @ts-ignore
let text = (e.originalEvent || e).clipboardData.getData('text/plain'); text = (e.originalEvent || e).clipboardData.getData('text/plain');
let entities = RichTextProcessor.parseEntities(text); entities = RichTextProcessor.parseEntities(text);
//console.log('messageInput paste', text, entities);
entities = entities.filter(e => e._ === 'messageEntityEmoji' || e._ === 'messageEntityLinebreak'); 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);
// @ts-ignore text = RichTextProcessor.wrapDraftText(text, {entities});
//let html = (e.originalEvent || e).clipboardData.getData('text/html');
// @ts-ignore
//console.log('paste text', text, );
window.document.execCommand('insertHTML', false, text); window.document.execCommand('insertHTML', false, text);
}); });

73
src/helpers/dom/getRichElementValue.ts

@ -18,7 +18,7 @@ export type MarkdownTag = {
}; };
export const markdownTags: {[type in MarkdownType]: MarkdownTag} = { export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
bold: { 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' entityName: 'messageEntityBold'
}, },
underline: { 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}) { export default function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number, entities?: MessageEntity[], offset = {offset: 0}) {
if(node.nodeType === 3) { // TEXT 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) { if(selNode === node) {
line.push(nodeValue.substr(0, selOffset) + '\x01' + nodeValue.substr(selOffset)); line.push(nodeValue.substr(0, selOffset) + '\x01' + nodeValue.substr(selOffset));
@ -57,18 +94,28 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
line.push(nodeValue); line.push(nodeValue);
} }
if(entities && nodeValue.trim()) { if(entities && nodeValue.length) {
if(node.parentNode) { if(node.parentNode) {
const parentElement = node.parentElement; const parentElement = node.parentElement;
// let closestTag: MarkdownTag, closestElementByTag: Element, closestDepth = Infinity;
for(const type in markdownTags) { for(const type in markdownTags) {
const tag = markdownTags[type as MarkdownType]; const tag = markdownTags[type as MarkdownType];
const closest = parentElement.closest(tag.match + ', [contenteditable]'); const closest = parentElement.closest(tag.match + ', [contenteditable]');
if(closest && closest.getAttribute('contenteditable') === null) { 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') { if(tag.entityName === 'messageEntityTextUrl') {
entities.push({ entities.push({
_: tag.entityName, _: tag.entityName,
url: (parentElement as HTMLAnchorElement).href, url: (closest as HTMLAnchorElement).href,
offset: offset.offset, offset: offset.offset,
length: nodeValue.length length: nodeValue.length
}); });
@ -77,7 +124,7 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
_: tag.entityName, _: tag.entityName,
offset: offset.offset, offset: offset.offset,
length: nodeValue.length, length: nodeValue.length,
user_id: parentElement.dataset.follow.toUserId() user_id: (closest as HTMLElement).dataset.follow.toUserId()
}); });
} else { } else {
entities.push({ entities.push({
@ -89,7 +136,6 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
} }
} }
} }
}
offset.offset += nodeValue.length; offset.offset += nodeValue.length;
@ -100,11 +146,12 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
return; return;
} }
const isSelected = (selNode === node); const isSelected = selNode === node;
const isBlock = node.tagName === 'DIV' || node.tagName === 'P'; const isBlock = BLOCK_TAG_NAMES.has(node.tagName);
if(isBlock && line.length || node.tagName === 'BR') { if(isBlock && line.length) {
lines.push(line.join('')); lines.push(line.join(''));
line.splice(0, line.length); line.splice(0, line.length);
++offset.offset;
} else if(node instanceof HTMLImageElement) { } else if(node instanceof HTMLImageElement) {
const alt = node.alt; const alt = node.alt;
if(alt) { if(alt) {
@ -130,5 +177,11 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
if(isBlock && line.length) { if(isBlock && line.length) {
lines.push(line.join('')); lines.push(line.join(''));
line.splice(0, line.length); 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) {
let value = lines.join('\n'); let value = lines.join('\n');
value = value.replace(/\u00A0/g, ' '); 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); RichTextProcessor.combineSameEntities(entities);
} }

7
src/lib/richtextprocessor.ts

@ -345,9 +345,10 @@ namespace RichTextProcessor {
entities.splice(0, entities.length); entities.splice(0, entities.length);
} }
if(!entities.length && !noTrim) { // ! idk what it was here for
newText = newText.trim(); // if(!entities.length && !noTrim) {
} // newText = newText.trim();
// }
mergeEntities(currentEntities, entities); mergeEntities(currentEntities, entities);
combineSameEntities(currentEntities); combineSameEntities(currentEntities);

Loading…
Cancel
Save