marktext/src/muya/lib/contentState/copyCutCtrl.js
Ran Luo 5b8da2cdf4
Optimization of code block (#1445)
* duplicate css rule

* remove all codeLine

* Fix: #1446

* Fix #942 #1310

* Fix copy paste will add one more empty line in code block

* remove debug codes

* Fix update thematic break error

* fix: #1447

* Update octokit/rest and url-loader

* Fix: CI test error

* Fix comment issue1

* Fix: escape charachters in code block
2019-10-08 14:12:51 +08:00

227 lines
7.2 KiB
JavaScript

import selection from '../selection'
import { CLASS_OR_ID } from '../config'
import { escapeHtml } from '../utils'
import { getSanitizeHtml } from '../utils/exportHtml'
import ExportMarkdown from '../utils/exportMarkdown'
import marked from '../parser/marked'
const copyCutCtrl = ContentState => {
ContentState.prototype.cutHandler = function () {
const { selectedImage } = this
if (selectedImage) {
const { key, token } = selectedImage
this.deleteImage({
key,
token
})
return
}
const { start, end } = selection.getCursorRange()
if (!start || !end) {
return
}
const startBlock = this.getBlock(start.key)
const endBlock = this.getBlock(end.key)
startBlock.text = startBlock.text.substring(0, start.offset) + endBlock.text.substring(end.offset)
if (start.key !== end.key) {
this.removeBlocks(startBlock, endBlock)
}
this.cursor = {
start,
end: start
}
this.checkInlineUpdate(startBlock)
this.partialRender()
}
ContentState.prototype.getClipBoradData = function () {
const { start, end } = selection.getCursorRange()
if (start.key === end.key) {
const startBlock = this.getBlock(start.key)
const { type, text, functionType } = startBlock
// Fix issue #942
if (type === 'span' && functionType === 'codeContent') {
const selectedText = escapeHtml(text.substring(start.offset, end.offset))
return {
html: marked(selectedText),
text: selectedText
}
}
}
const html = selection.getSelectionHtml()
const wrapper = document.createElement('div')
wrapper.innerHTML = html
const removedElements = wrapper.querySelectorAll(
`.${CLASS_OR_ID.AG_TOOL_BAR},
.${CLASS_OR_ID.AG_MATH_RENDER},
.${CLASS_OR_ID.AG_RUBY_RENDER},
.${CLASS_OR_ID.AG_HTML_PREVIEW},
.${CLASS_OR_ID.AG_MATH_PREVIEW},
.${CLASS_OR_ID.AG_COPY_REMOVE},
.${CLASS_OR_ID.AG_LANGUAGE_INPUT},
.${CLASS_OR_ID.AG_HTML_TAG} br,
.${CLASS_OR_ID.AG_FRONT_ICON}`
)
for (const e of removedElements) {
e.remove()
}
const images = wrapper.querySelectorAll('span.ag-inline-image img')
for (const image of images) {
const src = image.getAttribute('src')
let originSrc = null
for (const [sSrc, tSrc] of this.stateRender.urlMap.entries()) {
if (tSrc === src) {
originSrc = sSrc
break
}
}
if (originSrc) {
image.setAttribute('src', originSrc)
}
}
const hrs = wrapper.querySelectorAll('[data-role=hr]')
for (const hr of hrs) {
hr.replaceWith(document.createElement('hr'))
}
const headers = wrapper.querySelectorAll('[data-head]')
for (const header of headers) {
const p = document.createElement('p')
p.textContent = header.textContent
header.replaceWith(p)
}
// replace inline rule element: code, a, strong, em, del, auto_link to span element
// in order to escape turndown translation
const inlineRuleElements = wrapper.querySelectorAll(
`a.${CLASS_OR_ID.AG_INLINE_RULE},
code.${CLASS_OR_ID.AG_INLINE_RULE},
strong.${CLASS_OR_ID.AG_INLINE_RULE},
em.${CLASS_OR_ID.AG_INLINE_RULE},
del.${CLASS_OR_ID.AG_INLINE_RULE}`
)
for (const e of inlineRuleElements) {
const span = document.createElement('span')
span.textContent = e.textContent
e.replaceWith(span)
}
const aLinks = wrapper.querySelectorAll(`.${CLASS_OR_ID.AG_A_LINK}`)
for (const l of aLinks) {
const span = document.createElement('span')
span.innerHTML = l.innerHTML
l.replaceWith(span)
}
const codefense = wrapper.querySelectorAll('pre[data-role$=\'code\']')
for (const cf of codefense) {
const id = cf.id
const block = this.getBlock(id)
const language = block.lang || ''
const codeContent = cf.querySelector('.ag-code-content')
const value = escapeHtml(codeContent.textContent)
cf.innerHTML = `<code class="language-${language}">${value}</code>`
}
const tightListItem = wrapper.querySelectorAll('.ag-tight-list-item')
for (const li of tightListItem) {
for (const item of li.childNodes) {
if (item.tagName === 'P' && item.childElementCount === 1 && item.classList.contains('ag-paragraph')) {
li.replaceChild(item.firstElementChild, item)
}
}
}
const htmlBlock = wrapper.querySelectorAll('figure[data-role=\'HTML\']')
for (const hb of htmlBlock) {
const codeContent = hb.querySelector('.ag-code-content')
const pre = document.createElement('pre')
pre.textContent = codeContent.textContent
hb.replaceWith(pre)
}
// Just work for turndown, turndown will add `leading` and `traling` space in line-break.
const lineBreaks = wrapper.querySelectorAll('span.ag-soft-line-break, span.ag-hard-line-break')
for (const b of lineBreaks) {
b.innerHTML = ''
}
const mathBlock = wrapper.querySelectorAll('figure.ag-container-block')
for (const mb of mathBlock) {
const preElement = mb.querySelector('pre[data-role]')
const functionType = preElement.getAttribute('data-role')
const codeContent = mb.querySelector('.ag-code-content')
const value = codeContent.textContent
let pre
switch (functionType) {
case 'multiplemath':
pre = document.createElement('pre')
pre.classList.add('multiple-math')
pre.textContent = value
mb.replaceWith(pre)
break
case 'mermaid':
case 'flowchart':
case 'sequence':
case 'vega-lite':
pre = document.createElement('pre')
pre.innerHTML = `<code class="language-${functionType}">${value}</code>`
mb.replaceWith(pre)
break
}
}
let htmlData = wrapper.innerHTML
const textData = this.htmlToMarkdown(htmlData)
htmlData = marked(textData)
return { html: htmlData, text: textData }
}
ContentState.prototype.copyHandler = function (event, type) {
event.preventDefault()
const { selectedImage } = this
if (selectedImage) {
const { token } = selectedImage
event.clipboardData.setData('text/html', token.raw)
event.clipboardData.setData('text/plain', token.raw)
return
}
const { html, text } = this.getClipBoradData()
switch (type) {
case 'normal': {
event.clipboardData.setData('text/html', html)
event.clipboardData.setData('text/plain', text)
break
}
case 'copyAsMarkdown': {
event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', text)
break
}
case 'copyAsHtml': {
event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', getSanitizeHtml(text))
break
}
case 'copyTable': {
const table = this.getTableBlock()
if (!table) return
const listIndentation = this.listIndentation
const markdown = new ExportMarkdown([table], listIndentation).generate()
event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', markdown)
break
}
}
}
}
export default copyCutCtrl