Optimization of table block (#1456)
* 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
@ -8,6 +8,8 @@
|
||||
|
||||
- codeContent (used in code block)
|
||||
|
||||
- cellContent (used in table cell, it's parent must be th or td block)
|
||||
|
||||
- atxLine (can not contain soft line break and hard line break use in atx heading)
|
||||
|
||||
- thematicBreakLine (can not contain soft line break and hard line break use in thematic break)
|
||||
|
@ -102,7 +102,7 @@ export const updateSelectionMenus = (applicationMenu, { start, end, affiliation
|
||||
setParagraphMenuItemStatus(applicationMenu, true)
|
||||
|
||||
if (
|
||||
(/th|td/.test(start.type) && /th|td/.test(end.type)) ||
|
||||
(start.block.functionType === 'cellContent' && end.block.functionType === 'cellContent') ||
|
||||
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
||||
(end.type === 'span' && end.block.functionType === 'codeContent')
|
||||
) {
|
||||
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 903 B |
BIN
src/muya/lib/assets/pngicon/table_delete/1.png
Normal file
After Width: | Height: | Size: 779 B |
BIN
src/muya/lib/assets/pngicon/table_delete/2.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/muya/lib/assets/pngicon/table_delete/3.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -40,10 +40,18 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-paragraph-conte
|
||||
.ag-atx-line:empty::after,
|
||||
.ag-thematic-break-line:empty::after,
|
||||
.ag-code-content:empty::after,
|
||||
.ag-cell-content:empty::after,
|
||||
.ag-paragraph-content:empty::after {
|
||||
content: '\200B';
|
||||
}
|
||||
|
||||
.ag-cell-content {
|
||||
display: inline-block;
|
||||
min-width: 4em;
|
||||
width: 100%;
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
.ag-atx-line,
|
||||
.ag-thematic-break-line,
|
||||
.ag-paragraph-content,
|
||||
@ -249,14 +257,15 @@ span.ag-math > .ag-math-render.ag-math-error {
|
||||
figure {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin: 1rem 0;
|
||||
margin: 1.4em 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ag-tool-bar {
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
top: -22px;
|
||||
left: 0;
|
||||
display: none;
|
||||
}
|
||||
@ -273,7 +282,7 @@ figure {
|
||||
display: flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 3px;
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
color: var(--iconColor);
|
||||
@ -281,14 +290,12 @@ figure {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ag-tool-bar ul li[data-label=delete] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
.ag-tool-bar ul li[data-label=table] {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.ag-tool-bar ul li[data-label=delete] {
|
||||
color: var(--deleteColor);
|
||||
right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.ag-container-icon {
|
||||
@ -313,7 +320,7 @@ figure {
|
||||
width: 16px;
|
||||
overflow: hidden;
|
||||
color: var(--iconColor);
|
||||
opacity: .5;
|
||||
opacity: .7;
|
||||
transition: all .25s ease-in-out;
|
||||
}
|
||||
|
||||
@ -339,8 +346,7 @@ figure {
|
||||
}
|
||||
|
||||
.ag-tool-bar ul li[data-label=delete] i.icon {
|
||||
color: var(--deleteColor);
|
||||
opacity: 1;
|
||||
color: var(--iconColor);
|
||||
}
|
||||
|
||||
figure.ag-active .ag-tool-bar {
|
||||
@ -371,7 +377,6 @@ figure[data-role=HTML]:not(.ag-active):hover .ag-container-icon {
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -495,7 +500,6 @@ pre.ag-multiple-math span.ag-code-content:first-of-type:empty::after {
|
||||
color: var(--editorColor10);
|
||||
}
|
||||
|
||||
figure,
|
||||
pre.ag-html-block,
|
||||
pre.ag-fence-code,
|
||||
pre.ag-indent-code,
|
||||
|
@ -6,7 +6,7 @@ import voidHtmlTags from 'html-tags/void'
|
||||
// Electron 2.0.2 not support yet! So give a default value 4
|
||||
export const DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
|
||||
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
|
||||
export const HAS_TEXT_BLOCK_REG = /^(span|th|td)/i
|
||||
export const HAS_TEXT_BLOCK_REG = /^span$/i
|
||||
export const VOID_HTML_TAGS = voidHtmlTags
|
||||
export const HTML_TAGS = htmlTags
|
||||
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
||||
|
@ -15,38 +15,42 @@ const adjustOffset = (offset, block, event) => {
|
||||
|
||||
const arrowCtrl = ContentState => {
|
||||
ContentState.prototype.findNextRowCell = function (cell) {
|
||||
if (!/th|td/.test(cell.type)) {
|
||||
if (cell.functionType !== 'cellContent') {
|
||||
throw new Error(`block with type ${cell && cell.type} is not a table cell`)
|
||||
}
|
||||
const row = this.getParent(cell)
|
||||
const rowContainer = this.getParent(row) // thead or tbody
|
||||
const column = row.children.indexOf(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]
|
||||
return tbody.children[0].children[column].children[0]
|
||||
}
|
||||
} else if (rowContainer.type === 'tbody') {
|
||||
const nextRow = this.getNextSibling(row)
|
||||
if (nextRow) {
|
||||
return nextRow.children[column]
|
||||
return nextRow.children[column].children[0]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
ContentState.prototype.findPrevRowCell = function (cell) {
|
||||
if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`)
|
||||
const row = this.getParent(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(cell)
|
||||
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]
|
||||
return thead.children[0].children[column].children[0]
|
||||
} else if (rowIndex > 0) {
|
||||
return this.getPreSibling(row).children[column]
|
||||
return this.getPreSibling(row).children[column].children[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -118,12 +122,12 @@ const arrowCtrl = ContentState => {
|
||||
(event.key === EVENT_KEYS.ArrowUp && topOffset > 0) ||
|
||||
(event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)
|
||||
) {
|
||||
if (!/pre|th|td/.test(block.type)) {
|
||||
if (!/pre/.test(block.type) || block.functionType !== 'cellContent') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (/th|td/.test(block.type)) {
|
||||
if (block.functionType === 'cellContent') {
|
||||
let activeBlock
|
||||
const cellInNextRow = this.findNextRowCell(block)
|
||||
const cellInPrevRow = this.findPrevRowCell(block)
|
||||
|
@ -107,6 +107,10 @@ const backspaceCtrl = ContentState => {
|
||||
event.preventDefault()
|
||||
return this.deleteImage(this.selectedImage)
|
||||
}
|
||||
if (this.selectedTableCells) {
|
||||
event.preventDefault()
|
||||
return this.deleteSelectedTableCells()
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.backspaceHandler = function (event) {
|
||||
@ -122,6 +126,7 @@ const backspaceCtrl = ContentState => {
|
||||
return this.deleteImage(this.selectedImage)
|
||||
}
|
||||
|
||||
// Handle select all content.
|
||||
if (this.isSelectAll()) {
|
||||
event.preventDefault()
|
||||
this.blocks = [this.createBlockP()]
|
||||
@ -202,7 +207,8 @@ const backspaceCtrl = ContentState => {
|
||||
// fix bug when the first block is table, these two ways will cause bugs.
|
||||
// 1. one paragraph bollow table, selectAll, press backspace.
|
||||
// 2. select table from the first cell to the last cell, press backsapce.
|
||||
if (/th/.test(startBlock.type) && start.offset === 0 && !startBlock.preSibling) {
|
||||
const maybeCell = this.getParent(startBlock)
|
||||
if (/th/.test(maybeCell.type) && start.offset === 0 && !maybeCell.preSibling) {
|
||||
if (
|
||||
end.offset === endBlock.text.length &&
|
||||
startOutmostBlock === endOutmostBlock &&
|
||||
@ -211,7 +217,7 @@ const backspaceCtrl = ContentState => {
|
||||
) {
|
||||
event.preventDefault()
|
||||
// need remove the figure block.
|
||||
const figureBlock = this.getBlock(this.findFigure(startBlock))
|
||||
const figureBlock = this.getBlock(this.closest(startBlock, 'figure'))
|
||||
// if table is the only block, need create a p block.
|
||||
const p = this.createBlockP(endBlock.text.substring(end.offset))
|
||||
this.insertBefore(p, figureBlock)
|
||||
@ -231,6 +237,21 @@ const backspaceCtrl = ContentState => {
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed #1456 existed bugs `Select one cell and press backspace will cause bug`
|
||||
if (startBlock.functionType === 'cellContent' && this.cursor.start.offset === 0 && this.cursor.end.offset !== 0 && this.cursor.end.offset === startBlock.text.length) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
startBlock.text = ''
|
||||
const { key } = startBlock
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
|
||||
return this.singleRender(startBlock)
|
||||
}
|
||||
|
||||
// If select multiple paragraph or multiple characters in one paragraph, just let
|
||||
// inputCtrl to handle this case.
|
||||
if (start.key !== end.key || start.offset !== end.offset) {
|
||||
@ -282,7 +303,7 @@ const backspaceCtrl = ContentState => {
|
||||
}
|
||||
|
||||
// Fix issue #1218
|
||||
if (/th|td/.test(startBlock.type) && /<br\/>.{1}$/.test(startBlock.text)) {
|
||||
if (startBlock.functionType === 'cellContent' && /<br\/>.{1}$/.test(startBlock.text)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@ -298,11 +319,26 @@ const backspaceCtrl = ContentState => {
|
||||
return this.singleRender(startBlock)
|
||||
}
|
||||
|
||||
// Fix delete the last character in table cell, the default action will delete the cell content if not preventDefault.
|
||||
if (startBlock.functionType === 'cellContent' && left === 1 && right === 0) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
startBlock.text = ''
|
||||
const { key } = startBlock
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
|
||||
return this.singleRender(startBlock)
|
||||
}
|
||||
|
||||
const tableHasContent = table => {
|
||||
const tHead = table.children[0]
|
||||
const tBody = table.children[1]
|
||||
const tHeadHasContent = tHead.children[0].children.some(th => th.text.trim())
|
||||
const tBodyHasContent = tBody.children.some(row => row.children.some(td => td.text.trim()))
|
||||
const tHeadHasContent = tHead.children[0].children.some(th => th.children[0].text.trim())
|
||||
const tBodyHasContent = tBody.children.some(row => row.children.some(td => td.children[0].text.trim()))
|
||||
return tHeadHasContent || tBodyHasContent
|
||||
}
|
||||
|
||||
@ -348,24 +384,23 @@ const backspaceCtrl = ContentState => {
|
||||
}
|
||||
this.partialRender()
|
||||
}
|
||||
} else if (left === 0 && /th|td/.test(block.type)) {
|
||||
} else if (left === 0 && block.functionType === 'cellContent') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const tHead = this.getBlock(parent.parent)
|
||||
const table = this.getBlock(tHead.parent)
|
||||
const figure = this.getBlock(table.parent)
|
||||
const table = this.closest(block, 'table')
|
||||
const figure = this.closest(table, 'figure')
|
||||
const hasContent = tableHasContent(table)
|
||||
let key
|
||||
let offset
|
||||
|
||||
if ((!preBlock || !/th|td/.test(preBlock.type)) && !hasContent) {
|
||||
const newLine = this.createBlock('span')
|
||||
if ((!preBlock || preBlock.functionType !== 'cellContent') && !hasContent) {
|
||||
const paragraphContent = this.createBlock('span')
|
||||
delete figure.functionType
|
||||
figure.children = []
|
||||
this.appendChild(figure, newLine)
|
||||
this.appendChild(figure, paragraphContent)
|
||||
figure.text = ''
|
||||
figure.type = 'p'
|
||||
key = newLine.key
|
||||
key = paragraphContent.key
|
||||
offset = 0
|
||||
} else if (preBlock) {
|
||||
key = preBlock.key
|
||||
|
@ -6,7 +6,18 @@ 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
|
||||
@ -179,10 +190,49 @@ const copyCutCtrl = ContentState => {
|
||||
let htmlData = wrapper.innerHTML
|
||||
const textData = this.htmlToMarkdown(htmlData)
|
||||
htmlData = marked(textData)
|
||||
|
||||
return { html: htmlData, text: textData }
|
||||
}
|
||||
|
||||
ContentState.prototype.copyHandler = function (event, type) {
|
||||
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) {
|
||||
@ -210,11 +260,13 @@ const copyCutCtrl = ContentState => {
|
||||
event.clipboardData.setData('text/plain', getSanitizeHtml(text))
|
||||
break
|
||||
}
|
||||
case 'copyTable': {
|
||||
const table = this.getTableBlock()
|
||||
if (!table) return
|
||||
|
||||
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([table], listIndentation).generate()
|
||||
const markdown = new ExportMarkdown([anchor], listIndentation).generate()
|
||||
event.clipboardData.setData('text/html', '')
|
||||
event.clipboardData.setData('text/plain', markdown)
|
||||
break
|
||||
|
@ -1,6 +1,14 @@
|
||||
import selection from '../selection'
|
||||
|
||||
const deleteCtrl = ContentState => {
|
||||
// Handle `delete` keydown event on document.
|
||||
ContentState.prototype.docDeleteHandler = function (event) {
|
||||
if (this.selectedTableCells) {
|
||||
event.preventDefault()
|
||||
return this.deleteSelectedTableCells()
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.deleteHandler = function (event) {
|
||||
const { start, end } = selection.getCursorRange()
|
||||
if (!start || !end) {
|
||||
|
@ -49,18 +49,23 @@ const enterCtrl = ContentState => {
|
||||
return container
|
||||
}
|
||||
|
||||
ContentState.prototype.createRow = function (row) {
|
||||
const trBlock = this.createBlock('tr')
|
||||
ContentState.prototype.createRow = function (row, isHeader = false) {
|
||||
const tr = this.createBlock('tr')
|
||||
const len = row.children.length
|
||||
let i
|
||||
for (i = 0; i < len; i++) {
|
||||
const tdBlock = this.createBlock('td')
|
||||
const preChild = row.children[i]
|
||||
tdBlock.column = i
|
||||
tdBlock.align = preChild.align
|
||||
this.appendChild(trBlock, tdBlock)
|
||||
const cell = this.createBlock(isHeader ? 'th' : 'td', {
|
||||
align: row.children[i].align,
|
||||
column: i
|
||||
})
|
||||
const cellContent = this.createBlock('span', {
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
|
||||
this.appendChild(cell, cellContent)
|
||||
this.appendChild(tr, cell)
|
||||
}
|
||||
return trBlock
|
||||
return tr
|
||||
}
|
||||
|
||||
ContentState.prototype.createBlockLi = function (paragraphInListItem) {
|
||||
@ -264,7 +269,7 @@ const enterCtrl = ContentState => {
|
||||
// Insert `<br/>` in table cell if you want to open a new line.
|
||||
// Why not use `soft line break` or `hard line break` ?
|
||||
// Becasuse table cell only have one line.
|
||||
if (event.shiftKey && /th|td/.test(block.type)) {
|
||||
if (event.shiftKey && block.functionType === 'cellContent') {
|
||||
const { text, key } = block
|
||||
const brTag = '<br/>'
|
||||
block.text = text.substring(0, start.offset) + brTag + text.substring(start.offset)
|
||||
@ -297,19 +302,27 @@ const enterCtrl = ContentState => {
|
||||
}
|
||||
|
||||
// handle enter in table
|
||||
if (/th|td/.test(block.type)) {
|
||||
const row = this.getBlock(block.parent)
|
||||
if (block.functionType === 'cellContent') {
|
||||
const row = this.closest(block, 'tr')
|
||||
const rowContainer = this.getBlock(row.parent)
|
||||
const table = this.getBlock(rowContainer.parent)
|
||||
const table = this.closest(rowContainer, 'table')
|
||||
|
||||
if (
|
||||
(isOsx && event.metaKey) ||
|
||||
(!isOsx && event.ctrlKey)
|
||||
) {
|
||||
const nextRow = this.createRow(row)
|
||||
const nextRow = this.createRow(row, false)
|
||||
if (rowContainer.type === 'thead') {
|
||||
const tBody = this.getBlock(rowContainer.nextSibling)
|
||||
let tBody = this.getBlock(rowContainer.nextSibling)
|
||||
if (!tBody) {
|
||||
tBody = this.createBlock('tbody')
|
||||
this.appendChild(table, tBody)
|
||||
}
|
||||
if (tBody.children.length) {
|
||||
this.insertBefore(nextRow, tBody.children[0])
|
||||
} else {
|
||||
this.appendChild(tBody, nextRow)
|
||||
}
|
||||
} else {
|
||||
this.insertAfter(nextRow, row)
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ import backspaceCtrl from './backspaceCtrl'
|
||||
import deleteCtrl from './deleteCtrl'
|
||||
import codeBlockCtrl from './codeBlockCtrl'
|
||||
import tableBlockCtrl from './tableBlockCtrl'
|
||||
import selectionCtrl from './selectionCtrl'
|
||||
import tableDragBarCtrl from './tableDragBarCtrl'
|
||||
import tableSelectCellsCtrl from './tableSelectCellsCtrl'
|
||||
import History from './history'
|
||||
import arrowCtrl from './arrowCtrl'
|
||||
import pasteCtrl from './pasteCtrl'
|
||||
@ -33,7 +34,6 @@ import escapeCharactersMap, { escapeCharacters } from '../parser/escapeCharacter
|
||||
const prototypes = [
|
||||
tabCtrl,
|
||||
enterCtrl,
|
||||
selectionCtrl,
|
||||
updateCtrl,
|
||||
backspaceCtrl,
|
||||
deleteCtrl,
|
||||
@ -42,6 +42,8 @@ const prototypes = [
|
||||
pasteCtrl,
|
||||
copyCutCtrl,
|
||||
tableBlockCtrl,
|
||||
tableDragBarCtrl,
|
||||
tableSelectCellsCtrl,
|
||||
paragraphCtrl,
|
||||
formatCtrl,
|
||||
searchCtrl,
|
||||
@ -80,9 +82,37 @@ class ContentState {
|
||||
this.turndownConfig = Object.assign(DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
|
||||
this.fontSize = 16
|
||||
this.lineHeight = 1.6
|
||||
// table drag bar
|
||||
this.dragInfo = null
|
||||
this.isDragTableBar = false
|
||||
this.dragEventIds = []
|
||||
// table cell select
|
||||
this.cellSelectInfo = null
|
||||
this._selectedTableCells = null
|
||||
this.cellSelectEventIds = []
|
||||
this.init()
|
||||
}
|
||||
|
||||
set selectedTableCells (info) {
|
||||
const oldSelectedTableCells = this._selectedTableCells
|
||||
if (!info && !!oldSelectedTableCells) {
|
||||
const selectedCells = this.muya.container.querySelectorAll('.ag-cell-selected')
|
||||
|
||||
for (const cell of Array.from(selectedCells)) {
|
||||
cell.classList.remove('ag-cell-selected')
|
||||
cell.classList.remove('ag-cell-border-top')
|
||||
cell.classList.remove('ag-cell-border-right')
|
||||
cell.classList.remove('ag-cell-border-bottom')
|
||||
cell.classList.remove('ag-cell-border-left')
|
||||
}
|
||||
}
|
||||
this._selectedTableCells = info
|
||||
}
|
||||
|
||||
get selectedTableCells () {
|
||||
return this._selectedTableCells
|
||||
}
|
||||
|
||||
set selectedImage (image) {
|
||||
const oldSelectedImage = this._selectedImage
|
||||
// if there is no selected image, remove selected status of current selected image.
|
||||
@ -411,26 +441,16 @@ class ContentState {
|
||||
}
|
||||
}
|
||||
|
||||
// help func in removeBlocks
|
||||
findFigure (block) {
|
||||
if (block.type === 'figure') {
|
||||
return block.key
|
||||
} else {
|
||||
const parent = this.getBlock(block.parent)
|
||||
return this.findFigure(parent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove blocks between before and after, and includes after block.
|
||||
*/
|
||||
removeBlocks (before, after, isRemoveAfter = true, isRecursion = false) {
|
||||
if (!isRecursion) {
|
||||
if (/td|th/.test(before.type)) {
|
||||
this.exemption.add(this.findFigure(before))
|
||||
this.exemption.add(this.closest(before, 'figure'))
|
||||
}
|
||||
if (/td|th/.test(after.type)) {
|
||||
this.exemption.add(this.findFigure(after))
|
||||
this.exemption.add(this.closest(after, 'figure'))
|
||||
}
|
||||
}
|
||||
let nextSibling = this.getBlock(before.nextSibling)
|
||||
@ -712,6 +732,31 @@ class ContentState {
|
||||
return this.lastInDescendant(blocks[len - 1])
|
||||
}
|
||||
|
||||
closest (block, type) {
|
||||
if (!block) {
|
||||
return null
|
||||
}
|
||||
if (type instanceof RegExp ? type.test(block.type) : block.type === type) {
|
||||
return block
|
||||
} else {
|
||||
const parent = this.getParent(block)
|
||||
return this.closest(parent, type)
|
||||
}
|
||||
}
|
||||
|
||||
getAnchor (block) {
|
||||
const { type, functionType } = block
|
||||
if (type !== 'span') {
|
||||
return null
|
||||
}
|
||||
|
||||
if (functionType === 'codeContent' || functionType === 'cellContent') {
|
||||
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
||||
} else {
|
||||
return this.getParent(block)
|
||||
}
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.history.clearHistory()
|
||||
}
|
||||
|
@ -679,15 +679,24 @@ const paragraphCtrl = ContentState => {
|
||||
this.partialRender()
|
||||
return this.muya.eventCenter.dispatch('stateChange')
|
||||
}
|
||||
|
||||
// delete current paragraph
|
||||
ContentState.prototype.deleteParagraph = function () {
|
||||
ContentState.prototype.deleteParagraph = function (blockKey) {
|
||||
let startOutmostBlock
|
||||
if (blockKey) {
|
||||
const block = this.getBlock(blockKey)
|
||||
const firstEditableBlock = this.firstInDescendant(block)
|
||||
startOutmostBlock = this.getAnchor(firstEditableBlock)
|
||||
} else {
|
||||
const { start, end } = this.cursor
|
||||
const startOutmostBlock = this.findOutMostBlock(this.getBlock(start.key))
|
||||
startOutmostBlock = this.findOutMostBlock(this.getBlock(start.key))
|
||||
const endOutmostBlock = this.findOutMostBlock(this.getBlock(end.key))
|
||||
if (startOutmostBlock !== endOutmostBlock) {
|
||||
// if the cursor is not in one paragraph, just return
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const preBlock = this.getBlock(startOutmostBlock.preSibling)
|
||||
const nextBlock = this.getBlock(startOutmostBlock.nextSibling)
|
||||
let cursorBlock = null
|
||||
@ -723,25 +732,73 @@ const paragraphCtrl = ContentState => {
|
||||
!this.muya.keyboard.isComposed
|
||||
}
|
||||
|
||||
ContentState.prototype.selectAll = function () {
|
||||
const { start } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
// const endBlock = this.getBlock(end.key)
|
||||
// handle selectAll in table. only select the startBlock cell...
|
||||
if (/th|td/.test(startBlock.type)) {
|
||||
const { key } = start
|
||||
const textLength = startBlock.text.length
|
||||
ContentState.prototype.selectAllContent = function () {
|
||||
const firstTextBlock = this.getFirstBlock()
|
||||
const lastTextBlock = this.getLastBlock()
|
||||
this.cursor = {
|
||||
start: {
|
||||
key,
|
||||
key: firstTextBlock.key,
|
||||
offset: 0
|
||||
},
|
||||
end: {
|
||||
key,
|
||||
offset: textLength
|
||||
key: lastTextBlock.key,
|
||||
offset: lastTextBlock.text.length
|
||||
}
|
||||
}
|
||||
return this.partialRender()
|
||||
|
||||
return this.render()
|
||||
}
|
||||
|
||||
ContentState.prototype.selectAll = function () {
|
||||
const mayBeCell = this.isSingleCellSelected()
|
||||
const mayBeTable = this.isWholeTableSelected()
|
||||
if (mayBeTable) {
|
||||
this.selectedTableCells = null
|
||||
return this.selectAllContent()
|
||||
}
|
||||
// Select whole table if already select one cell.
|
||||
if (mayBeCell) {
|
||||
const table = this.closest(mayBeCell, 'table')
|
||||
if (table) {
|
||||
return this.selectTable(table)
|
||||
}
|
||||
}
|
||||
const { start, end } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const endBlock = this.getBlock(end.key)
|
||||
// handle selectAll in table.
|
||||
if (startBlock.functionType === 'cellContent' && endBlock.functionType === 'cellContent') {
|
||||
if (start.key === end.key) {
|
||||
const table = this.closest(startBlock, 'table')
|
||||
const cellBlock = this.closest(startBlock, /th|td/)
|
||||
this.selectedTableCells = {
|
||||
tableId: table.key,
|
||||
row: 1,
|
||||
column: 1,
|
||||
cells: [{
|
||||
key: cellBlock.key,
|
||||
top: true,
|
||||
right: true,
|
||||
bottom: true,
|
||||
left: true
|
||||
}]
|
||||
}
|
||||
this.muya.blur()
|
||||
this.singleRender(table, false)
|
||||
return this.muya.eventCenter.dispatch('muya-format-picker', { reference: null })
|
||||
} else {
|
||||
const startTable = this.closest(startBlock, 'table')
|
||||
const endTable = this.closest(endBlock, 'table')
|
||||
// Check whether both blocks are in the same table.
|
||||
if (!startTable || !endTable) {
|
||||
console.error('No table found or invalid type.')
|
||||
return
|
||||
} else if (startTable.key !== endTable.key) {
|
||||
// Select entire document
|
||||
return
|
||||
}
|
||||
return this.selectTable(startTable)
|
||||
}
|
||||
}
|
||||
// Handler selectAll in code block. only select all the code block conent.
|
||||
// `code block` here is Math, HTML, BLOCK CODE, Mermaid, vega-lite, flowchart, front-matter etc...
|
||||
@ -774,19 +831,8 @@ const paragraphCtrl = ContentState => {
|
||||
}
|
||||
return this.partialRender()
|
||||
}
|
||||
const firstTextBlock = this.getFirstBlock()
|
||||
const lastTextBlock = this.getLastBlock()
|
||||
this.cursor = {
|
||||
start: {
|
||||
key: firstTextBlock.key,
|
||||
offset: 0
|
||||
},
|
||||
end: {
|
||||
key: lastTextBlock.key,
|
||||
offset: lastTextBlock.text.length
|
||||
}
|
||||
}
|
||||
this.render()
|
||||
|
||||
return this.selectAllContent()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +329,7 @@ const pasteCtrl = ContentState => {
|
||||
return this.partialRender()
|
||||
}
|
||||
|
||||
if (/th|td/.test(startBlock.type)) {
|
||||
if (startBlock.functionType === 'cellContent') {
|
||||
const pendingText = text.trim().replace(/\n/g, '<br/>')
|
||||
startBlock.text = startBlock.text.substring(0, start.offset) + pendingText + startBlock.text.substring(end.offset)
|
||||
const { key } = startBlock
|
||||
|
@ -1,58 +0,0 @@
|
||||
const selectionCtrl = ContentState => {
|
||||
// Returns the table from the table cell:
|
||||
// table <-- thead or tbody <-- tr <-- th or td (cell)
|
||||
ContentState.prototype.getTableFromTableCell = function (block) {
|
||||
const table = this.getParent(this.getParent(this.getParent(block)))
|
||||
if (table && table.type !== 'table') {
|
||||
return null
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
ContentState.prototype.tableCellHandler = function (event) {
|
||||
const { start, end } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const endBlock = this.getBlock(end.key)
|
||||
const { type: startType } = startBlock
|
||||
const { type: endType } = endBlock
|
||||
if (/th|td/.test(startType) && /th|td/.test(endType)) {
|
||||
if (start.key === end.key) {
|
||||
const { text, key } = startBlock
|
||||
this.cursor = {
|
||||
start: { key, offset: 0 },
|
||||
end: { key, offset: text.length }
|
||||
}
|
||||
} else {
|
||||
const startTable = this.getTableFromTableCell(startBlock)
|
||||
const endTable = this.getTableFromTableCell(endBlock)
|
||||
// Check whether both blocks are in the same table.
|
||||
if (!startTable || !endTable) {
|
||||
console.error('No table found or invalid type.')
|
||||
return
|
||||
} else if (startTable.key !== endTable.key) {
|
||||
// Select entire document
|
||||
return
|
||||
}
|
||||
const firstTableCell = this.firstInDescendant(startTable)
|
||||
const lastTableCell = this.lastInDescendant(startTable)
|
||||
if (!firstTableCell || !/th|td/.test(firstTableCell.type) ||
|
||||
!lastTableCell || !/th|td/.test(lastTableCell.type)) {
|
||||
console.error('No table cell found or invalid type.')
|
||||
return
|
||||
}
|
||||
const { key: startKey } = firstTableCell
|
||||
const { key: endKey, text } = lastTableCell
|
||||
this.cursor = {
|
||||
start: { key: startKey, offset: 0 },
|
||||
end: { key: endKey, offset: text.length }
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.partialRender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default selectionCtrl
|
@ -40,25 +40,27 @@ const BOTH_SIDES_FORMATS = ['strong', 'em', 'inline_code', 'image', 'link', 'ref
|
||||
|
||||
const tabCtrl = ContentState => {
|
||||
ContentState.prototype.findNextCell = function (block) {
|
||||
if (!(/td|th/.test(block.type))) {
|
||||
if (block.functionType !== 'cellContent') {
|
||||
throw new Error('only th and td can have next cell')
|
||||
}
|
||||
const nextSibling = this.getBlock(block.nextSibling)
|
||||
const parent = this.getBlock(block.parent)
|
||||
const tbOrTh = this.getBlock(parent.parent)
|
||||
const cellBlock = this.getParent(block)
|
||||
const nextSibling = this.getBlock(cellBlock.nextSibling)
|
||||
const rowBlock = this.getBlock(cellBlock.parent)
|
||||
const tbOrTh = this.getBlock(rowBlock.parent)
|
||||
if (nextSibling) {
|
||||
return nextSibling
|
||||
return this.firstInDescendant(nextSibling)
|
||||
} else {
|
||||
if (parent.nextSibling) {
|
||||
const nextRow = this.getBlock(parent.nextSibling)
|
||||
return nextRow.children[0]
|
||||
if (rowBlock.nextSibling) {
|
||||
const nextRow = this.getBlock(rowBlock.nextSibling)
|
||||
return this.firstInDescendant(nextRow)
|
||||
} else if (tbOrTh.type === 'thead') {
|
||||
const tBody = this.getBlock(tbOrTh.nextSibling)
|
||||
if (tBody && tBody.children.length) {
|
||||
return tBody.children[0].children[0]
|
||||
return this.firstInDescendant(tBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -369,19 +371,22 @@ const tabCtrl = ContentState => {
|
||||
|
||||
// Handle `tab` key in table cell.
|
||||
let nextCell
|
||||
if (start.key === end.key && /th|td/.test(startBlock.type)) {
|
||||
if (start.key === end.key && startBlock.functionType === 'cellContent') {
|
||||
nextCell = this.findNextCell(startBlock)
|
||||
} else if (/th|td/.test(endBlock.type)) {
|
||||
} else if (endBlock.functionType === 'cellContent') {
|
||||
nextCell = endBlock
|
||||
}
|
||||
if (nextCell) {
|
||||
const key = nextCell.key
|
||||
const { key } = nextCell
|
||||
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset: 0 },
|
||||
end: { key, offset: 0 }
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
|
||||
return this.partialRender()
|
||||
const figure = this.closest(nextCell, 'figure')
|
||||
return this.singleRender(figure)
|
||||
}
|
||||
|
||||
if (this.isIndentableListItem()) {
|
||||
|
@ -3,25 +3,32 @@ import { isLengthEven, getParagraphReference } from '../utils'
|
||||
const TABLE_BLOCK_REG = /^\|.*?(\\*)\|.*?(\\*)\|/
|
||||
|
||||
const tableBlockCtrl = ContentState => {
|
||||
ContentState.prototype.createTableInFigure = function ({ rows, columns }, headerTexts) {
|
||||
const table = this.createBlock('table')
|
||||
ContentState.prototype.createTableInFigure = function ({ rows, columns }, tableContents = []) {
|
||||
const table = this.createBlock('table', {
|
||||
row: rows - 1, // zero base
|
||||
column: columns - 1
|
||||
})
|
||||
const tHead = this.createBlock('thead')
|
||||
const tBody = this.createBlock('tbody')
|
||||
|
||||
table.row = rows - 1 // zero base
|
||||
table.column = columns - 1 // zero base
|
||||
let i
|
||||
let j
|
||||
for (i = 0; i < rows; i++) {
|
||||
const rowBlock = this.createBlock('tr')
|
||||
i === 0 ? this.appendChild(tHead, rowBlock) : this.appendChild(tBody, rowBlock)
|
||||
const rowContents = tableContents[i]
|
||||
for (j = 0; j < columns; j++) {
|
||||
const cell = this.createBlock(i === 0 ? 'th' : 'td', {
|
||||
text: headerTexts && i === 0 ? headerTexts[j] : ''
|
||||
align: rowContents ? rowContents[j].align : '',
|
||||
column: j
|
||||
})
|
||||
const cellContent = this.createBlock('span', {
|
||||
text: rowContents ? rowContents[j].text : '',
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
|
||||
this.appendChild(cell, cellContent)
|
||||
this.appendChild(rowBlock, cell)
|
||||
cell.align = ''
|
||||
cell.column = j
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,52 +40,25 @@ const tableBlockCtrl = ContentState => {
|
||||
return table
|
||||
}
|
||||
|
||||
ContentState.prototype.closest = function (block, type) {
|
||||
if (!block) {
|
||||
return null
|
||||
}
|
||||
if (block.type === type) {
|
||||
return block
|
||||
} else {
|
||||
const parent = this.getParent(block)
|
||||
return this.closest(parent, type)
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.getAnchor = function (block) {
|
||||
const { type, functionType } = block
|
||||
switch (type) {
|
||||
case 'span':
|
||||
if (functionType === 'codeContent') {
|
||||
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
||||
} else {
|
||||
return this.getParent(block)
|
||||
}
|
||||
|
||||
case 'th':
|
||||
case 'td':
|
||||
return this.closest(block, 'figure')
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.createFigure = function ({ rows, columns }) {
|
||||
const { end } = this.cursor
|
||||
const table = this.createTableInFigure({ rows, columns })
|
||||
const figureBlock = this.createBlock('figure')
|
||||
figureBlock.functionType = 'table'
|
||||
const figureBlock = this.createBlock('figure', {
|
||||
functionType: 'table'
|
||||
})
|
||||
const endBlock = this.getBlock(end.key)
|
||||
const anchor = this.getAnchor(endBlock)
|
||||
|
||||
if (!anchor) return
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertAfter(figureBlock, anchor)
|
||||
if (/p|h\d/.test(anchor.type) && !endBlock.text) {
|
||||
this.removeBlock(anchor)
|
||||
}
|
||||
this.appendChild(figureBlock, table)
|
||||
const key = table.children[0].children[0].children[0].key // fist cell key in thead
|
||||
const { key } = this.firstInDescendant(table) // fist cell key in thead
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
@ -112,10 +92,11 @@ const tableBlockCtrl = ContentState => {
|
||||
rowHeader.push('')
|
||||
}
|
||||
}
|
||||
|
||||
const columns = rowHeader.length
|
||||
const rows = 2
|
||||
|
||||
const table = this.createTableInFigure({ rows, columns }, rowHeader)
|
||||
const table = this.createTableInFigure({ rows, columns }, [rowHeader.map(text => ({ text, align: '' }))])
|
||||
|
||||
block.type = 'figure'
|
||||
block.text = ''
|
||||
@ -123,21 +104,20 @@ const tableBlockCtrl = ContentState => {
|
||||
block.functionType = 'table'
|
||||
this.appendChild(block, table)
|
||||
|
||||
return table.children[1].children[0].children[0] // first cell in tbody
|
||||
return this.firstInDescendant(table.children[1]) // first cell content in tbody
|
||||
}
|
||||
|
||||
ContentState.prototype.tableToolBarClick = function (type) {
|
||||
const { start: { key } } = this.cursor
|
||||
const block = this.getBlock(key)
|
||||
if (!(/td|th/.test(block.type))) throw new Error('table is not active')
|
||||
const { column, align } = block
|
||||
const getTable = td => {
|
||||
const row = this.getBlock(block.parent)
|
||||
const rowContainer = this.getBlock(row.parent)
|
||||
return this.getBlock(rowContainer.parent)
|
||||
const parentBlock = this.getParent(block)
|
||||
if (block.functionType !== 'cellContent') {
|
||||
throw new Error('table is not active')
|
||||
}
|
||||
const table = getTable(block)
|
||||
const { column, align } = parentBlock
|
||||
const table = this.closest(block, 'table')
|
||||
const figure = this.getBlock(table.parent)
|
||||
|
||||
switch (type) {
|
||||
case 'left':
|
||||
case 'center':
|
||||
@ -171,26 +151,37 @@ const tableBlockCtrl = ContentState => {
|
||||
case 'table': {
|
||||
const { eventCenter } = this.muya
|
||||
const figureKey = figure.key
|
||||
const tableLable = document.querySelector(`#${figureKey} [data-label=table]`)
|
||||
const tableEle = document.querySelector(`#${figureKey} [data-label=table]`)
|
||||
const { row = 1, column = 1 } = table // zero base
|
||||
|
||||
const handler = (row, column) => {
|
||||
const { row: oldRow, column: oldColumn } = table
|
||||
const tBody = table.children[1]
|
||||
let tBody = table.children[1]
|
||||
const tHead = table.children[0]
|
||||
const headerRow = tHead.children[0]
|
||||
const bodyRows = tBody.children
|
||||
const bodyRows = tBody ? tBody.children : []
|
||||
let i
|
||||
if (column > oldColumn) {
|
||||
for (i = oldColumn + 1; i <= column; i++) {
|
||||
const th = this.createBlock('th')
|
||||
th.column = i
|
||||
th.align = ''
|
||||
const th = this.createBlock('th', {
|
||||
column: i,
|
||||
align: ''
|
||||
})
|
||||
const thContent = this.createBlock('span', {
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
this.appendChild(th, thContent)
|
||||
this.appendChild(headerRow, th)
|
||||
bodyRows.forEach(bodyRow => {
|
||||
const td = this.createBlock('td')
|
||||
td.column = i
|
||||
td.align = ''
|
||||
const td = this.createBlock('td', {
|
||||
column: i,
|
||||
align: ''
|
||||
})
|
||||
|
||||
const tdContent = this.createBlock('span', {
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
this.appendChild(td, tdContent)
|
||||
this.appendChild(bodyRow, td)
|
||||
})
|
||||
}
|
||||
@ -209,16 +200,25 @@ const tableBlockCtrl = ContentState => {
|
||||
const lastRow = tBody.children[tBody.children.length - 1]
|
||||
this.removeBlock(lastRow)
|
||||
}
|
||||
if (tBody.children.length === 0) {
|
||||
this.removeBlock(tBody)
|
||||
}
|
||||
} else if (row > oldRow) {
|
||||
const oneRowInBody = bodyRows[0]
|
||||
if (!tBody) {
|
||||
tBody = this.createBlock('tbody')
|
||||
this.appendChild(table, tBody)
|
||||
}
|
||||
const oneHeaderRow = tHead.children[0]
|
||||
for (i = oldRow + 1; i <= row; i++) {
|
||||
const bodyRow = this.createRow(oneRowInBody)
|
||||
const bodyRow = this.createRow(oneHeaderRow, false)
|
||||
|
||||
this.appendChild(tBody, bodyRow)
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(table, { row, column })
|
||||
|
||||
const cursorBlock = headerRow.children[0]
|
||||
const cursorBlock = this.firstInDescendant(headerRow)
|
||||
const key = cursorBlock.key
|
||||
const offset = cursorBlock.text.length
|
||||
this.cursor = {
|
||||
@ -228,66 +228,69 @@ const tableBlockCtrl = ContentState => {
|
||||
this.muya.eventCenter.dispatch('stateChange')
|
||||
this.partialRender()
|
||||
}
|
||||
const reference = getParagraphReference(tableLable, tableLable.id)
|
||||
|
||||
const reference = getParagraphReference(tableEle, tableEle.id)
|
||||
eventCenter.dispatch('muya-table-picker', { row, column }, reference, handler.bind(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert/remove row/column
|
||||
ContentState.prototype.editTable = function ({ location, action, target }) {
|
||||
const { start, end } = this.cursor
|
||||
const block = this.getBlock(start.key)
|
||||
if (start.key !== end.key || !/th|td/.test(block.type)) {
|
||||
ContentState.prototype.editTable = function ({ location, action, target }, cellContentKey) {
|
||||
let block
|
||||
let start
|
||||
let end
|
||||
if (cellContentKey) {
|
||||
block = this.getBlock(cellContentKey)
|
||||
} else {
|
||||
({ start, end } = this.cursor)
|
||||
if (start.key !== end.key) {
|
||||
throw new Error('Cursor is not in one block, can not editTable')
|
||||
}
|
||||
|
||||
block = this.getBlock(start.key)
|
||||
}
|
||||
|
||||
if (block.functionType !== 'cellContent') {
|
||||
throw new Error('Cursor is not in table block, so you can not insert/edit row/column')
|
||||
}
|
||||
const currentRow = this.getParent(block)
|
||||
const rowContainer = this.getParent(currentRow) // tbody or thead
|
||||
const table = this.getParent(rowContainer)
|
||||
|
||||
const cellBlock = this.getParent(block)
|
||||
const currentRow = this.getParent(cellBlock)
|
||||
const table = this.closest(block, 'table')
|
||||
const thead = table.children[0]
|
||||
const tbody = table.children[1]
|
||||
const { column } = table
|
||||
const columnIndex = currentRow.children.indexOf(block)
|
||||
let cursorBlock
|
||||
const columnIndex = currentRow.children.indexOf(cellBlock)
|
||||
// const rowIndex = rowContainer.type === 'thead' ? 0 : tbody.children.indexOf(currentRow) + 1
|
||||
|
||||
const createRow = (column, isHeader) => {
|
||||
const tr = this.createBlock('tr')
|
||||
let i
|
||||
for (i = 0; i <= column; i++) {
|
||||
const cell = this.createBlock(isHeader ? 'th' : 'td')
|
||||
cell.align = currentRow.children[i].align
|
||||
cell.column = i
|
||||
this.appendChild(tr, cell)
|
||||
}
|
||||
return tr
|
||||
}
|
||||
let cursorBlock
|
||||
|
||||
if (target === 'row') {
|
||||
if (action === 'insert') {
|
||||
const newRow = (location === 'previous' && block.type === 'th')
|
||||
? createRow(column, true)
|
||||
: createRow(column, false)
|
||||
const newRow = (location === 'previous' && cellBlock.type === 'th')
|
||||
? this.createRow(currentRow, true)
|
||||
: this.createRow(currentRow, false)
|
||||
if (location === 'previous') {
|
||||
this.insertBefore(newRow, currentRow)
|
||||
if (block.type === 'th') {
|
||||
if (cellBlock.type === 'th') {
|
||||
this.removeBlock(currentRow)
|
||||
currentRow.children.forEach(cell => (cell.type = 'td'))
|
||||
const firstRow = tbody.children[0]
|
||||
this.insertBefore(currentRow, firstRow)
|
||||
}
|
||||
} else {
|
||||
if (block.type === 'th') {
|
||||
if (cellBlock.type === 'th') {
|
||||
const firstRow = tbody.children[0]
|
||||
this.insertBefore(newRow, firstRow)
|
||||
} else {
|
||||
this.insertAfter(newRow, currentRow)
|
||||
}
|
||||
}
|
||||
cursorBlock = newRow.children[columnIndex]
|
||||
cursorBlock = newRow.children[columnIndex].children[0]
|
||||
// handle remove row
|
||||
} else {
|
||||
if (location === 'previous') {
|
||||
if (block.type === 'th') return
|
||||
if (cellBlock.type === 'th') return
|
||||
if (!currentRow.preSibling) {
|
||||
const headRow = thead.children[0]
|
||||
if (!currentRow.nextSibling) return
|
||||
@ -300,20 +303,20 @@ const tableBlockCtrl = ContentState => {
|
||||
this.removeBlock(preRow)
|
||||
}
|
||||
} else if (location === 'current') {
|
||||
if (block.type === 'th' && tbody.children.length >= 2) {
|
||||
if (cellBlock.type === 'th' && tbody.children.length >= 2) {
|
||||
const firstRow = tbody.children[0]
|
||||
this.removeBlock(currentRow)
|
||||
this.removeBlock(firstRow)
|
||||
this.appendChild(thead, firstRow)
|
||||
firstRow.children.forEach(cell => (cell.type = 'th'))
|
||||
cursorBlock = firstRow.children[columnIndex]
|
||||
cursorBlock = firstRow.children[columnIndex].children[0]
|
||||
}
|
||||
if (block.type === 'td' && (currentRow.preSibling || currentRow.nextSibling)) {
|
||||
cursorBlock = (this.getNextSibling(currentRow) || this.getPreSibling(currentRow)).children[columnIndex]
|
||||
if (cellBlock.type === 'td' && (currentRow.preSibling || currentRow.nextSibling)) {
|
||||
cursorBlock = (this.getNextSibling(currentRow) || this.getPreSibling(currentRow)).children[columnIndex].children[0]
|
||||
this.removeBlock(currentRow)
|
||||
}
|
||||
} else {
|
||||
if (block.type === 'th') {
|
||||
if (cellBlock.type === 'th') {
|
||||
if (tbody.children.length >= 2) {
|
||||
const firstRow = tbody.children[0]
|
||||
this.removeBlock(firstRow)
|
||||
@ -322,7 +325,9 @@ const tableBlockCtrl = ContentState => {
|
||||
}
|
||||
} else {
|
||||
const nextRow = this.getNextSibling(currentRow)
|
||||
if (nextRow) this.removeBlock(nextRow)
|
||||
if (nextRow) {
|
||||
this.removeBlock(nextRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -330,8 +335,13 @@ const tableBlockCtrl = ContentState => {
|
||||
if (action === 'insert') {
|
||||
[...thead.children, ...tbody.children].forEach(tableRow => {
|
||||
const targetCell = tableRow.children[columnIndex]
|
||||
const cell = this.createBlock(targetCell.type)
|
||||
cell.align = ''
|
||||
const cell = this.createBlock(targetCell.type, {
|
||||
align: ''
|
||||
})
|
||||
const cellContent = this.createBlock('span', {
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
this.appendChild(cell, cellContent)
|
||||
if (location === 'left') {
|
||||
this.insertBefore(cell, targetCell)
|
||||
} else {
|
||||
@ -341,7 +351,7 @@ const tableBlockCtrl = ContentState => {
|
||||
cell.column = i
|
||||
})
|
||||
})
|
||||
cursorBlock = location === 'left' ? this.getPreSibling(block) : this.getNextSibling(block)
|
||||
cursorBlock = location === 'left' ? this.getPreSibling(cellBlock).children[0] : this.getNextSibling(cellBlock).children[0]
|
||||
// handle remove column
|
||||
} else {
|
||||
if (currentRow.children.length <= 2) return
|
||||
@ -350,7 +360,7 @@ const tableBlockCtrl = ContentState => {
|
||||
const removeCell = location === 'left'
|
||||
? this.getPreSibling(targetCell)
|
||||
: (location === 'current' ? targetCell : this.getNextSibling(targetCell))
|
||||
if (removeCell === block) {
|
||||
if (removeCell === cellBlock) {
|
||||
cursorBlock = this.findNextBlockInLocation(block)
|
||||
}
|
||||
|
||||
@ -388,8 +398,8 @@ const tableBlockCtrl = ContentState => {
|
||||
.filter(p => endParents.includes(p))
|
||||
|
||||
if (affiliation.length) {
|
||||
const table = affiliation.find(p => p.type === 'figure')
|
||||
return table
|
||||
const figure = affiliation.find(p => p.type === 'figure')
|
||||
return figure
|
||||
}
|
||||
}
|
||||
|
||||
|
364
src/muya/lib/contentState/tableDragBarCtrl.js
Normal file
@ -0,0 +1,364 @@
|
||||
const calculateAspects = (tableId, barType) => {
|
||||
const table = document.querySelector(`#${tableId}`)
|
||||
if (barType === 'bottom') {
|
||||
const firstRow = table.querySelector('tr')
|
||||
return Array.from(firstRow.children).map(cell => cell.clientWidth)
|
||||
} else {
|
||||
return Array.from(table.querySelectorAll('tr')).map(row => row.clientHeight)
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllTableCells = tableId => {
|
||||
const table = document.querySelector(`#${tableId}`)
|
||||
const rows = table.querySelectorAll('tr')
|
||||
const cells = []
|
||||
for (const row of Array.from(rows)) {
|
||||
cells.push(Array.from(row.children))
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
export const getIndex = (barType, cell) => {
|
||||
if (cell.tagName === 'SPAN') {
|
||||
cell = cell.parentNode
|
||||
}
|
||||
const row = cell.parentNode
|
||||
if (barType === 'bottom') {
|
||||
return Array.from(row.children).indexOf(cell)
|
||||
} else {
|
||||
const rowContainer = row.parentNode
|
||||
if (rowContainer.tagName === 'THEAD') {
|
||||
return 0
|
||||
} else {
|
||||
return Array.from(rowContainer.children).indexOf(row) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDragCells = (tableId, barType, index) => {
|
||||
const table = document.querySelector(`#${tableId}`)
|
||||
const dragCells = []
|
||||
if (barType === 'left') {
|
||||
if (index === 0) {
|
||||
dragCells.push(...table.querySelectorAll('th'))
|
||||
} else {
|
||||
const row = table.querySelector('tbody').children[index - 1]
|
||||
dragCells.push(...row.children)
|
||||
}
|
||||
} else {
|
||||
const rows = Array.from(table.querySelectorAll('tr'))
|
||||
const len = rows.length
|
||||
let i
|
||||
for (i = 0; i < len; i++) {
|
||||
dragCells.push(rows[i].children[index])
|
||||
}
|
||||
}
|
||||
return dragCells
|
||||
}
|
||||
|
||||
const tableDragBarCtrl = ContentState => {
|
||||
ContentState.prototype.handleMouseDown = function (event) {
|
||||
event.preventDefault()
|
||||
const { eventCenter } = this.muya
|
||||
const { clientX, clientY, target } = event
|
||||
const tableId = target.closest('table').id
|
||||
const barType = target.classList.contains('left') ? 'left' : 'bottom'
|
||||
const index = getIndex(barType, target)
|
||||
const aspects = calculateAspects(tableId, barType)
|
||||
this.dragInfo = {
|
||||
tableId,
|
||||
clientX,
|
||||
clientY,
|
||||
barType,
|
||||
index,
|
||||
curIndex: index,
|
||||
dragCells: getDragCells(tableId, barType, index),
|
||||
cells: getAllTableCells(tableId),
|
||||
aspects,
|
||||
offset: 0
|
||||
}
|
||||
|
||||
for (const row of this.dragInfo.cells) {
|
||||
for (const cell of row) {
|
||||
if (!this.dragInfo.dragCells.includes(cell)) {
|
||||
cell.classList.add('ag-cell-transform')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mouseMoveId = eventCenter.attachDOMEvent(document, 'mousemove', this.handleMouseMove.bind(this))
|
||||
const mouseUpId = eventCenter.attachDOMEvent(document, 'mouseup', this.handleMouseUp.bind(this))
|
||||
this.dragEventIds.push(mouseMoveId, mouseUpId)
|
||||
}
|
||||
|
||||
ContentState.prototype.handleMouseMove = function (event) {
|
||||
if (!this.dragInfo) {
|
||||
return
|
||||
}
|
||||
const { barType } = this.dragInfo
|
||||
const attrName = barType === 'bottom' ? 'clientX' : 'clientY'
|
||||
const offset = this.dragInfo.offset = event[attrName] - this.dragInfo[attrName]
|
||||
if (Math.abs(offset) < 5) {
|
||||
return
|
||||
}
|
||||
this.isDragTableBar = true
|
||||
this.hideUnnecessaryBar()
|
||||
this.calculateCurIndex()
|
||||
this.setDragTargetStyle()
|
||||
this.setSwitchStyle()
|
||||
}
|
||||
|
||||
ContentState.prototype.handleMouseUp = function (event) {
|
||||
const { eventCenter } = this.muya
|
||||
for (const id of this.dragEventIds) {
|
||||
eventCenter.detachDOMEvent(id)
|
||||
}
|
||||
this.dragEventIds = []
|
||||
if (!this.isDragTableBar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setDropTargetStyle()
|
||||
|
||||
// The drop animation need 300ms.
|
||||
setTimeout(() => {
|
||||
this.switchTableData()
|
||||
this.resetDragTableBar()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
ContentState.prototype.hideUnnecessaryBar = function () {
|
||||
const { barType } = this.dragInfo
|
||||
const hideClassName = barType === 'bottom' ? 'left' : 'bottom'
|
||||
const needHideBar = document.querySelector(`.ag-drag-handler.${hideClassName}`)
|
||||
if (needHideBar) {
|
||||
needHideBar.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.calculateCurIndex = function () {
|
||||
let { offset, aspects, index } = this.dragInfo
|
||||
let curIndex = index
|
||||
const len = aspects.length
|
||||
let i
|
||||
if (offset > 0) {
|
||||
for (i = index; i < len; i++) {
|
||||
const aspect = aspects[i]
|
||||
if (i === index) {
|
||||
offset -= Math.floor(aspect / 2)
|
||||
} else {
|
||||
offset -= aspect
|
||||
}
|
||||
if (offset < 0) {
|
||||
break
|
||||
} else {
|
||||
curIndex++
|
||||
}
|
||||
}
|
||||
} else if (offset < 0) {
|
||||
for (i = index; i >= 0; i--) {
|
||||
const aspect = aspects[i]
|
||||
if (i === index) {
|
||||
offset += Math.floor(aspect / 2)
|
||||
} else {
|
||||
offset += aspect
|
||||
}
|
||||
if (offset > 0) {
|
||||
break
|
||||
} else {
|
||||
curIndex--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.dragInfo.curIndex = Math.max(0, Math.min(curIndex, len - 1))
|
||||
}
|
||||
|
||||
ContentState.prototype.setDragTargetStyle = function () {
|
||||
const { offset, barType, dragCells } = this.dragInfo
|
||||
|
||||
for (const cell of dragCells) {
|
||||
if (!cell.classList.contains('ag-drag-cell')) {
|
||||
cell.classList.add('ag-drag-cell')
|
||||
cell.classList.add(`ag-drag-${barType}`)
|
||||
}
|
||||
const valueName = barType === 'bottom' ? 'translateX' : 'translateY'
|
||||
cell.style.transform = `${valueName}(${offset}px)`
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.setSwitchStyle = function () {
|
||||
const { index, offset, curIndex, barType, aspects, cells } = this.dragInfo
|
||||
const aspect = aspects[index]
|
||||
const len = aspects.length
|
||||
|
||||
let i
|
||||
if (offset > 0) {
|
||||
if (barType === 'bottom') {
|
||||
for (const row of cells) {
|
||||
for (i = 0; i < len; i++) {
|
||||
const cell = row[i]
|
||||
if (i > index && i <= curIndex) {
|
||||
cell.style.transform = `translateX(${-aspect}px)`
|
||||
} else if (i !== index) {
|
||||
cell.style.transform = 'translateX(0px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < len; i++) {
|
||||
const row = cells[i]
|
||||
for (const cell of row) {
|
||||
if (i > index && i <= curIndex) {
|
||||
cell.style.transform = `translateY(${-aspect}px)`
|
||||
} else if (i !== index) {
|
||||
cell.style.transform = 'translateY(0px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (barType === 'bottom') {
|
||||
for (const row of cells) {
|
||||
for (i = 0; i < len; i++) {
|
||||
const cell = row[i]
|
||||
if (i >= curIndex && i < index) {
|
||||
cell.style.transform = `translateX(${aspect}px)`
|
||||
} else if (i !== index) {
|
||||
cell.style.transform = 'translateX(0px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < len; i++) {
|
||||
const row = cells[i]
|
||||
for (const cell of row) {
|
||||
if (i >= curIndex && i < index) {
|
||||
cell.style.transform = `translateY(${aspect}px)`
|
||||
} else if (i !== index) {
|
||||
cell.style.transform = 'translateY(0px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.setDropTargetStyle = function () {
|
||||
const { dragCells, barType, curIndex, index, aspects, offset } = this.dragInfo
|
||||
let move = 0
|
||||
let i
|
||||
if (offset > 0) {
|
||||
for (i = index + 1; i <= curIndex; i++) {
|
||||
move += aspects[i]
|
||||
}
|
||||
} else {
|
||||
for (i = curIndex; i < index; i++) {
|
||||
move -= aspects[i]
|
||||
}
|
||||
}
|
||||
for (const cell of dragCells) {
|
||||
cell.classList.remove('ag-drag-cell')
|
||||
cell.classList.remove(`ag-drag-${barType}`)
|
||||
cell.classList.add('ag-cell-transform')
|
||||
const valueName = barType === 'bottom' ? 'translateX' : 'translateY'
|
||||
cell.style.transform = `${valueName}(${move}px)`
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.switchTableData = function () {
|
||||
const { barType, index, curIndex, tableId, offset } = this.dragInfo
|
||||
const table = this.getBlock(tableId)
|
||||
const tHead = table.children[0]
|
||||
const tBody = table.children[1]
|
||||
const rows = [tHead.children[0], ...(tBody ? tBody.children : [])]
|
||||
let i
|
||||
|
||||
if (index !== curIndex) {
|
||||
// Cursor in the same cell.
|
||||
const { start, end } = this.cursor
|
||||
let key = null
|
||||
if (barType === 'bottom') {
|
||||
for (const row of rows) {
|
||||
const isCursorCell = row.children[index].children[0].key === start.key
|
||||
const { text } = row.children[index].children[0]
|
||||
const { align } = row.children[index]
|
||||
if (offset > 0) {
|
||||
for (i = index; i < curIndex; i++) {
|
||||
row.children[i].children[0].text = row.children[i + 1].children[0].text
|
||||
row.children[i].align = row.children[i + 1].align
|
||||
}
|
||||
row.children[curIndex].children[0].text = text
|
||||
row.children[curIndex].align = align
|
||||
} else {
|
||||
for (i = index; i > curIndex; i--) {
|
||||
row.children[i].children[0].text = row.children[i - 1].children[0].text
|
||||
row.children[i].align = row.children[i - 1].align
|
||||
}
|
||||
row.children[curIndex].children[0].text = text
|
||||
row.children[curIndex].align = align
|
||||
}
|
||||
if (isCursorCell) {
|
||||
key = row.children[curIndex].children[0].key
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let column = null
|
||||
const temp = rows[index].children.map((cell, i) => {
|
||||
if (cell.children[0].key === start.key) {
|
||||
column = i
|
||||
}
|
||||
return cell.children[0].text
|
||||
})
|
||||
if (offset > 0) {
|
||||
for (i = index; i < curIndex; i++) {
|
||||
rows[i].children.forEach((cell, ii) => {
|
||||
cell.children[0].text = rows[i + 1].children[ii].children[0].text
|
||||
})
|
||||
}
|
||||
rows[curIndex].children.forEach((cell, i) => {
|
||||
if (i === column) {
|
||||
key = cell.children[0].key
|
||||
}
|
||||
cell.children[0].text = temp[i]
|
||||
})
|
||||
} else {
|
||||
for (i = index; i > curIndex; i--) {
|
||||
rows[i].children.forEach((cell, ii) => {
|
||||
cell.children[0].text = rows[i - 1].children[ii].children[0].text
|
||||
})
|
||||
}
|
||||
rows[curIndex].children.forEach((cell, i) => {
|
||||
if (i === column) {
|
||||
key = cell.children[0].key
|
||||
}
|
||||
cell.children[0].text = temp[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
if (key) {
|
||||
this.cursor = {
|
||||
start: {
|
||||
key,
|
||||
offset: start.offset
|
||||
},
|
||||
end: {
|
||||
key,
|
||||
offset: end.offset
|
||||
}
|
||||
}
|
||||
return this.singleRender(table)
|
||||
} else {
|
||||
return this.partialRender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.resetDragTableBar = function () {
|
||||
this.dragInfo = null
|
||||
this.isDragTableBar = false
|
||||
}
|
||||
}
|
||||
|
||||
export default tableDragBarCtrl
|
266
src/muya/lib/contentState/tableSelectCellsCtrl.js
Normal file
@ -0,0 +1,266 @@
|
||||
import { getAllTableCells, getIndex } from './tableDragBarCtrl'
|
||||
|
||||
const tableSelectCellsCtrl = ContentState => {
|
||||
ContentState.prototype.handleCellMouseDown = function (event) {
|
||||
if (event.buttons === 2) {
|
||||
// the contextmenu is emit.
|
||||
return
|
||||
}
|
||||
const { eventCenter } = this.muya
|
||||
const { target } = event
|
||||
const cell = target.closest('th') || target.closest('td')
|
||||
const tableId = target.closest('table').id
|
||||
const row = getIndex('left', cell)
|
||||
const column = getIndex('bottom', cell)
|
||||
this.cellSelectInfo = {
|
||||
tableId,
|
||||
anchor: {
|
||||
key: cell.id,
|
||||
row,
|
||||
column
|
||||
},
|
||||
focus: null,
|
||||
isStartSelect: false,
|
||||
cells: getAllTableCells(tableId),
|
||||
selectedCells: []
|
||||
}
|
||||
|
||||
const mouseMoveId = eventCenter.attachDOMEvent(document.body, 'mousemove', this.handleCellMouseMove.bind(this))
|
||||
const mouseUpId = eventCenter.attachDOMEvent(document.body, 'mouseup', this.handleCellMouseUp.bind(this))
|
||||
this.cellSelectEventIds.push(mouseMoveId, mouseUpId)
|
||||
}
|
||||
|
||||
ContentState.prototype.handleCellMouseMove = function (event) {
|
||||
const { target } = event
|
||||
const cell = target.closest('th') || target.closest('td')
|
||||
const table = target.closest('table')
|
||||
const isOverSameTableCell = cell && table && table.id === this.cellSelectInfo.tableId
|
||||
if (isOverSameTableCell && cell.id !== this.cellSelectInfo.anchor.key) {
|
||||
this.cellSelectInfo.isStartSelect = true
|
||||
this.muya.blur()
|
||||
}
|
||||
if (isOverSameTableCell && this.cellSelectInfo.isStartSelect) {
|
||||
const row = getIndex('left', cell)
|
||||
const column = getIndex('bottom', cell)
|
||||
this.cellSelectInfo.focus = {
|
||||
key: cell.key,
|
||||
row,
|
||||
column
|
||||
}
|
||||
} else {
|
||||
this.cellSelectInfo.focus = null
|
||||
}
|
||||
|
||||
this.calculateSelectedCells()
|
||||
this.setSelectedCellsStyle()
|
||||
}
|
||||
|
||||
ContentState.prototype.handleCellMouseUp = function (event) {
|
||||
const { eventCenter } = this.muya
|
||||
for (const id of this.cellSelectEventIds) {
|
||||
eventCenter.detachDOMEvent(id)
|
||||
}
|
||||
this.cellSelectEventIds = []
|
||||
if (this.cellSelectInfo && this.cellSelectInfo.isStartSelect) {
|
||||
event.preventDefault()
|
||||
const { tableId, selectedCells, anchor, focus } = this.cellSelectInfo
|
||||
// Mouse up outside table, the focus is null
|
||||
if (!focus) {
|
||||
return
|
||||
}
|
||||
// We need to handle this after click event, because click event is emited after mouseup(mouseup will be followed by a click envent), but we set
|
||||
// the `selectedTableCells` to null when click event emited.
|
||||
setTimeout(() => {
|
||||
this.selectedTableCells = {
|
||||
tableId,
|
||||
row: Math.abs(anchor.row - focus.row) + 1, // 1 base
|
||||
column: Math.abs(anchor.column - focus.column) + 1, // 1 base
|
||||
cells: selectedCells.map(c => {
|
||||
delete c.ele
|
||||
return c
|
||||
})
|
||||
}
|
||||
this.cellSelectInfo = null
|
||||
const table = this.getBlock(tableId)
|
||||
return this.singleRender(table, false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.calculateSelectedCells = function () {
|
||||
const { anchor, focus, cells } = this.cellSelectInfo
|
||||
this.cellSelectInfo.selectedCells = []
|
||||
if (focus) {
|
||||
const startRowIndex = Math.min(anchor.row, focus.row)
|
||||
const endRowIndex = Math.max(anchor.row, focus.row)
|
||||
const startColIndex = Math.min(anchor.column, focus.column)
|
||||
const endColIndex = Math.max(anchor.column, focus.column)
|
||||
let i
|
||||
let j
|
||||
for (i = startRowIndex; i <= endRowIndex; i++) {
|
||||
const row = cells[i]
|
||||
for (j = startColIndex; j <= endColIndex; j++) {
|
||||
const cell = row[j]
|
||||
const cellBlock = this.getBlock(cell.id)
|
||||
this.cellSelectInfo.selectedCells.push({
|
||||
ele: cell,
|
||||
key: cell.id,
|
||||
text: cellBlock.children[0].text,
|
||||
align: cellBlock.align,
|
||||
top: i === startRowIndex,
|
||||
right: j === endColIndex,
|
||||
bottom: i === endRowIndex,
|
||||
left: j === startColIndex
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.setSelectedCellsStyle = function () {
|
||||
const { selectedCells, cells } = this.cellSelectInfo
|
||||
for (const row of cells) {
|
||||
for (const cell of row) {
|
||||
cell.classList.remove('ag-cell-selected')
|
||||
cell.classList.remove('ag-cell-border-top')
|
||||
cell.classList.remove('ag-cell-border-right')
|
||||
cell.classList.remove('ag-cell-border-bottom')
|
||||
cell.classList.remove('ag-cell-border-left')
|
||||
}
|
||||
}
|
||||
|
||||
for (const cell of selectedCells) {
|
||||
const { ele, top, right, bottom, left } = cell
|
||||
ele.classList.add('ag-cell-selected')
|
||||
if (top) {
|
||||
ele.classList.add('ag-cell-border-top')
|
||||
}
|
||||
if (right) {
|
||||
ele.classList.add('ag-cell-border-right')
|
||||
}
|
||||
if (bottom) {
|
||||
ele.classList.add('ag-cell-border-bottom')
|
||||
}
|
||||
if (left) {
|
||||
ele.classList.add('ag-cell-border-left')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the content of selected table cell, delete the row/column if selected one row/column without content.
|
||||
// Delete the table if the selected whole table is empty.
|
||||
ContentState.prototype.deleteSelectedTableCells = function (isCut = false) {
|
||||
const { tableId, cells } = this.selectedTableCells
|
||||
const tableBlock = this.getBlock(tableId)
|
||||
const { row, column } = tableBlock
|
||||
const rows = new Set()
|
||||
let lastColumn = null
|
||||
let isSameColumn = true
|
||||
let hasContent = false
|
||||
for (const cell of cells) {
|
||||
const cellBlock = this.getBlock(cell.key)
|
||||
const rowBlock = this.getParent(cellBlock)
|
||||
const { column: cellColumn } = cellBlock
|
||||
rows.add(rowBlock)
|
||||
if (cellBlock.children[0].text) {
|
||||
hasContent = true
|
||||
}
|
||||
if (typeof lastColumn === 'object') {
|
||||
lastColumn = cellColumn
|
||||
} else if (cellColumn !== lastColumn) {
|
||||
isSameColumn = false
|
||||
}
|
||||
cellBlock.children[0].text = ''
|
||||
}
|
||||
|
||||
const isOneColumnSelected = rows.size === +row + 1 && isSameColumn
|
||||
const isOneRowSelected = cells.length === +column + 1 && rows.size === 1
|
||||
const isWholeTableSelected = rows.size === +row + 1 && cells.length === (+row + 1) * (+column + 1)
|
||||
|
||||
if (isCut && isWholeTableSelected) {
|
||||
return this.deleteParagraph(tableId)
|
||||
}
|
||||
|
||||
if (hasContent) {
|
||||
this.singleRender(tableBlock, false)
|
||||
|
||||
return this.muya.dispatchChange()
|
||||
} else {
|
||||
const cellKey = cells[0].key
|
||||
const cellBlock = this.getBlock(cellKey)
|
||||
const cellContentKey = cellBlock.children[0].key
|
||||
if (isOneColumnSelected) {
|
||||
// Remove one empty column
|
||||
return this.editTable({
|
||||
location: 'current',
|
||||
action: 'remove',
|
||||
target: 'column'
|
||||
}, cellContentKey)
|
||||
} else if (isOneRowSelected) {
|
||||
// Remove one empty row
|
||||
return this.editTable({
|
||||
location: 'current',
|
||||
action: 'remove',
|
||||
target: 'row'
|
||||
}, cellContentKey)
|
||||
} else if (isWholeTableSelected) {
|
||||
// Select whole empty table
|
||||
return this.deleteParagraph(tableId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentState.prototype.selectTable = function (table) {
|
||||
// For calculateSelectedCells
|
||||
this.cellSelectInfo = {
|
||||
anchor: {
|
||||
row: 0,
|
||||
column: 0
|
||||
},
|
||||
focus: {
|
||||
row: table.row,
|
||||
column: table.column
|
||||
},
|
||||
cells: getAllTableCells(table.key)
|
||||
}
|
||||
this.calculateSelectedCells()
|
||||
this.selectedTableCells = {
|
||||
tableId: table.key,
|
||||
row: table.row + 1,
|
||||
column: table.column + 1,
|
||||
cells: this.cellSelectInfo.selectedCells.map(c => {
|
||||
delete c.ele
|
||||
return c
|
||||
})
|
||||
}
|
||||
// reset cellSelectInfo
|
||||
this.cellSelectInfo = null
|
||||
this.muya.blur()
|
||||
return this.singleRender(table, false)
|
||||
}
|
||||
|
||||
// Return the cell block if yes, else return null.
|
||||
ContentState.prototype.isSingleCellSelected = function () {
|
||||
const { selectedTableCells } = this
|
||||
if (selectedTableCells && selectedTableCells.cells.length === 1) {
|
||||
const key = selectedTableCells.cells[0].key
|
||||
return this.getBlock(key)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Return the cell block if yes, else return null.
|
||||
ContentState.prototype.isWholeTableSelected = function () {
|
||||
const { selectedTableCells } = this
|
||||
const table = selectedTableCells ? this.getBlock(selectedTableCells.tableId) : {}
|
||||
const { row, column } = table
|
||||
if (selectedTableCells && table && selectedTableCells.cells.length === (+row + 1) * (+column + 1)) {
|
||||
return table
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default tableSelectCellsCtrl
|
@ -67,8 +67,12 @@ const updateCtrl = ContentState => {
|
||||
*/
|
||||
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||
// table cell can not have blocks in it
|
||||
if (/th|td|figure/.test(block.type)) return false
|
||||
if (/codeContent|languageInput/.test(block.functionType)) return false
|
||||
if (/figure/.test(block.type)) {
|
||||
return false
|
||||
}
|
||||
if (/cellContent|codeContent|languageInput/.test(block.functionType)) {
|
||||
return false
|
||||
}
|
||||
|
||||
let line = null
|
||||
const { text } = block
|
||||
|
@ -66,6 +66,7 @@ class ClickEvent {
|
||||
// handler table click
|
||||
const toolItem = getToolItem(target)
|
||||
contentState.selectedImage = null
|
||||
contentState.selectedTableCells = null
|
||||
if (toolItem) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@ -75,7 +76,26 @@ class ClickEvent {
|
||||
contentState.tableToolBarClick(type)
|
||||
}
|
||||
}
|
||||
// Handler image and inline math preview click
|
||||
// Handle table drag bar click
|
||||
if (target.classList.contains('ag-drag-handler')) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const rect = target.getBoundingClientRect()
|
||||
const reference = {
|
||||
getBoundingClientRect () {
|
||||
return rect
|
||||
},
|
||||
width: rect.offsetWidth,
|
||||
height: rect.offsetHeight
|
||||
}
|
||||
eventCenter.dispatch('muya-table-bar', {
|
||||
reference,
|
||||
tableInfo: {
|
||||
barType: target.classList.contains('left') ? 'left' : 'bottom'
|
||||
}
|
||||
})
|
||||
}
|
||||
// Handle image and inline math preview click
|
||||
const markedImageText = target.previousElementSibling
|
||||
const mathRender = target.closest(`.${CLASS_OR_ID.AG_MATH_RENDER}`)
|
||||
const rubyRender = target.closest(`.${CLASS_OR_ID.AG_RUBY_RENDER}`)
|
||||
|
@ -3,6 +3,7 @@ class Clipboard {
|
||||
this.muya = muya
|
||||
this._copyType = 'normal' // `normal` or `copyAsMarkdown` or `copyAsHtml`
|
||||
this._pasteType = 'normal' // `normal` or `pasteAsPlainText`
|
||||
this._copyInfo = null
|
||||
this.listen()
|
||||
}
|
||||
|
||||
@ -11,8 +12,16 @@ class Clipboard {
|
||||
const docPasteHandler = event => {
|
||||
contentState.docPasteHandler(event)
|
||||
}
|
||||
const docCopyCutHandler = event => {
|
||||
contentState.docCopyHandler(event)
|
||||
if (event.type === 'cut') {
|
||||
// when user use `cut` function, the dom has been deleted by default.
|
||||
// But should update content state manually.
|
||||
contentState.docCutHandler(event)
|
||||
}
|
||||
}
|
||||
const copyCutHandler = event => {
|
||||
contentState.copyHandler(event, this._copyType)
|
||||
contentState.copyHandler(event, this._copyType, this._copyInfo)
|
||||
if (event.type === 'cut') {
|
||||
// when user use `cut` function, the dom has been deleted by default.
|
||||
// But should update content state manually.
|
||||
@ -30,6 +39,8 @@ class Clipboard {
|
||||
eventCenter.attachDOMEvent(container, 'paste', pasteHandler)
|
||||
eventCenter.attachDOMEvent(container, 'cut', copyCutHandler)
|
||||
eventCenter.attachDOMEvent(container, 'copy', copyCutHandler)
|
||||
eventCenter.attachDOMEvent(document.body, 'cut', docCopyCutHandler)
|
||||
eventCenter.attachDOMEvent(document.body, 'copy', docCopyCutHandler)
|
||||
}
|
||||
|
||||
copyAsMarkdown () {
|
||||
@ -47,15 +58,14 @@ class Clipboard {
|
||||
document.execCommand('paste')
|
||||
}
|
||||
|
||||
copy (name) {
|
||||
switch (name) {
|
||||
case 'table':
|
||||
this._copyType = 'copyTable'
|
||||
/**
|
||||
* Copy the anchor block(table, paragraph, math block etc) with the info
|
||||
* @param {string|object} info is the block key if it's string, or block if it's object
|
||||
*/
|
||||
copy (info) {
|
||||
this._copyType = 'copyBlock'
|
||||
this._copyInfo = info
|
||||
document.execCommand('copy')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,9 @@ class Keyboard {
|
||||
case EVENT_KEYS.Backspace: {
|
||||
return contentState.docBackspaceHandler(event)
|
||||
}
|
||||
case EVENT_KEYS.Delete: {
|
||||
return contentState.docDeleteHandler(event)
|
||||
}
|
||||
case EVENT_KEYS.ArrowUp: // fallthrough
|
||||
case EVENT_KEYS.ArrowDown: // fallthrough
|
||||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||||
@ -127,6 +130,7 @@ class Keyboard {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
container.classList.add('ag-meta-or-ctrl')
|
||||
}
|
||||
|
||||
if (
|
||||
this.shownFloat.size > 0 &&
|
||||
(
|
||||
@ -172,11 +176,6 @@ class Keyboard {
|
||||
this.muya.dispatchChange()
|
||||
}
|
||||
break
|
||||
case 'a':
|
||||
if (event.ctrlKey) {
|
||||
contentState.tableCellHandler(event)
|
||||
}
|
||||
break
|
||||
case EVENT_KEYS.ArrowUp: // fallthrough
|
||||
case EVENT_KEYS.ArrowDown: // fallthrough
|
||||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||||
|
@ -4,6 +4,7 @@ class MouseEvent {
|
||||
constructor (muya) {
|
||||
this.muya = muya
|
||||
this.mouseBinding()
|
||||
this.mouseDown()
|
||||
}
|
||||
|
||||
mouseBinding () {
|
||||
@ -39,6 +40,19 @@ class MouseEvent {
|
||||
eventCenter.attachDOMEvent(container, 'mouseover', handler)
|
||||
eventCenter.attachDOMEvent(container, 'mouseout', leaveHandler)
|
||||
}
|
||||
|
||||
mouseDown () {
|
||||
const { container, eventCenter, contentState } = this.muya
|
||||
const handler = event => {
|
||||
const target = event.target
|
||||
if (target.classList && target.classList.contains('ag-drag-handler')) {
|
||||
contentState.handleMouseDown(event)
|
||||
} else if (target && target.closest('tr')) {
|
||||
contentState.handleCellMouseDown(event)
|
||||
}
|
||||
}
|
||||
eventCenter.attachDOMEvent(container, 'mousedown', handler)
|
||||
}
|
||||
}
|
||||
|
||||
export default MouseEvent
|
||||
|
@ -262,6 +262,9 @@ class Muya {
|
||||
}
|
||||
|
||||
blur () {
|
||||
const selection = document.getSelection()
|
||||
|
||||
selection.removeAllRanges()
|
||||
this.container.blur()
|
||||
}
|
||||
|
||||
@ -321,14 +324,15 @@ class Muya {
|
||||
}
|
||||
|
||||
selectAll () {
|
||||
if (this.hasFocus()) {
|
||||
this.contentState.selectAll()
|
||||
}
|
||||
|
||||
if (!this.hasFocus()) {
|
||||
const activeElement = document.activeElement
|
||||
if (activeElement.nodeName === 'INPUT') {
|
||||
activeElement.select()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyAsMarkdown () {
|
||||
this.clipboard.copyAsMarkdown()
|
||||
@ -342,8 +346,12 @@ class Muya {
|
||||
this.clipboard.pasteAsPlainText()
|
||||
}
|
||||
|
||||
copy (name) {
|
||||
this.clipboard.copy(name)
|
||||
/**
|
||||
* Copy the anchor block contains the block with `info`. like copy as markdown.
|
||||
* @param {string|object} key the block key or block
|
||||
*/
|
||||
copy (info) {
|
||||
return this.clipboard.copy(info)
|
||||
}
|
||||
|
||||
setOptions (options, needRender = false) {
|
||||
|
@ -149,7 +149,7 @@ class StateRender {
|
||||
render (blocks, activeBlocks, matches) {
|
||||
const selector = `div#${CLASS_OR_ID.AG_EDITOR_ID}`
|
||||
const children = blocks.map(block => {
|
||||
return this.renderBlock(block, activeBlocks, matches, true)
|
||||
return this.renderBlock(null, block, activeBlocks, matches, true)
|
||||
})
|
||||
|
||||
const newVdom = h(selector, children)
|
||||
@ -167,7 +167,7 @@ class StateRender {
|
||||
const cursorOutMostBlock = activeBlocks[activeBlocks.length - 1]
|
||||
// If cursor is not in render blocks, need to render cursor block independently
|
||||
const needRenderCursorBlock = blocks.indexOf(cursorOutMostBlock) === -1
|
||||
const newVnode = h('section', blocks.map(block => this.renderBlock(block, activeBlocks, matches)))
|
||||
const newVnode = h('section', blocks.map(block => this.renderBlock(null, block, activeBlocks, matches)))
|
||||
const html = toHTML(newVnode).replace(/^<section>([\s\S]+?)<\/section>$/, '$1')
|
||||
|
||||
const needToRemoved = []
|
||||
@ -196,7 +196,7 @@ class StateRender {
|
||||
const cursorDom = document.querySelector(`#${key}`)
|
||||
if (cursorDom) {
|
||||
const oldCursorVnode = toVNode(cursorDom)
|
||||
const newCursorVnode = this.renderBlock(cursorOutMostBlock, activeBlocks, matches)
|
||||
const newCursorVnode = this.renderBlock(null, cursorOutMostBlock, activeBlocks, matches)
|
||||
patch(oldCursorVnode, newCursorVnode)
|
||||
}
|
||||
}
|
||||
@ -215,7 +215,7 @@ class StateRender {
|
||||
*/
|
||||
singleRender (block, activeBlocks, matches) {
|
||||
const selector = `#${block.key}`
|
||||
const newVdom = this.renderBlock(block, activeBlocks, matches, true)
|
||||
const newVdom = this.renderBlock(null, block, activeBlocks, matches, true)
|
||||
const rootDom = document.querySelector(selector)
|
||||
const oldVdom = toVNode(rootDom)
|
||||
patch(oldVdom, newVdom)
|
||||
|
@ -1,10 +1,10 @@
|
||||
/**
|
||||
* [renderBlock render one block, no matter it is a container block or text block]
|
||||
*/
|
||||
export default function renderBlock (block, activeBlocks, matches, useCache = false) {
|
||||
export default function renderBlock (parent, block, activeBlocks, matches, useCache = false) {
|
||||
const method = Array.isArray(block.children) && block.children.length > 0
|
||||
? 'renderContainerBlock'
|
||||
: 'renderLeafBlock'
|
||||
|
||||
return this[method](block, activeBlocks, matches, useCache)
|
||||
return this[method](parent, block, activeBlocks, matches, useCache)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { CLASS_OR_ID } from '../../../config'
|
||||
import { renderTableTools } from './renderToolBar'
|
||||
import { renderEditIcon } from './renderContainerEditIcon'
|
||||
import { renderLeftBar, renderBottomBar } from './renderTableDargBar'
|
||||
import { h } from '../snabbdom'
|
||||
|
||||
const PRE_BLOCK_HASH = {
|
||||
@ -15,9 +16,11 @@ const PRE_BLOCK_HASH = {
|
||||
'vega-lite': `.${CLASS_OR_ID.AG_VEGA_LITE}`
|
||||
}
|
||||
|
||||
export default function renderContainerBlock (block, activeBlocks, matches, useCache = false) {
|
||||
export default function renderContainerBlock (parent, block, activeBlocks, matches, useCache = false) {
|
||||
let selector = this.getSelector(block, activeBlocks)
|
||||
const {
|
||||
key,
|
||||
align,
|
||||
type,
|
||||
headingStyle,
|
||||
editable,
|
||||
@ -26,9 +29,10 @@ export default function renderContainerBlock (block, activeBlocks, matches, useC
|
||||
listItemType,
|
||||
bulletMarkerOrDelimiter,
|
||||
isLooseListItem,
|
||||
lang
|
||||
lang,
|
||||
column
|
||||
} = block
|
||||
const children = block.children.map(child => this.renderBlock(child, activeBlocks, matches, useCache))
|
||||
const children = block.children.map(child => this.renderBlock(block, child, activeBlocks, matches, useCache))
|
||||
const data = {
|
||||
attrs: {},
|
||||
dataset: {}
|
||||
@ -42,7 +46,66 @@ export default function renderContainerBlock (block, activeBlocks, matches, useC
|
||||
selector += `.language-${lang.replace(/[#.]{1}/g, '')}`
|
||||
}
|
||||
|
||||
if (/^h/.test(type)) {
|
||||
if (/th|td/.test(type)) {
|
||||
const { cells } = this.muya.contentState.selectedTableCells || {}
|
||||
if (cells && cells.length) {
|
||||
const cell = cells.find(c => c.key === key)
|
||||
if (cell) {
|
||||
const { top, right, bottom, left } = cell
|
||||
selector += '.ag-cell-selected'
|
||||
if (top) {
|
||||
selector += '.ag-cell-border-top'
|
||||
}
|
||||
if (right) {
|
||||
selector += '.ag-cell-border-right'
|
||||
}
|
||||
if (bottom) {
|
||||
selector += '.ag-cell-border-bottom'
|
||||
}
|
||||
if (left) {
|
||||
selector += '.ag-cell-border-left'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Judge whether to render the table drag bar.
|
||||
const { cells } = this.muya.contentState.selectedTableCells || {}
|
||||
if (/th|td/.test(type) && (!cells || cells && cells.length === 0)) {
|
||||
const table = this.muya.contentState.closest(block, 'table')
|
||||
const findTable = activeBlocks.find(b => b.key === table.key)
|
||||
if (findTable) {
|
||||
const { row: tableRow, column: tableColumn } = findTable
|
||||
const isLastRow = () => {
|
||||
const rowContainer = this.muya.contentState.closest(block, /tbody|thead/)
|
||||
if (rowContainer.type === 'thead') {
|
||||
return tableRow === 0
|
||||
} else {
|
||||
return !parent.nextSibling
|
||||
}
|
||||
}
|
||||
if (block.parent === activeBlocks[1].parent && !block.preSibling && tableRow > 0) {
|
||||
children.unshift(renderLeftBar())
|
||||
}
|
||||
|
||||
if (column === activeBlocks[1].column && isLastRow() && tableColumn > 0) {
|
||||
children.push(renderBottomBar())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (/th|td/.test(type)) {
|
||||
if (align) {
|
||||
Object.assign(data.attrs, {
|
||||
style: `text-align:${align}`
|
||||
})
|
||||
}
|
||||
if (typeof column === 'number') {
|
||||
Object.assign(data.dataset, {
|
||||
column
|
||||
})
|
||||
}
|
||||
} else if (/^h/.test(type)) {
|
||||
if (/^h\d$/.test(type)) {
|
||||
// TODO: This should be the best place to create and update the TOC.
|
||||
// Cache `block.key` and title and update only if necessary.
|
||||
|
@ -68,7 +68,7 @@ const hasReferenceToken = tokens => {
|
||||
return result
|
||||
}
|
||||
|
||||
export default function renderLeafBlock (block, activeBlocks, matches, useCache = false) {
|
||||
export default function renderLeafBlock (parent, block, activeBlocks, matches, useCache = false) {
|
||||
const { loadMathMap } = this
|
||||
const { cursor } = this.muya.contentState
|
||||
let selector = this.getSelector(block, activeBlocks)
|
||||
@ -77,7 +77,6 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
||||
const {
|
||||
text,
|
||||
type,
|
||||
align,
|
||||
checked,
|
||||
key,
|
||||
lang,
|
||||
@ -120,11 +119,7 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
||||
})
|
||||
}
|
||||
|
||||
if (/th|td/.test(type) && align) {
|
||||
Object.assign(data.attrs, {
|
||||
style: `text-align:${align}`
|
||||
})
|
||||
} else if (type === 'div') {
|
||||
if (type === 'div') {
|
||||
const code = this.codeCache.get(block.preSibling)
|
||||
switch (functionType) {
|
||||
case 'html': {
|
||||
|
13
src/muya/lib/parser/render/renderBlock/renderTableDargBar.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { h } from '../snabbdom'
|
||||
|
||||
export const renderLeftBar = () => {
|
||||
return h('span.ag-drag-handler.left', {
|
||||
attrs: { contenteditable: 'false' }
|
||||
})
|
||||
}
|
||||
|
||||
export const renderBottomBar = () => {
|
||||
return h('span.ag-drag-handler.bottom', {
|
||||
attrs: { contenteditable: 'false' }
|
||||
})
|
||||
}
|
@ -5,7 +5,7 @@ import TableIcon from '../../../assets/pngicon/table/table@2x.png'
|
||||
import AlignLeftIcon from '../../../assets/pngicon/algin_left/2.png'
|
||||
import AlignRightIcon from '../../../assets/pngicon/algin_right/2.png'
|
||||
import AlignCenterIcon from '../../../assets/pngicon/algin_center/2.png'
|
||||
import DeleteIcon from '../../../assets/pngicon/delete/2.png'
|
||||
import DeleteIcon from '../../../assets/pngicon/table_delete/2.png'
|
||||
|
||||
export const TABLE_TOOLS = [{
|
||||
label: 'table',
|
||||
@ -32,7 +32,7 @@ export const TABLE_TOOLS = [{
|
||||
const renderToolBar = (type, tools, activeBlocks) => {
|
||||
const children = tools.map(tool => {
|
||||
const { label, title, icon } = tool
|
||||
const { align } = activeBlocks[0]
|
||||
const { align } = activeBlocks[1] // activeBlocks[0] is span block. cell content.
|
||||
let selector = 'li'
|
||||
if (align && label === align) {
|
||||
selector += '.active'
|
||||
|
@ -497,9 +497,8 @@ class Selection {
|
||||
if (node.nodeType === 3) {
|
||||
node = node.parentNode
|
||||
}
|
||||
return node.closest('span.ag-paragraph') ||
|
||||
node.closest('th.ag-paragraph') ||
|
||||
node.closest('td.ag-paragraph')
|
||||
|
||||
return node.closest('span.ag-paragraph')
|
||||
}
|
||||
|
||||
getCursorRange () {
|
||||
|
@ -149,7 +149,7 @@ class TablePicker extends BaseFloat {
|
||||
selectItem () {
|
||||
const { cb } = this
|
||||
const { row, column } = this.select
|
||||
cb(Math.max(row, 1), Math.max(column, 1))
|
||||
cb(Math.max(row, 0), Math.max(column, 0))
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
34
src/muya/lib/ui/tableTools/config.js
Normal file
@ -0,0 +1,34 @@
|
||||
export const toolList = {
|
||||
left: [{
|
||||
label: 'Insert Row Above',
|
||||
action: 'insert',
|
||||
location: 'previous',
|
||||
target: 'row'
|
||||
}, {
|
||||
label: 'Insert Row Below',
|
||||
action: 'insert',
|
||||
location: 'next',
|
||||
target: 'row'
|
||||
}, {
|
||||
label: 'Remove Row',
|
||||
action: 'remove',
|
||||
location: 'current',
|
||||
target: 'row'
|
||||
}],
|
||||
bottom: [{
|
||||
label: 'Insert Column Left',
|
||||
action: 'insert',
|
||||
location: 'left',
|
||||
target: 'column'
|
||||
}, {
|
||||
label: 'Insert Column Right',
|
||||
action: 'insert',
|
||||
location: 'right',
|
||||
target: 'column'
|
||||
}, {
|
||||
label: 'Remove Column',
|
||||
action: 'remove',
|
||||
location: 'current',
|
||||
target: 'column'
|
||||
}]
|
||||
}
|
41
src/muya/lib/ui/tableTools/index.css
Normal file
@ -0,0 +1,41 @@
|
||||
.ag-table-bar-tools {
|
||||
width: 150px;
|
||||
}
|
||||
.ag-table-bar-tools ul,
|
||||
.ag-table-bar-tools li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ag-table-bar-tools ul {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.ag-table-bar-tools li.item {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
color: var(--editorColor);
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.ag-table-bar-tools li.item[data-label=remove] {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ag-table-bar-tools li.item[data-label=remove]::before {
|
||||
content: '';
|
||||
width: calc(100% - 16px);
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
background: var(--editorColor04);
|
||||
top: -5px;
|
||||
display: block;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.ag-table-bar-tools li.item:hover {
|
||||
background-color: var(--floatHoverColor);
|
||||
}
|
86
src/muya/lib/ui/tableTools/index.js
Normal file
@ -0,0 +1,86 @@
|
||||
import BaseFloat from '../baseFloat'
|
||||
import { patch, h } from '../../parser/render/snabbdom'
|
||||
import { toolList } from './config'
|
||||
|
||||
import './index.css'
|
||||
|
||||
const defaultOptions = {
|
||||
placement: 'right-start',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: '0, 5'
|
||||
}
|
||||
},
|
||||
showArrow: false
|
||||
}
|
||||
|
||||
class TableBarTools extends BaseFloat {
|
||||
static pluginName = 'tableBarTools'
|
||||
|
||||
constructor (muya, options = {}) {
|
||||
const name = 'ag-table-bar-tools'
|
||||
const opts = Object.assign({}, defaultOptions, options)
|
||||
super(muya, name, opts)
|
||||
this.options = opts
|
||||
this.oldVnode = null
|
||||
this.tableInfo = null
|
||||
this.floatBox.classList.add('ag-table-bar-tools')
|
||||
const tableBarContainer = this.tableBarContainer = document.createElement('div')
|
||||
this.container.appendChild(tableBarContainer)
|
||||
this.listen()
|
||||
}
|
||||
|
||||
listen () {
|
||||
super.listen()
|
||||
const { eventCenter } = this.muya
|
||||
eventCenter.subscribe('muya-table-bar', ({ reference, tableInfo }) => {
|
||||
if (reference) {
|
||||
this.tableInfo = tableInfo
|
||||
this.show(reference)
|
||||
this.render()
|
||||
} else {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { tableInfo, oldVnode, tableBarContainer } = this
|
||||
const renderArray = toolList[tableInfo.barType]
|
||||
const children = renderArray.map((item) => {
|
||||
const { label } = item
|
||||
|
||||
const selector = 'li.item'
|
||||
return h(selector, {
|
||||
dataset: {
|
||||
label: item.action
|
||||
},
|
||||
on: {
|
||||
click: event => {
|
||||
this.selectItem(event, item)
|
||||
}
|
||||
}
|
||||
}, label)
|
||||
})
|
||||
|
||||
const vnode = h('ul', children)
|
||||
|
||||
if (oldVnode) {
|
||||
patch(oldVnode, vnode)
|
||||
} else {
|
||||
patch(tableBarContainer, vnode)
|
||||
}
|
||||
this.oldVnode = vnode
|
||||
}
|
||||
|
||||
selectItem (event, item) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const { contentState } = this.muya
|
||||
contentState.editTable(item)
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
export default TableBarTools
|
@ -5,7 +5,7 @@ const position = (source, ele) => {
|
||||
const { top, right, height } = rect
|
||||
|
||||
Object.assign(ele.style, {
|
||||
top: `${top + height + 20}px`,
|
||||
top: `${top + height + 15}px`,
|
||||
left: `${right - ele.offsetWidth / 2 - 10}px`
|
||||
})
|
||||
}
|
||||
|
@ -285,10 +285,10 @@ class ExportMarkdown {
|
||||
return str.replace(/([^\\])\|/g, '$1\\|')
|
||||
}
|
||||
|
||||
tableData.push(tHeader.children[0].children.map(th => escapeText(th.text).trim()))
|
||||
tableData.push(tHeader.children[0].children.map(th => escapeText(th.children[0].text).trim()))
|
||||
if (tBody) {
|
||||
tBody.children.forEach(bodyRow => {
|
||||
tableData.push(bodyRow.children.map(td => escapeText(td.text).trim()))
|
||||
tableData.push(bodyRow.children.map(td => escapeText(td.children[0].text).trim()))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -216,33 +216,53 @@ const importRegister = ContentState => {
|
||||
// We have to re-escape the chraracter to not break the table.
|
||||
return text.replace(/\|/g, '\\|')
|
||||
}
|
||||
for (const headText of header) {
|
||||
const i = header.indexOf(headText)
|
||||
let i
|
||||
let j
|
||||
const headerLen = header.length
|
||||
for (i = 0; i < headerLen; i++) {
|
||||
const headText = header[i]
|
||||
const th = this.createBlock('th', {
|
||||
text: restoreTableEscapeCharacters(headText)
|
||||
align: align[i] || '',
|
||||
column: i
|
||||
})
|
||||
Object.assign(th, { align: align[i] || '', column: i })
|
||||
const cellContent = this.createBlock('span', {
|
||||
text: restoreTableEscapeCharacters(headText),
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
this.appendChild(th, cellContent)
|
||||
this.appendChild(theadRow, th)
|
||||
}
|
||||
for (const row of cells) {
|
||||
const rowLen = cells.length
|
||||
for (i = 0; i < rowLen; i++) {
|
||||
const rowBlock = this.createBlock('tr')
|
||||
for (const cell of row) {
|
||||
const i = row.indexOf(cell)
|
||||
const rowContents = cells[i]
|
||||
const colLen = rowContents.length
|
||||
for (j = 0; j < colLen; j++) {
|
||||
const cell = rowContents[j]
|
||||
const td = this.createBlock('td', {
|
||||
text: restoreTableEscapeCharacters(cell)
|
||||
align: align[j] || '',
|
||||
column: j
|
||||
})
|
||||
Object.assign(td, { align: align[i] || '', column: i })
|
||||
const cellContent = this.createBlock('span', {
|
||||
text: restoreTableEscapeCharacters(cell),
|
||||
functionType: 'cellContent'
|
||||
})
|
||||
|
||||
this.appendChild(td, cellContent)
|
||||
this.appendChild(rowBlock, td)
|
||||
}
|
||||
this.appendChild(tbody, rowBlock)
|
||||
}
|
||||
|
||||
Object.assign(table, { row: cells.length, column: header.length - 1 }) // set row and column
|
||||
block = this.createBlock('figure')
|
||||
block.functionType = 'table'
|
||||
this.appendChild(thead, theadRow)
|
||||
this.appendChild(block, table)
|
||||
this.appendChild(table, thead)
|
||||
if (tbody.children.length) {
|
||||
this.appendChild(table, tbody)
|
||||
}
|
||||
this.appendChild(parentList[0], block)
|
||||
break
|
||||
}
|
||||
@ -358,8 +378,8 @@ const importRegister = ContentState => {
|
||||
html = html.replace(/<span> <\/span>/g, String.fromCharCode(160))
|
||||
|
||||
html = turnSoftBreakToSpan(html)
|
||||
|
||||
const markdown = turndownService.turndown(html)
|
||||
|
||||
return markdown
|
||||
}
|
||||
|
||||
|
@ -385,24 +385,39 @@ kbd {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table thead tr,
|
||||
/* table thead tr,
|
||||
table tr:nth-child(2n) {
|
||||
background-color: var(--editorColor04);
|
||||
}
|
||||
} */
|
||||
|
||||
table tr th {
|
||||
font-weight: bold;
|
||||
border: 1px solid var(--tableBorderColor);
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
table tr td {
|
||||
border: 1px solid var(--tableBorderColor);
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
table tr th::before,
|
||||
table tr td::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: calc(100% + 1px);
|
||||
height: calc(100% + 1px);
|
||||
border: 1px solid var(--tableBorderColor);
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
table tr th:first-child,
|
||||
@ -415,6 +430,119 @@ kbd {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table tr .ag-drag-handler.left {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 19px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--iconColor);
|
||||
left: -11px;
|
||||
cursor: pointer;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
table tr .ag-drag-handler.left::before,
|
||||
table tr .ag-drag-handler.left::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background: var(--iconColor);
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 4px;
|
||||
}
|
||||
table tr .ag-drag-handler.left::after {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
|
||||
table tr .ag-drag-handler.bottom {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: block;
|
||||
width: 19px;
|
||||
height: 9px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--iconColor);
|
||||
bottom: -15px;
|
||||
cursor: pointer;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
table tr .ag-drag-handler.bottom::before,
|
||||
table tr .ag-drag-handler.bottom::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background: var(--iconColor);
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
}
|
||||
table tr .ag-drag-handler.bottom::after {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
table tr .ag-drag-handler.left,
|
||||
table tr .ag-drag-handler.bottom
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
table.ag-active tr .ag-drag-handler {
|
||||
display: block;
|
||||
}
|
||||
|
||||
table .ag-drag-cell {
|
||||
opacity: .7;
|
||||
z-index: 1;
|
||||
background: var(--floatBgColor);
|
||||
transition: left .3s ease-in-out, bottom .3s ease-in-out;
|
||||
}
|
||||
|
||||
table .ag-drag-cell.ag-drag-left {
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
table .ag-drag-cell.ag-drag-bottom {
|
||||
bottom: -6px;
|
||||
}
|
||||
|
||||
table .ag-cell-transform {
|
||||
transition: transform .3s ease-in-out;
|
||||
}
|
||||
|
||||
table .ag-cell-selected::before {
|
||||
background: var(--editorColor04);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
table .ag-cell-border-top::before {
|
||||
border-top-color: var(--themeColor);
|
||||
}
|
||||
|
||||
table .ag-cell-border-right::before {
|
||||
border-right-color: var(--themeColor);
|
||||
}
|
||||
|
||||
table .ag-cell-border-bottom::before {
|
||||
border-bottom-color: var(--themeColor);
|
||||
}
|
||||
|
||||
table .ag-cell-border-left::before {
|
||||
border-left-color: var(--themeColor);
|
||||
}
|
||||
|
||||
span code,
|
||||
td code,
|
||||
th code {
|
||||
|
@ -86,6 +86,7 @@ import ImageToolbar from 'muya/lib/ui/imageToolbar'
|
||||
import Transformer from 'muya/lib/ui/transformer'
|
||||
import FormatPicker from 'muya/lib/ui/formatPicker'
|
||||
import LinkTools from 'muya/lib/ui/linkTools'
|
||||
import TableBarTools from 'muya/lib/ui/tableTools'
|
||||
import FrontMenu from 'muya/lib/ui/frontMenu'
|
||||
import bus from '../../bus'
|
||||
import Search from '../search'
|
||||
@ -362,6 +363,7 @@ export default {
|
||||
Muya.use(LinkTools, {
|
||||
jumpClick: this.jumpClick
|
||||
})
|
||||
Muya.use(TableBarTools)
|
||||
|
||||
const options = {
|
||||
focusMode,
|
||||
@ -424,9 +426,7 @@ export default {
|
||||
bus.$on('createParagraph', this.handleParagraph)
|
||||
bus.$on('deleteParagraph', this.handleParagraph)
|
||||
bus.$on('insertParagraph', this.handleInsertParagraph)
|
||||
bus.$on('editTable', this.handleEditTable)
|
||||
bus.$on('scroll-to-header', this.scrollToHeader)
|
||||
bus.$on('copy-block', this.handleCopyBlock)
|
||||
bus.$on('print', this.handlePrint)
|
||||
bus.$on('screenshot-captured', this.handleScreenShot)
|
||||
|
||||
@ -753,19 +753,10 @@ export default {
|
||||
editor && editor.insertParagraph(location)
|
||||
},
|
||||
|
||||
handleEditTable (data) {
|
||||
const { editor } = this
|
||||
editor && editor.editTable(data)
|
||||
},
|
||||
|
||||
blurEditor () {
|
||||
this.editor.blur()
|
||||
},
|
||||
|
||||
handleCopyBlock (name) {
|
||||
this.editor.copy(name)
|
||||
},
|
||||
|
||||
handleScreenShot () {
|
||||
if (this.editor) {
|
||||
document.execCommand('paste')
|
||||
@ -795,9 +786,7 @@ export default {
|
||||
bus.$off('createParagraph', this.handleParagraph)
|
||||
bus.$off('deleteParagraph', this.handleParagraph)
|
||||
bus.$off('insertParagraph', this.handleInsertParagraph)
|
||||
bus.$off('editTable', this.handleEditTable)
|
||||
bus.$off('scroll-to-header', this.scrollToHeader)
|
||||
bus.$off('copy-block', this.handleCopyBlock)
|
||||
bus.$off('print', this.handlePrint)
|
||||
bus.$off('screenshot-captured', this.handleScreenShot)
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
import bus from '../../bus'
|
||||
|
||||
export const copyTable = () => {
|
||||
bus.$emit('copy-block', 'table')
|
||||
}
|
||||
|
||||
export const copyAsMarkdown = (menuItem, browserWindow) => {
|
||||
bus.$emit('copyAsMarkdown', 'copyAsMarkdown')
|
||||
}
|
||||
@ -19,7 +15,3 @@ export const pasteAsPlainText = (menuItem, browserWindow) => {
|
||||
export const insertParagraph = location => {
|
||||
bus.$emit('insertParagraph', location)
|
||||
}
|
||||
|
||||
export const editTable = data => {
|
||||
bus.$emit('editTable', data)
|
||||
}
|
||||
|
@ -3,17 +3,12 @@ import {
|
||||
CUT,
|
||||
COPY,
|
||||
PASTE,
|
||||
COPY_TABLE,
|
||||
COPY_AS_MARKDOWN,
|
||||
COPY_AS_HTML,
|
||||
PASTE_AS_PLAIN_TEXT,
|
||||
SEPARATOR,
|
||||
INSERT_BEFORE,
|
||||
INSERT_AFTER,
|
||||
INSERT_ROW,
|
||||
REMOVE_ROW,
|
||||
INSERT_COLUMN,
|
||||
REMOVE_COLUMN
|
||||
INSERT_AFTER
|
||||
} from './menuItems'
|
||||
|
||||
const { Menu, MenuItem } = remote
|
||||
@ -24,19 +19,7 @@ export const showContextMenu = (event, { start, end }) => {
|
||||
const disableCutAndCopy = start.key === end.key && start.offset === end.offset
|
||||
const CONTEXT_ITEMS = [INSERT_BEFORE, INSERT_AFTER, SEPARATOR, CUT, COPY, PASTE, SEPARATOR, COPY_AS_MARKDOWN, COPY_AS_HTML, PASTE_AS_PLAIN_TEXT]
|
||||
|
||||
if (/th|td/.test(start.block.type) && start.key === end.key) {
|
||||
CONTEXT_ITEMS.unshift(
|
||||
INSERT_ROW,
|
||||
REMOVE_ROW,
|
||||
INSERT_COLUMN,
|
||||
REMOVE_COLUMN,
|
||||
SEPARATOR,
|
||||
COPY_TABLE,
|
||||
SEPARATOR
|
||||
)
|
||||
}
|
||||
|
||||
[CUT, COPY, COPY_AS_HTML, COPY_AS_MARKDOWN].forEach(item => {
|
||||
;[CUT, COPY, COPY_AS_HTML, COPY_AS_MARKDOWN].forEach(item => {
|
||||
item.enabled = !disableCutAndCopy
|
||||
})
|
||||
|
||||
|
@ -18,14 +18,6 @@ export const PASTE = {
|
||||
role: 'paste'
|
||||
}
|
||||
|
||||
export const COPY_TABLE = {
|
||||
label: 'Copy Table',
|
||||
id: 'copyTableMenuItem',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.copyTable()
|
||||
}
|
||||
}
|
||||
|
||||
export const COPY_AS_MARKDOWN = {
|
||||
label: 'Copy As Markdown',
|
||||
id: 'copyAsMarkdownMenuItem',
|
||||
@ -66,116 +58,6 @@ export const INSERT_AFTER = {
|
||||
}
|
||||
}
|
||||
|
||||
export const INSERT_ROW = {
|
||||
label: 'Insert Row',
|
||||
submenu: [{
|
||||
label: 'Previous Row',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'previous',
|
||||
action: 'insert',
|
||||
target: 'row'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Next Row',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'next',
|
||||
action: 'insert',
|
||||
target: 'row'
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
export const REMOVE_ROW = {
|
||||
label: 'Remove Row',
|
||||
submenu: [{
|
||||
label: 'Previous Row',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'previous',
|
||||
action: 'remove',
|
||||
target: 'row'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Current Row',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'current',
|
||||
action: 'remove',
|
||||
target: 'row'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Next Row',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'next',
|
||||
action: 'remove',
|
||||
target: 'row'
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
export const INSERT_COLUMN = {
|
||||
label: 'Insert Column',
|
||||
submenu: [{
|
||||
label: 'Left Column',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'left',
|
||||
action: 'insert',
|
||||
target: 'column'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Right Column',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'right',
|
||||
action: 'insert',
|
||||
target: 'column'
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
export const REMOVE_COLUMN = {
|
||||
label: 'Remove Column',
|
||||
submenu: [{
|
||||
label: 'Left Column',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'left',
|
||||
action: 'remove',
|
||||
target: 'column'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Current Column',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'current',
|
||||
action: 'remove',
|
||||
target: 'column'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: 'Right Column',
|
||||
click (menuItem, browserWindow) {
|
||||
contextMenu.editTable({
|
||||
location: 'right',
|
||||
action: 'remove',
|
||||
target: 'column'
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
export const SEPARATOR = {
|
||||
type: 'separator'
|
||||
}
|
||||
|