mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 21:02:27 +08:00

* Prepare for drag and drop row and column * remove regexp th|td * render drag button * Feat: support drag and drop row and column of table * Feat: table bar tools * remove unnecessary codes * Feat: support select multiple cells * Do not show table drag bar when selected cells * Feat: support delete selected cells content or remove row/column/table * Feat: select one cell or table when press ctrl + a * Support select all content * Remove table tools in context menu * Feat: support copy paste selected cells as sub table * Fix: PR issue 1 press tab will not show the table drag bars * Select one cell and press backspace will cause bug * Fix: The table drag bar location error when there are tow tables in the editor * Fix unable copy and paste 1* n or n * 1 table * Drag any row to the top to editor will cause error. * Update table resize icon * Fix: table resize is not work in table tool bar * Fix: No need to show left drag bar if only one row, and no need to show bottom drag bar if only one column. * Fix: Create an empty table in source code mode, turn to preview mode, there are more than two drag bars in one table. * Fix: resize table * Opti: table is not 100% width now * Fix drag in one row or column * Change table delete icon * Fix: backspace is not work * Little style opti * Fix: cmd + enter bug * Update the table drag bar context menu text * Handle delete key when select table cells * remove all unnecessary debug codes * Feat: support cut selected cells and copy/cut by context menu * Fix typo * Rename some methods name * Fix an issue when drag and drop table drag bar * fix do not handle cell selection when the context menu shown * Do not handle select cells when mouse up outside table
279 lines
8.7 KiB
JavaScript
279 lines
8.7 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.docCutHandler = function (event) {
|
|
const { selectedTableCells } = this
|
|
if (selectedTableCells) {
|
|
event.preventDefault()
|
|
return this.deleteSelectedTableCells(true)
|
|
}
|
|
}
|
|
|
|
ContentState.prototype.cutHandler = function () {
|
|
if (this.selectedTableCells) {
|
|
return
|
|
}
|
|
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.docCopyHandler = function (event) {
|
|
const { selectedTableCells } = this
|
|
if (selectedTableCells) {
|
|
event.preventDefault()
|
|
const { row, column, cells } = selectedTableCells
|
|
const figureBlock = this.createBlock('figure', {
|
|
functionType: 'table'
|
|
})
|
|
const tableContents = []
|
|
let i
|
|
let j
|
|
for (i = 0; i < row; i++) {
|
|
const rowWrapper = []
|
|
for (j = 0; j < column; j++) {
|
|
const cell = cells[i * column + j]
|
|
|
|
rowWrapper.push({
|
|
text: cell.text,
|
|
align: cell.align
|
|
})
|
|
}
|
|
tableContents.push(rowWrapper)
|
|
}
|
|
|
|
const table = this.createTableInFigure({ rows: row, columns: column }, tableContents)
|
|
this.appendChild(figureBlock, table)
|
|
const listIndentation = this.listIndentation
|
|
const markdown = new ExportMarkdown([figureBlock], listIndentation).generate()
|
|
|
|
event.clipboardData.setData('text/html', '')
|
|
event.clipboardData.setData('text/plain', markdown)
|
|
}
|
|
}
|
|
|
|
ContentState.prototype.copyHandler = function (event, type, copyInfo = null) {
|
|
if (this.selectedTableCells) {
|
|
// Hand over to docCopyHandler
|
|
return
|
|
}
|
|
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 'copyBlock': {
|
|
const block = typeof copyInfo === 'string' ? this.getBlock(copyInfo) : copyInfo
|
|
if (!block) return
|
|
const anchor = this.getAnchor(block)
|
|
const listIndentation = this.listIndentation
|
|
const markdown = new ExportMarkdown([anchor], listIndentation).generate()
|
|
event.clipboardData.setData('text/html', '')
|
|
event.clipboardData.setData('text/plain', markdown)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default copyCutCtrl
|