diff --git a/core/editor.js b/core/editor.js index b55c002c38..0a0f212c28 100644 --- a/core/editor.js +++ b/core/editor.js @@ -7,6 +7,8 @@ import { LeafBlot } from 'parchment'; import CursorBlot from '../blots/cursor'; import Block, { bubbleFormats } from '../blots/block'; import Break from '../blots/break'; +import ScrollBlot from '../blots/scroll'; +import TextBlot from '../blots/text'; const ASCII = /^[ -~]*$/; @@ -131,6 +133,10 @@ class Editor { return extend.apply(extend, formatsArr); } + getHTML(index, length) { + return convertHTML(this.scroll, index, length); + } + getText(index, length) { return this.getContents(index, length) .filter(op => typeof op.insert === 'string') @@ -214,6 +220,71 @@ class Editor { } } +function convertListHTML(items, lastIndent) { + if (items.length === 0) { + if (lastIndent <= 0) { + return ''; + } + return `${convertListHTML([], lastIndent - 1)}`; + } + const [{ child, offset, length, indent }, ...rest] = items; + if (indent > lastIndent) { + return `
  1. ${convertHTML(child, offset, length)}${convertListHTML( + rest, + indent, + )}`; + } else if (indent === lastIndent) { + return `
  2. ${convertHTML(child, offset, length)}${convertListHTML( + rest, + indent, + )}`; + } else if (indent === lastIndent - 1) { + return `
  • ${convertHTML( + child, + offset, + length, + )}${convertListHTML(rest, indent)}`; + } + return `
  • ${convertListHTML(items, lastIndent - 1)}`; +} + +function convertHTML(blot, index, length) { + if (typeof blot.html === 'function') { + return blot.html(index, length); + } else if (blot instanceof TextBlot) { + return blot.value().slice(index, index + length); + } else if (blot.children) { + // TODO fix API + if (blot.statics.blotName === 'list-container') { + const items = []; + blot.children.forEachAt(index, length, (child, offset, childLength) => { + const formats = child.formats(); + items.push({ + child, + offset, + length: childLength, + indent: formats.indent || 0, + }); + }); + return convertListHTML(items, -1); + } + const parts = []; + const { outerHTML, innerHTML } = blot.domNode; + const strIndex = outerHTML.lastIndexOf(innerHTML); + let startTag = outerHTML.slice(0, strIndex); + let endTag = outerHTML.slice(strIndex + innerHTML.length); + if (blot instanceof ScrollBlot || blot.statics.blotName === 'list') { + startTag = ''; + endTag = ''; + } + blot.children.forEachAt(index, length, (child, offset, childLength) => { + parts.push(convertHTML(child, offset, childLength)); + }); + return `${startTag}${parts.join('')}${endTag}`; + } + return blot.domNode.outerHTML; +} + function combineFormats(formats, combined) { return Object.keys(combined).reduce((merged, name) => { if (formats[name] == null) return merged; diff --git a/core/quill.js b/core/quill.js index e3f5f493ee..74a77658db 100644 --- a/core/quill.js +++ b/core/quill.js @@ -298,6 +298,11 @@ class Quill { return this.selection.getRange()[0]; } + getSemanticHTML(index = 0, length = this.getLength() - index) { + [index, length] = overload(index, length); + return this.editor.getHTML(index, length); + } + getText(index = 0, length = this.getLength() - index) { [index, length] = overload(index, length); return this.editor.getText(index, length); diff --git a/formats/code.js b/formats/code.js index a573fa736a..d4d41af989 100644 --- a/formats/code.js +++ b/formats/code.js @@ -13,6 +13,11 @@ class CodeBlockContainer extends Container { domNode.setAttribute('spellcheck', false); return domNode; } + + html(index, length) { + const html = this.domNode.innerText.slice(index, index + length); + return `
    ${html}
    `; + } } class CodeBlock extends Block { diff --git a/formats/formula.js b/formats/formula.js index 99731fb162..4ebd2cbfbe 100644 --- a/formats/formula.js +++ b/formats/formula.js @@ -19,6 +19,11 @@ class Formula extends Embed { static value(domNode) { return domNode.getAttribute('data-value'); } + + html() { + const { formula } = this.value(); + return `${formula}`; + } } Formula.blotName = 'formula'; Formula.className = 'ql-formula'; diff --git a/formats/video.js b/formats/video.js index 332e2d5dd1..6af30cd000 100644 --- a/formats/video.js +++ b/formats/video.js @@ -40,6 +40,11 @@ class Video extends BlockEmbed { super.format(name, value); } } + + html() { + const { video } = this.value(); + return `${video}`; + } } Video.blotName = 'video'; Video.className = 'ql-video'; diff --git a/modules/clipboard.js b/modules/clipboard.js index ea9eef1a73..b8e030ab11 100644 --- a/modules/clipboard.js +++ b/modules/clipboard.js @@ -126,8 +126,8 @@ class Clipboard extends Module { onCaptureCopy(e) { if (e.defaultPrevented) return; this.quill.update(); - const [range, native] = this.quill.selection.getRange(); - this.onCopy(e, range, native); + const [range] = this.quill.selection.getRange(); + this.onCopy(e, range); e.preventDefault(); } @@ -143,17 +143,11 @@ class Clipboard extends Module { e.preventDefault(); } - onCopy(e, range, nativeRange) { + onCopy(e, range) { const text = this.quill.getText(range); - const fragment = nativeRange.native.cloneContents(); - Array.from(fragment.querySelectorAll('select')).forEach(select => { - select.parentNode.removeChild(select); - }); - const div = this.quill.root.ownerDocument.createElement('div'); - div.style.whiteSpace = 'pre-wrap'; - div.appendChild(fragment); + const html = this.quill.getSemanticHTML(range); e.clipboardData.setData('text/plain', text); - e.clipboardData.setData('text/html', div.outerHTML); + e.clipboardData.setData('text/html', html); } onPaste(e, range) {