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
224 lines
6.7 KiB
JavaScript
224 lines
6.7 KiB
JavaScript
import { EVENT_KEYS, CLASS_OR_ID } from '../config'
|
|
import { findNearestParagraph } from '../selection/dom'
|
|
import selection from '../selection'
|
|
|
|
// If the next block is header, put cursor after the `#{1,6} *`
|
|
const adjustOffset = (offset, block, event) => {
|
|
if (/^span$/.test(block.type) && block.functionType === 'atxLine' && event.key === EVENT_KEYS.ArrowDown) {
|
|
const match = /^\s{0,3}(?:#{1,6})(?:\s{1,}|$)/.exec(block.text)
|
|
if (match) {
|
|
return match[0].length
|
|
}
|
|
}
|
|
return offset
|
|
}
|
|
|
|
const arrowCtrl = ContentState => {
|
|
ContentState.prototype.findNextRowCell = function (cell) {
|
|
if (cell.functionType !== 'cellContent') {
|
|
throw new Error(`block with type ${cell && cell.type} is not a table cell`)
|
|
}
|
|
const thOrTd = this.getParent(cell)
|
|
const row = this.closest(cell, 'tr')
|
|
const rowContainer = this.closest(row, /thead|tbody/) // thead or tbody
|
|
const column = row.children.indexOf(thOrTd)
|
|
if (rowContainer.type === 'thead') {
|
|
const tbody = this.getNextSibling(rowContainer)
|
|
if (tbody && tbody.children.length) {
|
|
return tbody.children[0].children[column].children[0]
|
|
}
|
|
} else if (rowContainer.type === 'tbody') {
|
|
const nextRow = this.getNextSibling(row)
|
|
if (nextRow) {
|
|
return nextRow.children[column].children[0]
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
ContentState.prototype.findPrevRowCell = function (cell) {
|
|
if (cell.functionType !== 'cellContent') {
|
|
throw new Error(`block with type ${cell && cell.type} is not a table cell`)
|
|
}
|
|
const thOrTd = this.getParent(cell)
|
|
const row = this.closest(cell, 'tr')
|
|
const rowContainer = this.getParent(row) // thead or tbody
|
|
const rowIndex = rowContainer.children.indexOf(row)
|
|
const column = row.children.indexOf(thOrTd)
|
|
if (rowContainer.type === 'tbody') {
|
|
if (rowIndex === 0 && rowContainer.preSibling) {
|
|
const thead = this.getPreSibling(rowContainer)
|
|
return thead.children[0].children[column].children[0]
|
|
} else if (rowIndex > 0) {
|
|
return this.getPreSibling(row).children[column].children[0]
|
|
}
|
|
return null
|
|
}
|
|
return null
|
|
}
|
|
|
|
ContentState.prototype.docArrowHandler = function (event) {
|
|
const { selectedImage } = this
|
|
if (selectedImage) {
|
|
const { key, token } = selectedImage
|
|
const { start, end } = token.range
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
const block = this.getBlock(key)
|
|
switch (event.key) {
|
|
case EVENT_KEYS.ArrowUp:
|
|
case EVENT_KEYS.ArrowLeft: {
|
|
this.cursor = {
|
|
start: { key, offset: start },
|
|
end: { key, offset: start }
|
|
}
|
|
break
|
|
}
|
|
case EVENT_KEYS.ArrowDown:
|
|
case EVENT_KEYS.ArrowRight: {
|
|
this.cursor = {
|
|
start: { key, offset: end },
|
|
end: { key, offset: end }
|
|
}
|
|
break
|
|
}
|
|
}
|
|
this.muya.keyboard.hideAllFloatTools()
|
|
return this.singleRender(block)
|
|
}
|
|
}
|
|
|
|
ContentState.prototype.arrowHandler = function (event) {
|
|
const node = selection.getSelectionStart()
|
|
const paragraph = findNearestParagraph(node)
|
|
const id = paragraph.id
|
|
const block = this.getBlock(id)
|
|
const preBlock = this.findPreBlockInLocation(block)
|
|
const nextBlock = this.findNextBlockInLocation(block)
|
|
const { start, end } = selection.getCursorRange()
|
|
const { topOffset, bottomOffset } = selection.getCursorYOffset(paragraph)
|
|
if (!start || !end) {
|
|
return
|
|
}
|
|
|
|
// fix #101
|
|
if (event.key === EVENT_KEYS.ArrowRight && node && node.classList && node.classList.contains(CLASS_OR_ID.AG_MATH_TEXT)) {
|
|
const { right } = selection.getCaretOffsets(node)
|
|
if (right === 0 && start.key === end.key && start.offset === end.offset) {
|
|
// It's not recommended to use such lower API, but it's work well.
|
|
return selection.select(node.parentNode.nextElementSibling, 0)
|
|
}
|
|
}
|
|
|
|
// Just do nothing if the cursor is not collapsed or `shiftKey` pressed
|
|
if (
|
|
(start.key === end.key && start.offset !== end.offset) ||
|
|
start.key !== end.key || event.shiftKey
|
|
) {
|
|
return
|
|
}
|
|
|
|
if (
|
|
(event.key === EVENT_KEYS.ArrowUp && topOffset > 0) ||
|
|
(event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)
|
|
) {
|
|
if (!/pre/.test(block.type) || block.functionType !== 'cellContent') {
|
|
return
|
|
}
|
|
}
|
|
|
|
if (block.functionType === 'cellContent') {
|
|
let activeBlock
|
|
const cellInNextRow = this.findNextRowCell(block)
|
|
const cellInPrevRow = this.findPrevRowCell(block)
|
|
|
|
if (event.key === EVENT_KEYS.ArrowUp) {
|
|
if (cellInPrevRow) {
|
|
activeBlock = cellInPrevRow
|
|
} else {
|
|
activeBlock = this.findPreBlockInLocation(this.getTableBlock())
|
|
}
|
|
}
|
|
|
|
if (event.key === EVENT_KEYS.ArrowDown) {
|
|
if (cellInNextRow) {
|
|
activeBlock = cellInNextRow
|
|
} else {
|
|
activeBlock = this.findNextBlockInLocation(this.getTableBlock())
|
|
}
|
|
}
|
|
|
|
if (activeBlock) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
let offset = activeBlock.type === 'p'
|
|
? 0
|
|
: (event.key === EVENT_KEYS.ArrowUp
|
|
? activeBlock.text.length
|
|
: 0)
|
|
|
|
offset = adjustOffset(offset, activeBlock, event)
|
|
|
|
const key = activeBlock.type === 'p'
|
|
? activeBlock.children[0].key
|
|
: activeBlock.key
|
|
|
|
this.cursor = {
|
|
start: {
|
|
key,
|
|
offset
|
|
},
|
|
end: {
|
|
key,
|
|
offset
|
|
}
|
|
}
|
|
|
|
return this.partialRender()
|
|
}
|
|
}
|
|
|
|
if (
|
|
(event.key === EVENT_KEYS.ArrowUp) ||
|
|
(event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
|
|
) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
if (!preBlock) return
|
|
const key = preBlock.key
|
|
const offset = preBlock.text.length
|
|
this.cursor = {
|
|
start: { key, offset },
|
|
end: { key, offset }
|
|
}
|
|
|
|
return this.partialRender()
|
|
} else if (
|
|
(event.key === EVENT_KEYS.ArrowDown) ||
|
|
(event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
|
|
) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
let key
|
|
let newBlock
|
|
if (nextBlock) {
|
|
key = nextBlock.key
|
|
} else {
|
|
newBlock = this.createBlockP()
|
|
const lastBlock = this.blocks[this.blocks.length - 1]
|
|
this.insertAfter(newBlock, lastBlock)
|
|
key = newBlock.children[0].key
|
|
}
|
|
const offset = adjustOffset(0, nextBlock || newBlock, event)
|
|
this.cursor = {
|
|
start: { key, offset },
|
|
end: { key, offset }
|
|
}
|
|
|
|
return this.partialRender()
|
|
}
|
|
}
|
|
}
|
|
|
|
export default arrowCtrl
|