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)
|
- 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)
|
- 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)
|
- 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)
|
setParagraphMenuItemStatus(applicationMenu, true)
|
||||||
|
|
||||||
if (
|
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') ||
|
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
||||||
(end.type === 'span' && end.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-atx-line:empty::after,
|
||||||
.ag-thematic-break-line:empty::after,
|
.ag-thematic-break-line:empty::after,
|
||||||
.ag-code-content:empty::after,
|
.ag-code-content:empty::after,
|
||||||
|
.ag-cell-content:empty::after,
|
||||||
.ag-paragraph-content:empty::after {
|
.ag-paragraph-content:empty::after {
|
||||||
content: '\200B';
|
content: '\200B';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-cell-content {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 4em;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.ag-atx-line,
|
.ag-atx-line,
|
||||||
.ag-thematic-break-line,
|
.ag-thematic-break-line,
|
||||||
.ag-paragraph-content,
|
.ag-paragraph-content,
|
||||||
@ -249,14 +257,15 @@ span.ag-math > .ag-math-render.ag-math-error {
|
|||||||
figure {
|
figure {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin: 1rem 0;
|
margin: 1.4em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-tool-bar {
|
.ag-tool-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -20px;
|
top: -22px;
|
||||||
left: 0;
|
left: 0;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -273,7 +282,7 @@ figure {
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-right: 3px;
|
margin-right: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: var(--iconColor);
|
color: var(--iconColor);
|
||||||
@ -281,14 +290,12 @@ figure {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-tool-bar ul li[data-label=delete] {
|
.ag-tool-bar ul li[data-label=table] {
|
||||||
position: absolute;
|
margin-right: 16px;
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-tool-bar ul li[data-label=delete] {
|
.ag-tool-bar ul li[data-label=delete] {
|
||||||
color: var(--deleteColor);
|
margin-left: 16px;
|
||||||
right: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-container-icon {
|
.ag-container-icon {
|
||||||
@ -313,7 +320,7 @@ figure {
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--iconColor);
|
color: var(--iconColor);
|
||||||
opacity: .5;
|
opacity: .7;
|
||||||
transition: all .25s ease-in-out;
|
transition: all .25s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,8 +346,7 @@ figure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ag-tool-bar ul li[data-label=delete] i.icon {
|
.ag-tool-bar ul li[data-label=delete] i.icon {
|
||||||
color: var(--deleteColor);
|
color: var(--iconColor);
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
figure.ag-active .ag-tool-bar {
|
figure.ag-active .ag-tool-bar {
|
||||||
@ -371,7 +377,6 @@ figure[data-role=HTML]:not(.ag-active):hover .ag-container-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@ -495,7 +500,6 @@ pre.ag-multiple-math span.ag-code-content:first-of-type:empty::after {
|
|||||||
color: var(--editorColor10);
|
color: var(--editorColor10);
|
||||||
}
|
}
|
||||||
|
|
||||||
figure,
|
|
||||||
pre.ag-html-block,
|
pre.ag-html-block,
|
||||||
pre.ag-fence-code,
|
pre.ag-fence-code,
|
||||||
pre.ag-indent-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
|
// 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 DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
|
||||||
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
|
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 VOID_HTML_TAGS = voidHtmlTags
|
||||||
export const HTML_TAGS = htmlTags
|
export const HTML_TAGS = htmlTags
|
||||||
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
||||||
|
@ -15,38 +15,42 @@ const adjustOffset = (offset, block, event) => {
|
|||||||
|
|
||||||
const arrowCtrl = ContentState => {
|
const arrowCtrl = ContentState => {
|
||||||
ContentState.prototype.findNextRowCell = function (cell) {
|
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`)
|
throw new Error(`block with type ${cell && cell.type} is not a table cell`)
|
||||||
}
|
}
|
||||||
const row = this.getParent(cell)
|
const thOrTd = this.getParent(cell)
|
||||||
const rowContainer = this.getParent(row) // thead or tbody
|
const row = this.closest(cell, 'tr')
|
||||||
const column = row.children.indexOf(cell)
|
const rowContainer = this.closest(row, /thead|tbody/) // thead or tbody
|
||||||
|
const column = row.children.indexOf(thOrTd)
|
||||||
if (rowContainer.type === 'thead') {
|
if (rowContainer.type === 'thead') {
|
||||||
const tbody = this.getNextSibling(rowContainer)
|
const tbody = this.getNextSibling(rowContainer)
|
||||||
if (tbody && tbody.children.length) {
|
if (tbody && tbody.children.length) {
|
||||||
return tbody.children[0].children[column]
|
return tbody.children[0].children[column].children[0]
|
||||||
}
|
}
|
||||||
} else if (rowContainer.type === 'tbody') {
|
} else if (rowContainer.type === 'tbody') {
|
||||||
const nextRow = this.getNextSibling(row)
|
const nextRow = this.getNextSibling(row)
|
||||||
if (nextRow) {
|
if (nextRow) {
|
||||||
return nextRow.children[column]
|
return nextRow.children[column].children[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.findPrevRowCell = function (cell) {
|
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`)
|
if (cell.functionType !== 'cellContent') {
|
||||||
const row = this.getParent(cell)
|
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 rowContainer = this.getParent(row) // thead or tbody
|
||||||
const rowIndex = rowContainer.children.indexOf(row)
|
const rowIndex = rowContainer.children.indexOf(row)
|
||||||
const column = row.children.indexOf(cell)
|
const column = row.children.indexOf(thOrTd)
|
||||||
if (rowContainer.type === 'tbody') {
|
if (rowContainer.type === 'tbody') {
|
||||||
if (rowIndex === 0 && rowContainer.preSibling) {
|
if (rowIndex === 0 && rowContainer.preSibling) {
|
||||||
const thead = this.getPreSibling(rowContainer)
|
const thead = this.getPreSibling(rowContainer)
|
||||||
return thead.children[0].children[column]
|
return thead.children[0].children[column].children[0]
|
||||||
} else if (rowIndex > 0) {
|
} else if (rowIndex > 0) {
|
||||||
return this.getPreSibling(row).children[column]
|
return this.getPreSibling(row).children[column].children[0]
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -118,12 +122,12 @@ const arrowCtrl = ContentState => {
|
|||||||
(event.key === EVENT_KEYS.ArrowUp && topOffset > 0) ||
|
(event.key === EVENT_KEYS.ArrowUp && topOffset > 0) ||
|
||||||
(event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)
|
(event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)
|
||||||
) {
|
) {
|
||||||
if (!/pre|th|td/.test(block.type)) {
|
if (!/pre/.test(block.type) || block.functionType !== 'cellContent') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/th|td/.test(block.type)) {
|
if (block.functionType === 'cellContent') {
|
||||||
let activeBlock
|
let activeBlock
|
||||||
const cellInNextRow = this.findNextRowCell(block)
|
const cellInNextRow = this.findNextRowCell(block)
|
||||||
const cellInPrevRow = this.findPrevRowCell(block)
|
const cellInPrevRow = this.findPrevRowCell(block)
|
||||||
|
@ -107,6 +107,10 @@ const backspaceCtrl = ContentState => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return this.deleteImage(this.selectedImage)
|
return this.deleteImage(this.selectedImage)
|
||||||
}
|
}
|
||||||
|
if (this.selectedTableCells) {
|
||||||
|
event.preventDefault()
|
||||||
|
return this.deleteSelectedTableCells()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.backspaceHandler = function (event) {
|
ContentState.prototype.backspaceHandler = function (event) {
|
||||||
@ -122,6 +126,7 @@ const backspaceCtrl = ContentState => {
|
|||||||
return this.deleteImage(this.selectedImage)
|
return this.deleteImage(this.selectedImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle select all content.
|
||||||
if (this.isSelectAll()) {
|
if (this.isSelectAll()) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.blocks = [this.createBlockP()]
|
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.
|
// fix bug when the first block is table, these two ways will cause bugs.
|
||||||
// 1. one paragraph bollow table, selectAll, press backspace.
|
// 1. one paragraph bollow table, selectAll, press backspace.
|
||||||
// 2. select table from the first cell to the last cell, press backsapce.
|
// 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 (
|
if (
|
||||||
end.offset === endBlock.text.length &&
|
end.offset === endBlock.text.length &&
|
||||||
startOutmostBlock === endOutmostBlock &&
|
startOutmostBlock === endOutmostBlock &&
|
||||||
@ -211,7 +217,7 @@ const backspaceCtrl = ContentState => {
|
|||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
// need remove the figure block.
|
// 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.
|
// if table is the only block, need create a p block.
|
||||||
const p = this.createBlockP(endBlock.text.substring(end.offset))
|
const p = this.createBlockP(endBlock.text.substring(end.offset))
|
||||||
this.insertBefore(p, figureBlock)
|
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
|
// If select multiple paragraph or multiple characters in one paragraph, just let
|
||||||
// inputCtrl to handle this case.
|
// inputCtrl to handle this case.
|
||||||
if (start.key !== end.key || start.offset !== end.offset) {
|
if (start.key !== end.key || start.offset !== end.offset) {
|
||||||
@ -282,7 +303,7 @@ const backspaceCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix issue #1218
|
// 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.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
@ -298,11 +319,26 @@ const backspaceCtrl = ContentState => {
|
|||||||
return this.singleRender(startBlock)
|
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 tableHasContent = table => {
|
||||||
const tHead = table.children[0]
|
const tHead = table.children[0]
|
||||||
const tBody = table.children[1]
|
const tBody = table.children[1]
|
||||||
const tHeadHasContent = tHead.children[0].children.some(th => th.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.text.trim()))
|
const tBodyHasContent = tBody.children.some(row => row.children.some(td => td.children[0].text.trim()))
|
||||||
return tHeadHasContent || tBodyHasContent
|
return tHeadHasContent || tBodyHasContent
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,24 +384,23 @@ const backspaceCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
this.partialRender()
|
this.partialRender()
|
||||||
}
|
}
|
||||||
} else if (left === 0 && /th|td/.test(block.type)) {
|
} else if (left === 0 && block.functionType === 'cellContent') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
const tHead = this.getBlock(parent.parent)
|
const table = this.closest(block, 'table')
|
||||||
const table = this.getBlock(tHead.parent)
|
const figure = this.closest(table, 'figure')
|
||||||
const figure = this.getBlock(table.parent)
|
|
||||||
const hasContent = tableHasContent(table)
|
const hasContent = tableHasContent(table)
|
||||||
let key
|
let key
|
||||||
let offset
|
let offset
|
||||||
|
|
||||||
if ((!preBlock || !/th|td/.test(preBlock.type)) && !hasContent) {
|
if ((!preBlock || preBlock.functionType !== 'cellContent') && !hasContent) {
|
||||||
const newLine = this.createBlock('span')
|
const paragraphContent = this.createBlock('span')
|
||||||
delete figure.functionType
|
delete figure.functionType
|
||||||
figure.children = []
|
figure.children = []
|
||||||
this.appendChild(figure, newLine)
|
this.appendChild(figure, paragraphContent)
|
||||||
figure.text = ''
|
figure.text = ''
|
||||||
figure.type = 'p'
|
figure.type = 'p'
|
||||||
key = newLine.key
|
key = paragraphContent.key
|
||||||
offset = 0
|
offset = 0
|
||||||
} else if (preBlock) {
|
} else if (preBlock) {
|
||||||
key = preBlock.key
|
key = preBlock.key
|
||||||
|
@ -6,7 +6,18 @@ import ExportMarkdown from '../utils/exportMarkdown'
|
|||||||
import marked from '../parser/marked'
|
import marked from '../parser/marked'
|
||||||
|
|
||||||
const copyCutCtrl = ContentState => {
|
const copyCutCtrl = ContentState => {
|
||||||
|
ContentState.prototype.docCutHandler = function (event) {
|
||||||
|
const { selectedTableCells } = this
|
||||||
|
if (selectedTableCells) {
|
||||||
|
event.preventDefault()
|
||||||
|
return this.deleteSelectedTableCells(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ContentState.prototype.cutHandler = function () {
|
ContentState.prototype.cutHandler = function () {
|
||||||
|
if (this.selectedTableCells) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const { selectedImage } = this
|
const { selectedImage } = this
|
||||||
if (selectedImage) {
|
if (selectedImage) {
|
||||||
const { key, token } = selectedImage
|
const { key, token } = selectedImage
|
||||||
@ -179,10 +190,49 @@ const copyCutCtrl = ContentState => {
|
|||||||
let htmlData = wrapper.innerHTML
|
let htmlData = wrapper.innerHTML
|
||||||
const textData = this.htmlToMarkdown(htmlData)
|
const textData = this.htmlToMarkdown(htmlData)
|
||||||
htmlData = marked(textData)
|
htmlData = marked(textData)
|
||||||
|
|
||||||
return { html: htmlData, text: 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()
|
event.preventDefault()
|
||||||
const { selectedImage } = this
|
const { selectedImage } = this
|
||||||
if (selectedImage) {
|
if (selectedImage) {
|
||||||
@ -210,11 +260,13 @@ const copyCutCtrl = ContentState => {
|
|||||||
event.clipboardData.setData('text/plain', getSanitizeHtml(text))
|
event.clipboardData.setData('text/plain', getSanitizeHtml(text))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'copyTable': {
|
|
||||||
const table = this.getTableBlock()
|
case 'copyBlock': {
|
||||||
if (!table) return
|
const block = typeof copyInfo === 'string' ? this.getBlock(copyInfo) : copyInfo
|
||||||
|
if (!block) return
|
||||||
|
const anchor = this.getAnchor(block)
|
||||||
const listIndentation = this.listIndentation
|
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/html', '')
|
||||||
event.clipboardData.setData('text/plain', markdown)
|
event.clipboardData.setData('text/plain', markdown)
|
||||||
break
|
break
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import selection from '../selection'
|
import selection from '../selection'
|
||||||
|
|
||||||
const deleteCtrl = ContentState => {
|
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) {
|
ContentState.prototype.deleteHandler = function (event) {
|
||||||
const { start, end } = selection.getCursorRange()
|
const { start, end } = selection.getCursorRange()
|
||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
|
@ -49,18 +49,23 @@ const enterCtrl = ContentState => {
|
|||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.createRow = function (row) {
|
ContentState.prototype.createRow = function (row, isHeader = false) {
|
||||||
const trBlock = this.createBlock('tr')
|
const tr = this.createBlock('tr')
|
||||||
const len = row.children.length
|
const len = row.children.length
|
||||||
let i
|
let i
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
const tdBlock = this.createBlock('td')
|
const cell = this.createBlock(isHeader ? 'th' : 'td', {
|
||||||
const preChild = row.children[i]
|
align: row.children[i].align,
|
||||||
tdBlock.column = i
|
column: i
|
||||||
tdBlock.align = preChild.align
|
})
|
||||||
this.appendChild(trBlock, tdBlock)
|
const cellContent = this.createBlock('span', {
|
||||||
|
functionType: 'cellContent'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.appendChild(cell, cellContent)
|
||||||
|
this.appendChild(tr, cell)
|
||||||
}
|
}
|
||||||
return trBlock
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.createBlockLi = function (paragraphInListItem) {
|
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.
|
// Insert `<br/>` in table cell if you want to open a new line.
|
||||||
// Why not use `soft line break` or `hard line break` ?
|
// Why not use `soft line break` or `hard line break` ?
|
||||||
// Becasuse table cell only have one line.
|
// 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 { text, key } = block
|
||||||
const brTag = '<br/>'
|
const brTag = '<br/>'
|
||||||
block.text = text.substring(0, start.offset) + brTag + text.substring(start.offset)
|
block.text = text.substring(0, start.offset) + brTag + text.substring(start.offset)
|
||||||
@ -297,19 +302,27 @@ const enterCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle enter in table
|
// handle enter in table
|
||||||
if (/th|td/.test(block.type)) {
|
if (block.functionType === 'cellContent') {
|
||||||
const row = this.getBlock(block.parent)
|
const row = this.closest(block, 'tr')
|
||||||
const rowContainer = this.getBlock(row.parent)
|
const rowContainer = this.getBlock(row.parent)
|
||||||
const table = this.getBlock(rowContainer.parent)
|
const table = this.closest(rowContainer, 'table')
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(isOsx && event.metaKey) ||
|
(isOsx && event.metaKey) ||
|
||||||
(!isOsx && event.ctrlKey)
|
(!isOsx && event.ctrlKey)
|
||||||
) {
|
) {
|
||||||
const nextRow = this.createRow(row)
|
const nextRow = this.createRow(row, false)
|
||||||
if (rowContainer.type === 'thead') {
|
if (rowContainer.type === 'thead') {
|
||||||
const tBody = this.getBlock(rowContainer.nextSibling)
|
let tBody = this.getBlock(rowContainer.nextSibling)
|
||||||
this.insertBefore(nextRow, tBody.children[0])
|
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 {
|
} else {
|
||||||
this.insertAfter(nextRow, row)
|
this.insertAfter(nextRow, row)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ import backspaceCtrl from './backspaceCtrl'
|
|||||||
import deleteCtrl from './deleteCtrl'
|
import deleteCtrl from './deleteCtrl'
|
||||||
import codeBlockCtrl from './codeBlockCtrl'
|
import codeBlockCtrl from './codeBlockCtrl'
|
||||||
import tableBlockCtrl from './tableBlockCtrl'
|
import tableBlockCtrl from './tableBlockCtrl'
|
||||||
import selectionCtrl from './selectionCtrl'
|
import tableDragBarCtrl from './tableDragBarCtrl'
|
||||||
|
import tableSelectCellsCtrl from './tableSelectCellsCtrl'
|
||||||
import History from './history'
|
import History from './history'
|
||||||
import arrowCtrl from './arrowCtrl'
|
import arrowCtrl from './arrowCtrl'
|
||||||
import pasteCtrl from './pasteCtrl'
|
import pasteCtrl from './pasteCtrl'
|
||||||
@ -33,7 +34,6 @@ import escapeCharactersMap, { escapeCharacters } from '../parser/escapeCharacter
|
|||||||
const prototypes = [
|
const prototypes = [
|
||||||
tabCtrl,
|
tabCtrl,
|
||||||
enterCtrl,
|
enterCtrl,
|
||||||
selectionCtrl,
|
|
||||||
updateCtrl,
|
updateCtrl,
|
||||||
backspaceCtrl,
|
backspaceCtrl,
|
||||||
deleteCtrl,
|
deleteCtrl,
|
||||||
@ -42,6 +42,8 @@ const prototypes = [
|
|||||||
pasteCtrl,
|
pasteCtrl,
|
||||||
copyCutCtrl,
|
copyCutCtrl,
|
||||||
tableBlockCtrl,
|
tableBlockCtrl,
|
||||||
|
tableDragBarCtrl,
|
||||||
|
tableSelectCellsCtrl,
|
||||||
paragraphCtrl,
|
paragraphCtrl,
|
||||||
formatCtrl,
|
formatCtrl,
|
||||||
searchCtrl,
|
searchCtrl,
|
||||||
@ -80,9 +82,37 @@ class ContentState {
|
|||||||
this.turndownConfig = Object.assign(DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
|
this.turndownConfig = Object.assign(DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
|
||||||
this.fontSize = 16
|
this.fontSize = 16
|
||||||
this.lineHeight = 1.6
|
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()
|
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) {
|
set selectedImage (image) {
|
||||||
const oldSelectedImage = this._selectedImage
|
const oldSelectedImage = this._selectedImage
|
||||||
// if there is no selected image, remove selected status of current selected image.
|
// 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.
|
* remove blocks between before and after, and includes after block.
|
||||||
*/
|
*/
|
||||||
removeBlocks (before, after, isRemoveAfter = true, isRecursion = false) {
|
removeBlocks (before, after, isRemoveAfter = true, isRecursion = false) {
|
||||||
if (!isRecursion) {
|
if (!isRecursion) {
|
||||||
if (/td|th/.test(before.type)) {
|
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)) {
|
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)
|
let nextSibling = this.getBlock(before.nextSibling)
|
||||||
@ -712,6 +732,31 @@ class ContentState {
|
|||||||
return this.lastInDescendant(blocks[len - 1])
|
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 () {
|
clear () {
|
||||||
this.history.clearHistory()
|
this.history.clearHistory()
|
||||||
}
|
}
|
||||||
|
@ -679,15 +679,24 @@ const paragraphCtrl = ContentState => {
|
|||||||
this.partialRender()
|
this.partialRender()
|
||||||
return this.muya.eventCenter.dispatch('stateChange')
|
return this.muya.eventCenter.dispatch('stateChange')
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete current paragraph
|
// delete current paragraph
|
||||||
ContentState.prototype.deleteParagraph = function () {
|
ContentState.prototype.deleteParagraph = function (blockKey) {
|
||||||
const { start, end } = this.cursor
|
let startOutmostBlock
|
||||||
const startOutmostBlock = this.findOutMostBlock(this.getBlock(start.key))
|
if (blockKey) {
|
||||||
const endOutmostBlock = this.findOutMostBlock(this.getBlock(end.key))
|
const block = this.getBlock(blockKey)
|
||||||
if (startOutmostBlock !== endOutmostBlock) {
|
const firstEditableBlock = this.firstInDescendant(block)
|
||||||
// if the cursor is not in one paragraph, just return
|
startOutmostBlock = this.getAnchor(firstEditableBlock)
|
||||||
return
|
} else {
|
||||||
|
const { start, end } = this.cursor
|
||||||
|
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 preBlock = this.getBlock(startOutmostBlock.preSibling)
|
||||||
const nextBlock = this.getBlock(startOutmostBlock.nextSibling)
|
const nextBlock = this.getBlock(startOutmostBlock.nextSibling)
|
||||||
let cursorBlock = null
|
let cursorBlock = null
|
||||||
@ -723,25 +732,73 @@ const paragraphCtrl = ContentState => {
|
|||||||
!this.muya.keyboard.isComposed
|
!this.muya.keyboard.isComposed
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.selectAll = function () {
|
ContentState.prototype.selectAllContent = function () {
|
||||||
const { start } = this.cursor
|
const firstTextBlock = this.getFirstBlock()
|
||||||
const startBlock = this.getBlock(start.key)
|
const lastTextBlock = this.getLastBlock()
|
||||||
// const endBlock = this.getBlock(end.key)
|
this.cursor = {
|
||||||
// handle selectAll in table. only select the startBlock cell...
|
start: {
|
||||||
if (/th|td/.test(startBlock.type)) {
|
key: firstTextBlock.key,
|
||||||
const { key } = start
|
offset: 0
|
||||||
const textLength = startBlock.text.length
|
},
|
||||||
this.cursor = {
|
end: {
|
||||||
start: {
|
key: lastTextBlock.key,
|
||||||
key,
|
offset: lastTextBlock.text.length
|
||||||
offset: 0
|
}
|
||||||
},
|
}
|
||||||
end: {
|
|
||||||
key,
|
return this.render()
|
||||||
offset: textLength
|
}
|
||||||
}
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
return this.partialRender()
|
|
||||||
}
|
}
|
||||||
// Handler selectAll in code block. only select all the code block conent.
|
// 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...
|
// `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()
|
return this.partialRender()
|
||||||
}
|
}
|
||||||
const firstTextBlock = this.getFirstBlock()
|
|
||||||
const lastTextBlock = this.getLastBlock()
|
return this.selectAllContent()
|
||||||
this.cursor = {
|
|
||||||
start: {
|
|
||||||
key: firstTextBlock.key,
|
|
||||||
offset: 0
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
key: lastTextBlock.key,
|
|
||||||
offset: lastTextBlock.text.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.render()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ const pasteCtrl = ContentState => {
|
|||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/th|td/.test(startBlock.type)) {
|
if (startBlock.functionType === 'cellContent') {
|
||||||
const pendingText = text.trim().replace(/\n/g, '<br/>')
|
const pendingText = text.trim().replace(/\n/g, '<br/>')
|
||||||
startBlock.text = startBlock.text.substring(0, start.offset) + pendingText + startBlock.text.substring(end.offset)
|
startBlock.text = startBlock.text.substring(0, start.offset) + pendingText + startBlock.text.substring(end.offset)
|
||||||
const { key } = startBlock
|
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 => {
|
const tabCtrl = ContentState => {
|
||||||
ContentState.prototype.findNextCell = function (block) {
|
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')
|
throw new Error('only th and td can have next cell')
|
||||||
}
|
}
|
||||||
const nextSibling = this.getBlock(block.nextSibling)
|
const cellBlock = this.getParent(block)
|
||||||
const parent = this.getBlock(block.parent)
|
const nextSibling = this.getBlock(cellBlock.nextSibling)
|
||||||
const tbOrTh = this.getBlock(parent.parent)
|
const rowBlock = this.getBlock(cellBlock.parent)
|
||||||
|
const tbOrTh = this.getBlock(rowBlock.parent)
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
return nextSibling
|
return this.firstInDescendant(nextSibling)
|
||||||
} else {
|
} else {
|
||||||
if (parent.nextSibling) {
|
if (rowBlock.nextSibling) {
|
||||||
const nextRow = this.getBlock(parent.nextSibling)
|
const nextRow = this.getBlock(rowBlock.nextSibling)
|
||||||
return nextRow.children[0]
|
return this.firstInDescendant(nextRow)
|
||||||
} else if (tbOrTh.type === 'thead') {
|
} else if (tbOrTh.type === 'thead') {
|
||||||
const tBody = this.getBlock(tbOrTh.nextSibling)
|
const tBody = this.getBlock(tbOrTh.nextSibling)
|
||||||
if (tBody && tBody.children.length) {
|
if (tBody && tBody.children.length) {
|
||||||
return tBody.children[0].children[0]
|
return this.firstInDescendant(tBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,19 +371,22 @@ const tabCtrl = ContentState => {
|
|||||||
|
|
||||||
// Handle `tab` key in table cell.
|
// Handle `tab` key in table cell.
|
||||||
let nextCell
|
let nextCell
|
||||||
if (start.key === end.key && /th|td/.test(startBlock.type)) {
|
if (start.key === end.key && startBlock.functionType === 'cellContent') {
|
||||||
nextCell = this.findNextCell(startBlock)
|
nextCell = this.findNextCell(startBlock)
|
||||||
} else if (/th|td/.test(endBlock.type)) {
|
} else if (endBlock.functionType === 'cellContent') {
|
||||||
nextCell = endBlock
|
nextCell = endBlock
|
||||||
}
|
}
|
||||||
if (nextCell) {
|
if (nextCell) {
|
||||||
const key = nextCell.key
|
const { key } = nextCell
|
||||||
|
|
||||||
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset: 0 },
|
start: { key, offset },
|
||||||
end: { key, offset: 0 }
|
end: { key, offset }
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.partialRender()
|
const figure = this.closest(nextCell, 'figure')
|
||||||
|
return this.singleRender(figure)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isIndentableListItem()) {
|
if (this.isIndentableListItem()) {
|
||||||
|
@ -3,25 +3,32 @@ import { isLengthEven, getParagraphReference } from '../utils'
|
|||||||
const TABLE_BLOCK_REG = /^\|.*?(\\*)\|.*?(\\*)\|/
|
const TABLE_BLOCK_REG = /^\|.*?(\\*)\|.*?(\\*)\|/
|
||||||
|
|
||||||
const tableBlockCtrl = ContentState => {
|
const tableBlockCtrl = ContentState => {
|
||||||
ContentState.prototype.createTableInFigure = function ({ rows, columns }, headerTexts) {
|
ContentState.prototype.createTableInFigure = function ({ rows, columns }, tableContents = []) {
|
||||||
const table = this.createBlock('table')
|
const table = this.createBlock('table', {
|
||||||
|
row: rows - 1, // zero base
|
||||||
|
column: columns - 1
|
||||||
|
})
|
||||||
const tHead = this.createBlock('thead')
|
const tHead = this.createBlock('thead')
|
||||||
const tBody = this.createBlock('tbody')
|
const tBody = this.createBlock('tbody')
|
||||||
|
|
||||||
table.row = rows - 1 // zero base
|
|
||||||
table.column = columns - 1 // zero base
|
|
||||||
let i
|
let i
|
||||||
let j
|
let j
|
||||||
for (i = 0; i < rows; i++) {
|
for (i = 0; i < rows; i++) {
|
||||||
const rowBlock = this.createBlock('tr')
|
const rowBlock = this.createBlock('tr')
|
||||||
i === 0 ? this.appendChild(tHead, rowBlock) : this.appendChild(tBody, rowBlock)
|
i === 0 ? this.appendChild(tHead, rowBlock) : this.appendChild(tBody, rowBlock)
|
||||||
|
const rowContents = tableContents[i]
|
||||||
for (j = 0; j < columns; j++) {
|
for (j = 0; j < columns; j++) {
|
||||||
const cell = this.createBlock(i === 0 ? 'th' : 'td', {
|
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)
|
this.appendChild(rowBlock, cell)
|
||||||
cell.align = ''
|
|
||||||
cell.column = j
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,52 +40,25 @@ const tableBlockCtrl = ContentState => {
|
|||||||
return table
|
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 }) {
|
ContentState.prototype.createFigure = function ({ rows, columns }) {
|
||||||
const { end } = this.cursor
|
const { end } = this.cursor
|
||||||
const table = this.createTableInFigure({ rows, columns })
|
const table = this.createTableInFigure({ rows, columns })
|
||||||
const figureBlock = this.createBlock('figure')
|
const figureBlock = this.createBlock('figure', {
|
||||||
figureBlock.functionType = 'table'
|
functionType: 'table'
|
||||||
|
})
|
||||||
const endBlock = this.getBlock(end.key)
|
const endBlock = this.getBlock(end.key)
|
||||||
const anchor = this.getAnchor(endBlock)
|
const anchor = this.getAnchor(endBlock)
|
||||||
|
|
||||||
if (!anchor) return
|
if (!anchor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.insertAfter(figureBlock, anchor)
|
this.insertAfter(figureBlock, anchor)
|
||||||
if (/p|h\d/.test(anchor.type) && !endBlock.text) {
|
if (/p|h\d/.test(anchor.type) && !endBlock.text) {
|
||||||
this.removeBlock(anchor)
|
this.removeBlock(anchor)
|
||||||
}
|
}
|
||||||
this.appendChild(figureBlock, table)
|
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
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
@ -112,10 +92,11 @@ const tableBlockCtrl = ContentState => {
|
|||||||
rowHeader.push('')
|
rowHeader.push('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = rowHeader.length
|
const columns = rowHeader.length
|
||||||
const rows = 2
|
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.type = 'figure'
|
||||||
block.text = ''
|
block.text = ''
|
||||||
@ -123,21 +104,20 @@ const tableBlockCtrl = ContentState => {
|
|||||||
block.functionType = 'table'
|
block.functionType = 'table'
|
||||||
this.appendChild(block, 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) {
|
ContentState.prototype.tableToolBarClick = function (type) {
|
||||||
const { start: { key } } = this.cursor
|
const { start: { key } } = this.cursor
|
||||||
const block = this.getBlock(key)
|
const block = this.getBlock(key)
|
||||||
if (!(/td|th/.test(block.type))) throw new Error('table is not active')
|
const parentBlock = this.getParent(block)
|
||||||
const { column, align } = block
|
if (block.functionType !== 'cellContent') {
|
||||||
const getTable = td => {
|
throw new Error('table is not active')
|
||||||
const row = this.getBlock(block.parent)
|
|
||||||
const rowContainer = this.getBlock(row.parent)
|
|
||||||
return this.getBlock(rowContainer.parent)
|
|
||||||
}
|
}
|
||||||
const table = getTable(block)
|
const { column, align } = parentBlock
|
||||||
|
const table = this.closest(block, 'table')
|
||||||
const figure = this.getBlock(table.parent)
|
const figure = this.getBlock(table.parent)
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'left':
|
case 'left':
|
||||||
case 'center':
|
case 'center':
|
||||||
@ -171,26 +151,37 @@ const tableBlockCtrl = ContentState => {
|
|||||||
case 'table': {
|
case 'table': {
|
||||||
const { eventCenter } = this.muya
|
const { eventCenter } = this.muya
|
||||||
const figureKey = figure.key
|
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 { row = 1, column = 1 } = table // zero base
|
||||||
|
|
||||||
const handler = (row, column) => {
|
const handler = (row, column) => {
|
||||||
const { row: oldRow, column: oldColumn } = table
|
const { row: oldRow, column: oldColumn } = table
|
||||||
const tBody = table.children[1]
|
let tBody = table.children[1]
|
||||||
const tHead = table.children[0]
|
const tHead = table.children[0]
|
||||||
const headerRow = tHead.children[0]
|
const headerRow = tHead.children[0]
|
||||||
const bodyRows = tBody.children
|
const bodyRows = tBody ? tBody.children : []
|
||||||
let i
|
let i
|
||||||
if (column > oldColumn) {
|
if (column > oldColumn) {
|
||||||
for (i = oldColumn + 1; i <= column; i++) {
|
for (i = oldColumn + 1; i <= column; i++) {
|
||||||
const th = this.createBlock('th')
|
const th = this.createBlock('th', {
|
||||||
th.column = i
|
column: i,
|
||||||
th.align = ''
|
align: ''
|
||||||
|
})
|
||||||
|
const thContent = this.createBlock('span', {
|
||||||
|
functionType: 'cellContent'
|
||||||
|
})
|
||||||
|
this.appendChild(th, thContent)
|
||||||
this.appendChild(headerRow, th)
|
this.appendChild(headerRow, th)
|
||||||
bodyRows.forEach(bodyRow => {
|
bodyRows.forEach(bodyRow => {
|
||||||
const td = this.createBlock('td')
|
const td = this.createBlock('td', {
|
||||||
td.column = i
|
column: i,
|
||||||
td.align = ''
|
align: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const tdContent = this.createBlock('span', {
|
||||||
|
functionType: 'cellContent'
|
||||||
|
})
|
||||||
|
this.appendChild(td, tdContent)
|
||||||
this.appendChild(bodyRow, td)
|
this.appendChild(bodyRow, td)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -209,16 +200,25 @@ const tableBlockCtrl = ContentState => {
|
|||||||
const lastRow = tBody.children[tBody.children.length - 1]
|
const lastRow = tBody.children[tBody.children.length - 1]
|
||||||
this.removeBlock(lastRow)
|
this.removeBlock(lastRow)
|
||||||
}
|
}
|
||||||
|
if (tBody.children.length === 0) {
|
||||||
|
this.removeBlock(tBody)
|
||||||
|
}
|
||||||
} else if (row > oldRow) {
|
} 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++) {
|
for (i = oldRow + 1; i <= row; i++) {
|
||||||
const bodyRow = this.createRow(oneRowInBody)
|
const bodyRow = this.createRow(oneHeaderRow, false)
|
||||||
|
|
||||||
this.appendChild(tBody, bodyRow)
|
this.appendChild(tBody, bodyRow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(table, { row, column })
|
Object.assign(table, { row, column })
|
||||||
|
|
||||||
const cursorBlock = headerRow.children[0]
|
const cursorBlock = this.firstInDescendant(headerRow)
|
||||||
const key = cursorBlock.key
|
const key = cursorBlock.key
|
||||||
const offset = cursorBlock.text.length
|
const offset = cursorBlock.text.length
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
@ -228,66 +228,69 @@ const tableBlockCtrl = ContentState => {
|
|||||||
this.muya.eventCenter.dispatch('stateChange')
|
this.muya.eventCenter.dispatch('stateChange')
|
||||||
this.partialRender()
|
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))
|
eventCenter.dispatch('muya-table-picker', { row, column }, reference, handler.bind(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert/remove row/column
|
// insert/remove row/column
|
||||||
ContentState.prototype.editTable = function ({ location, action, target }) {
|
ContentState.prototype.editTable = function ({ location, action, target }, cellContentKey) {
|
||||||
const { start, end } = this.cursor
|
let block
|
||||||
const block = this.getBlock(start.key)
|
let start
|
||||||
if (start.key !== end.key || !/th|td/.test(block.type)) {
|
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')
|
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 cellBlock = this.getParent(block)
|
||||||
const table = this.getParent(rowContainer)
|
const currentRow = this.getParent(cellBlock)
|
||||||
|
const table = this.closest(block, 'table')
|
||||||
const thead = table.children[0]
|
const thead = table.children[0]
|
||||||
const tbody = table.children[1]
|
const tbody = table.children[1]
|
||||||
const { column } = table
|
const columnIndex = currentRow.children.indexOf(cellBlock)
|
||||||
const columnIndex = currentRow.children.indexOf(block)
|
// const rowIndex = rowContainer.type === 'thead' ? 0 : tbody.children.indexOf(currentRow) + 1
|
||||||
let cursorBlock
|
|
||||||
|
|
||||||
const createRow = (column, isHeader) => {
|
let cursorBlock
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target === 'row') {
|
if (target === 'row') {
|
||||||
if (action === 'insert') {
|
if (action === 'insert') {
|
||||||
const newRow = (location === 'previous' && block.type === 'th')
|
const newRow = (location === 'previous' && cellBlock.type === 'th')
|
||||||
? createRow(column, true)
|
? this.createRow(currentRow, true)
|
||||||
: createRow(column, false)
|
: this.createRow(currentRow, false)
|
||||||
if (location === 'previous') {
|
if (location === 'previous') {
|
||||||
this.insertBefore(newRow, currentRow)
|
this.insertBefore(newRow, currentRow)
|
||||||
if (block.type === 'th') {
|
if (cellBlock.type === 'th') {
|
||||||
this.removeBlock(currentRow)
|
this.removeBlock(currentRow)
|
||||||
currentRow.children.forEach(cell => (cell.type = 'td'))
|
currentRow.children.forEach(cell => (cell.type = 'td'))
|
||||||
const firstRow = tbody.children[0]
|
const firstRow = tbody.children[0]
|
||||||
this.insertBefore(currentRow, firstRow)
|
this.insertBefore(currentRow, firstRow)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (block.type === 'th') {
|
if (cellBlock.type === 'th') {
|
||||||
const firstRow = tbody.children[0]
|
const firstRow = tbody.children[0]
|
||||||
this.insertBefore(newRow, firstRow)
|
this.insertBefore(newRow, firstRow)
|
||||||
} else {
|
} else {
|
||||||
this.insertAfter(newRow, currentRow)
|
this.insertAfter(newRow, currentRow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursorBlock = newRow.children[columnIndex]
|
cursorBlock = newRow.children[columnIndex].children[0]
|
||||||
// handle remove row
|
// handle remove row
|
||||||
} else {
|
} else {
|
||||||
if (location === 'previous') {
|
if (location === 'previous') {
|
||||||
if (block.type === 'th') return
|
if (cellBlock.type === 'th') return
|
||||||
if (!currentRow.preSibling) {
|
if (!currentRow.preSibling) {
|
||||||
const headRow = thead.children[0]
|
const headRow = thead.children[0]
|
||||||
if (!currentRow.nextSibling) return
|
if (!currentRow.nextSibling) return
|
||||||
@ -300,20 +303,20 @@ const tableBlockCtrl = ContentState => {
|
|||||||
this.removeBlock(preRow)
|
this.removeBlock(preRow)
|
||||||
}
|
}
|
||||||
} else if (location === 'current') {
|
} 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]
|
const firstRow = tbody.children[0]
|
||||||
this.removeBlock(currentRow)
|
this.removeBlock(currentRow)
|
||||||
this.removeBlock(firstRow)
|
this.removeBlock(firstRow)
|
||||||
this.appendChild(thead, firstRow)
|
this.appendChild(thead, firstRow)
|
||||||
firstRow.children.forEach(cell => (cell.type = 'th'))
|
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)) {
|
if (cellBlock.type === 'td' && (currentRow.preSibling || currentRow.nextSibling)) {
|
||||||
cursorBlock = (this.getNextSibling(currentRow) || this.getPreSibling(currentRow)).children[columnIndex]
|
cursorBlock = (this.getNextSibling(currentRow) || this.getPreSibling(currentRow)).children[columnIndex].children[0]
|
||||||
this.removeBlock(currentRow)
|
this.removeBlock(currentRow)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (block.type === 'th') {
|
if (cellBlock.type === 'th') {
|
||||||
if (tbody.children.length >= 2) {
|
if (tbody.children.length >= 2) {
|
||||||
const firstRow = tbody.children[0]
|
const firstRow = tbody.children[0]
|
||||||
this.removeBlock(firstRow)
|
this.removeBlock(firstRow)
|
||||||
@ -322,7 +325,9 @@ const tableBlockCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const nextRow = this.getNextSibling(currentRow)
|
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') {
|
if (action === 'insert') {
|
||||||
[...thead.children, ...tbody.children].forEach(tableRow => {
|
[...thead.children, ...tbody.children].forEach(tableRow => {
|
||||||
const targetCell = tableRow.children[columnIndex]
|
const targetCell = tableRow.children[columnIndex]
|
||||||
const cell = this.createBlock(targetCell.type)
|
const cell = this.createBlock(targetCell.type, {
|
||||||
cell.align = ''
|
align: ''
|
||||||
|
})
|
||||||
|
const cellContent = this.createBlock('span', {
|
||||||
|
functionType: 'cellContent'
|
||||||
|
})
|
||||||
|
this.appendChild(cell, cellContent)
|
||||||
if (location === 'left') {
|
if (location === 'left') {
|
||||||
this.insertBefore(cell, targetCell)
|
this.insertBefore(cell, targetCell)
|
||||||
} else {
|
} else {
|
||||||
@ -341,7 +351,7 @@ const tableBlockCtrl = ContentState => {
|
|||||||
cell.column = i
|
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
|
// handle remove column
|
||||||
} else {
|
} else {
|
||||||
if (currentRow.children.length <= 2) return
|
if (currentRow.children.length <= 2) return
|
||||||
@ -350,7 +360,7 @@ const tableBlockCtrl = ContentState => {
|
|||||||
const removeCell = location === 'left'
|
const removeCell = location === 'left'
|
||||||
? this.getPreSibling(targetCell)
|
? this.getPreSibling(targetCell)
|
||||||
: (location === 'current' ? targetCell : this.getNextSibling(targetCell))
|
: (location === 'current' ? targetCell : this.getNextSibling(targetCell))
|
||||||
if (removeCell === block) {
|
if (removeCell === cellBlock) {
|
||||||
cursorBlock = this.findNextBlockInLocation(block)
|
cursorBlock = this.findNextBlockInLocation(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,8 +398,8 @@ const tableBlockCtrl = ContentState => {
|
|||||||
.filter(p => endParents.includes(p))
|
.filter(p => endParents.includes(p))
|
||||||
|
|
||||||
if (affiliation.length) {
|
if (affiliation.length) {
|
||||||
const table = affiliation.find(p => p.type === 'figure')
|
const figure = affiliation.find(p => p.type === 'figure')
|
||||||
return table
|
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) {
|
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||||
// table cell can not have blocks in it
|
// table cell can not have blocks in it
|
||||||
if (/th|td|figure/.test(block.type)) return false
|
if (/figure/.test(block.type)) {
|
||||||
if (/codeContent|languageInput/.test(block.functionType)) return false
|
return false
|
||||||
|
}
|
||||||
|
if (/cellContent|codeContent|languageInput/.test(block.functionType)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
let line = null
|
let line = null
|
||||||
const { text } = block
|
const { text } = block
|
||||||
|
@ -66,6 +66,7 @@ class ClickEvent {
|
|||||||
// handler table click
|
// handler table click
|
||||||
const toolItem = getToolItem(target)
|
const toolItem = getToolItem(target)
|
||||||
contentState.selectedImage = null
|
contentState.selectedImage = null
|
||||||
|
contentState.selectedTableCells = null
|
||||||
if (toolItem) {
|
if (toolItem) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@ -75,7 +76,26 @@ class ClickEvent {
|
|||||||
contentState.tableToolBarClick(type)
|
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 markedImageText = target.previousElementSibling
|
||||||
const mathRender = target.closest(`.${CLASS_OR_ID.AG_MATH_RENDER}`)
|
const mathRender = target.closest(`.${CLASS_OR_ID.AG_MATH_RENDER}`)
|
||||||
const rubyRender = target.closest(`.${CLASS_OR_ID.AG_RUBY_RENDER}`)
|
const rubyRender = target.closest(`.${CLASS_OR_ID.AG_RUBY_RENDER}`)
|
||||||
|
@ -3,6 +3,7 @@ class Clipboard {
|
|||||||
this.muya = muya
|
this.muya = muya
|
||||||
this._copyType = 'normal' // `normal` or `copyAsMarkdown` or `copyAsHtml`
|
this._copyType = 'normal' // `normal` or `copyAsMarkdown` or `copyAsHtml`
|
||||||
this._pasteType = 'normal' // `normal` or `pasteAsPlainText`
|
this._pasteType = 'normal' // `normal` or `pasteAsPlainText`
|
||||||
|
this._copyInfo = null
|
||||||
this.listen()
|
this.listen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,8 +12,16 @@ class Clipboard {
|
|||||||
const docPasteHandler = event => {
|
const docPasteHandler = event => {
|
||||||
contentState.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 => {
|
const copyCutHandler = event => {
|
||||||
contentState.copyHandler(event, this._copyType)
|
contentState.copyHandler(event, this._copyType, this._copyInfo)
|
||||||
if (event.type === 'cut') {
|
if (event.type === 'cut') {
|
||||||
// when user use `cut` function, the dom has been deleted by default.
|
// when user use `cut` function, the dom has been deleted by default.
|
||||||
// But should update content state manually.
|
// But should update content state manually.
|
||||||
@ -30,6 +39,8 @@ class Clipboard {
|
|||||||
eventCenter.attachDOMEvent(container, 'paste', pasteHandler)
|
eventCenter.attachDOMEvent(container, 'paste', pasteHandler)
|
||||||
eventCenter.attachDOMEvent(container, 'cut', copyCutHandler)
|
eventCenter.attachDOMEvent(container, 'cut', copyCutHandler)
|
||||||
eventCenter.attachDOMEvent(container, 'copy', copyCutHandler)
|
eventCenter.attachDOMEvent(container, 'copy', copyCutHandler)
|
||||||
|
eventCenter.attachDOMEvent(document.body, 'cut', docCopyCutHandler)
|
||||||
|
eventCenter.attachDOMEvent(document.body, 'copy', docCopyCutHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
copyAsMarkdown () {
|
copyAsMarkdown () {
|
||||||
@ -47,15 +58,14 @@ class Clipboard {
|
|||||||
document.execCommand('paste')
|
document.execCommand('paste')
|
||||||
}
|
}
|
||||||
|
|
||||||
copy (name) {
|
/**
|
||||||
switch (name) {
|
* Copy the anchor block(table, paragraph, math block etc) with the info
|
||||||
case 'table':
|
* @param {string|object} info is the block key if it's string, or block if it's object
|
||||||
this._copyType = 'copyTable'
|
*/
|
||||||
document.execCommand('copy')
|
copy (info) {
|
||||||
break
|
this._copyType = 'copyBlock'
|
||||||
default:
|
this._copyInfo = info
|
||||||
break
|
document.execCommand('copy')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,9 @@ class Keyboard {
|
|||||||
case EVENT_KEYS.Backspace: {
|
case EVENT_KEYS.Backspace: {
|
||||||
return contentState.docBackspaceHandler(event)
|
return contentState.docBackspaceHandler(event)
|
||||||
}
|
}
|
||||||
|
case EVENT_KEYS.Delete: {
|
||||||
|
return contentState.docDeleteHandler(event)
|
||||||
|
}
|
||||||
case EVENT_KEYS.ArrowUp: // fallthrough
|
case EVENT_KEYS.ArrowUp: // fallthrough
|
||||||
case EVENT_KEYS.ArrowDown: // fallthrough
|
case EVENT_KEYS.ArrowDown: // fallthrough
|
||||||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||||||
@ -127,6 +130,7 @@ class Keyboard {
|
|||||||
if (event.metaKey || event.ctrlKey) {
|
if (event.metaKey || event.ctrlKey) {
|
||||||
container.classList.add('ag-meta-or-ctrl')
|
container.classList.add('ag-meta-or-ctrl')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.shownFloat.size > 0 &&
|
this.shownFloat.size > 0 &&
|
||||||
(
|
(
|
||||||
@ -172,11 +176,6 @@ class Keyboard {
|
|||||||
this.muya.dispatchChange()
|
this.muya.dispatchChange()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'a':
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
contentState.tableCellHandler(event)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case EVENT_KEYS.ArrowUp: // fallthrough
|
case EVENT_KEYS.ArrowUp: // fallthrough
|
||||||
case EVENT_KEYS.ArrowDown: // fallthrough
|
case EVENT_KEYS.ArrowDown: // fallthrough
|
||||||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||||||
|
@ -4,6 +4,7 @@ class MouseEvent {
|
|||||||
constructor (muya) {
|
constructor (muya) {
|
||||||
this.muya = muya
|
this.muya = muya
|
||||||
this.mouseBinding()
|
this.mouseBinding()
|
||||||
|
this.mouseDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseBinding () {
|
mouseBinding () {
|
||||||
@ -39,6 +40,19 @@ class MouseEvent {
|
|||||||
eventCenter.attachDOMEvent(container, 'mouseover', handler)
|
eventCenter.attachDOMEvent(container, 'mouseover', handler)
|
||||||
eventCenter.attachDOMEvent(container, 'mouseout', leaveHandler)
|
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
|
export default MouseEvent
|
||||||
|
@ -262,6 +262,9 @@ class Muya {
|
|||||||
}
|
}
|
||||||
|
|
||||||
blur () {
|
blur () {
|
||||||
|
const selection = document.getSelection()
|
||||||
|
|
||||||
|
selection.removeAllRanges()
|
||||||
this.container.blur()
|
this.container.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,12 +324,13 @@ class Muya {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectAll () {
|
selectAll () {
|
||||||
if (this.hasFocus()) {
|
this.contentState.selectAll()
|
||||||
this.contentState.selectAll()
|
|
||||||
}
|
if (!this.hasFocus()) {
|
||||||
const activeElement = document.activeElement
|
const activeElement = document.activeElement
|
||||||
if (activeElement.nodeName === 'INPUT') {
|
if (activeElement.nodeName === 'INPUT') {
|
||||||
activeElement.select()
|
activeElement.select()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,8 +346,12 @@ class Muya {
|
|||||||
this.clipboard.pasteAsPlainText()
|
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) {
|
setOptions (options, needRender = false) {
|
||||||
|
@ -149,7 +149,7 @@ class StateRender {
|
|||||||
render (blocks, activeBlocks, matches) {
|
render (blocks, activeBlocks, matches) {
|
||||||
const selector = `div#${CLASS_OR_ID.AG_EDITOR_ID}`
|
const selector = `div#${CLASS_OR_ID.AG_EDITOR_ID}`
|
||||||
const children = blocks.map(block => {
|
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)
|
const newVdom = h(selector, children)
|
||||||
@ -167,7 +167,7 @@ class StateRender {
|
|||||||
const cursorOutMostBlock = activeBlocks[activeBlocks.length - 1]
|
const cursorOutMostBlock = activeBlocks[activeBlocks.length - 1]
|
||||||
// If cursor is not in render blocks, need to render cursor block independently
|
// If cursor is not in render blocks, need to render cursor block independently
|
||||||
const needRenderCursorBlock = blocks.indexOf(cursorOutMostBlock) === -1
|
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 html = toHTML(newVnode).replace(/^<section>([\s\S]+?)<\/section>$/, '$1')
|
||||||
|
|
||||||
const needToRemoved = []
|
const needToRemoved = []
|
||||||
@ -196,7 +196,7 @@ class StateRender {
|
|||||||
const cursorDom = document.querySelector(`#${key}`)
|
const cursorDom = document.querySelector(`#${key}`)
|
||||||
if (cursorDom) {
|
if (cursorDom) {
|
||||||
const oldCursorVnode = toVNode(cursorDom)
|
const oldCursorVnode = toVNode(cursorDom)
|
||||||
const newCursorVnode = this.renderBlock(cursorOutMostBlock, activeBlocks, matches)
|
const newCursorVnode = this.renderBlock(null, cursorOutMostBlock, activeBlocks, matches)
|
||||||
patch(oldCursorVnode, newCursorVnode)
|
patch(oldCursorVnode, newCursorVnode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,7 +215,7 @@ class StateRender {
|
|||||||
*/
|
*/
|
||||||
singleRender (block, activeBlocks, matches) {
|
singleRender (block, activeBlocks, matches) {
|
||||||
const selector = `#${block.key}`
|
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 rootDom = document.querySelector(selector)
|
||||||
const oldVdom = toVNode(rootDom)
|
const oldVdom = toVNode(rootDom)
|
||||||
patch(oldVdom, newVdom)
|
patch(oldVdom, newVdom)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* [renderBlock render one block, no matter it is a container block or text block]
|
* [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
|
const method = Array.isArray(block.children) && block.children.length > 0
|
||||||
? 'renderContainerBlock'
|
? 'renderContainerBlock'
|
||||||
: 'renderLeafBlock'
|
: '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 { CLASS_OR_ID } from '../../../config'
|
||||||
import { renderTableTools } from './renderToolBar'
|
import { renderTableTools } from './renderToolBar'
|
||||||
import { renderEditIcon } from './renderContainerEditIcon'
|
import { renderEditIcon } from './renderContainerEditIcon'
|
||||||
|
import { renderLeftBar, renderBottomBar } from './renderTableDargBar'
|
||||||
import { h } from '../snabbdom'
|
import { h } from '../snabbdom'
|
||||||
|
|
||||||
const PRE_BLOCK_HASH = {
|
const PRE_BLOCK_HASH = {
|
||||||
@ -15,9 +16,11 @@ const PRE_BLOCK_HASH = {
|
|||||||
'vega-lite': `.${CLASS_OR_ID.AG_VEGA_LITE}`
|
'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)
|
let selector = this.getSelector(block, activeBlocks)
|
||||||
const {
|
const {
|
||||||
|
key,
|
||||||
|
align,
|
||||||
type,
|
type,
|
||||||
headingStyle,
|
headingStyle,
|
||||||
editable,
|
editable,
|
||||||
@ -26,9 +29,10 @@ export default function renderContainerBlock (block, activeBlocks, matches, useC
|
|||||||
listItemType,
|
listItemType,
|
||||||
bulletMarkerOrDelimiter,
|
bulletMarkerOrDelimiter,
|
||||||
isLooseListItem,
|
isLooseListItem,
|
||||||
lang
|
lang,
|
||||||
|
column
|
||||||
} = block
|
} = 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 = {
|
const data = {
|
||||||
attrs: {},
|
attrs: {},
|
||||||
dataset: {}
|
dataset: {}
|
||||||
@ -42,7 +46,66 @@ export default function renderContainerBlock (block, activeBlocks, matches, useC
|
|||||||
selector += `.language-${lang.replace(/[#.]{1}/g, '')}`
|
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)) {
|
if (/^h\d$/.test(type)) {
|
||||||
// TODO: This should be the best place to create and update the TOC.
|
// TODO: This should be the best place to create and update the TOC.
|
||||||
// Cache `block.key` and title and update only if necessary.
|
// Cache `block.key` and title and update only if necessary.
|
||||||
|
@ -68,7 +68,7 @@ const hasReferenceToken = tokens => {
|
|||||||
return result
|
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 { loadMathMap } = this
|
||||||
const { cursor } = this.muya.contentState
|
const { cursor } = this.muya.contentState
|
||||||
let selector = this.getSelector(block, activeBlocks)
|
let selector = this.getSelector(block, activeBlocks)
|
||||||
@ -77,7 +77,6 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
|||||||
const {
|
const {
|
||||||
text,
|
text,
|
||||||
type,
|
type,
|
||||||
align,
|
|
||||||
checked,
|
checked,
|
||||||
key,
|
key,
|
||||||
lang,
|
lang,
|
||||||
@ -120,11 +119,7 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/th|td/.test(type) && align) {
|
if (type === 'div') {
|
||||||
Object.assign(data.attrs, {
|
|
||||||
style: `text-align:${align}`
|
|
||||||
})
|
|
||||||
} else if (type === 'div') {
|
|
||||||
const code = this.codeCache.get(block.preSibling)
|
const code = this.codeCache.get(block.preSibling)
|
||||||
switch (functionType) {
|
switch (functionType) {
|
||||||
case 'html': {
|
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 AlignLeftIcon from '../../../assets/pngicon/algin_left/2.png'
|
||||||
import AlignRightIcon from '../../../assets/pngicon/algin_right/2.png'
|
import AlignRightIcon from '../../../assets/pngicon/algin_right/2.png'
|
||||||
import AlignCenterIcon from '../../../assets/pngicon/algin_center/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 = [{
|
export const TABLE_TOOLS = [{
|
||||||
label: 'table',
|
label: 'table',
|
||||||
@ -32,7 +32,7 @@ export const TABLE_TOOLS = [{
|
|||||||
const renderToolBar = (type, tools, activeBlocks) => {
|
const renderToolBar = (type, tools, activeBlocks) => {
|
||||||
const children = tools.map(tool => {
|
const children = tools.map(tool => {
|
||||||
const { label, title, icon } = tool
|
const { label, title, icon } = tool
|
||||||
const { align } = activeBlocks[0]
|
const { align } = activeBlocks[1] // activeBlocks[0] is span block. cell content.
|
||||||
let selector = 'li'
|
let selector = 'li'
|
||||||
if (align && label === align) {
|
if (align && label === align) {
|
||||||
selector += '.active'
|
selector += '.active'
|
||||||
|
@ -497,9 +497,8 @@ class Selection {
|
|||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3) {
|
||||||
node = node.parentNode
|
node = node.parentNode
|
||||||
}
|
}
|
||||||
return node.closest('span.ag-paragraph') ||
|
|
||||||
node.closest('th.ag-paragraph') ||
|
return node.closest('span.ag-paragraph')
|
||||||
node.closest('td.ag-paragraph')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCursorRange () {
|
getCursorRange () {
|
||||||
|
@ -149,7 +149,7 @@ class TablePicker extends BaseFloat {
|
|||||||
selectItem () {
|
selectItem () {
|
||||||
const { cb } = this
|
const { cb } = this
|
||||||
const { row, column } = this.select
|
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()
|
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
|
const { top, right, height } = rect
|
||||||
|
|
||||||
Object.assign(ele.style, {
|
Object.assign(ele.style, {
|
||||||
top: `${top + height + 20}px`,
|
top: `${top + height + 15}px`,
|
||||||
left: `${right - ele.offsetWidth / 2 - 10}px`
|
left: `${right - ele.offsetWidth / 2 - 10}px`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -285,10 +285,10 @@ class ExportMarkdown {
|
|||||||
return str.replace(/([^\\])\|/g, '$1\\|')
|
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) {
|
if (tBody) {
|
||||||
tBody.children.forEach(bodyRow => {
|
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.
|
// We have to re-escape the chraracter to not break the table.
|
||||||
return text.replace(/\|/g, '\\|')
|
return text.replace(/\|/g, '\\|')
|
||||||
}
|
}
|
||||||
for (const headText of header) {
|
let i
|
||||||
const i = header.indexOf(headText)
|
let j
|
||||||
|
const headerLen = header.length
|
||||||
|
for (i = 0; i < headerLen; i++) {
|
||||||
|
const headText = header[i]
|
||||||
const th = this.createBlock('th', {
|
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)
|
this.appendChild(theadRow, th)
|
||||||
}
|
}
|
||||||
for (const row of cells) {
|
const rowLen = cells.length
|
||||||
|
for (i = 0; i < rowLen; i++) {
|
||||||
const rowBlock = this.createBlock('tr')
|
const rowBlock = this.createBlock('tr')
|
||||||
for (const cell of row) {
|
const rowContents = cells[i]
|
||||||
const i = row.indexOf(cell)
|
const colLen = rowContents.length
|
||||||
|
for (j = 0; j < colLen; j++) {
|
||||||
|
const cell = rowContents[j]
|
||||||
const td = this.createBlock('td', {
|
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(rowBlock, td)
|
||||||
}
|
}
|
||||||
this.appendChild(tbody, rowBlock)
|
this.appendChild(tbody, rowBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(table, { row: cells.length, column: header.length - 1 }) // set row and column
|
Object.assign(table, { row: cells.length, column: header.length - 1 }) // set row and column
|
||||||
block = this.createBlock('figure')
|
block = this.createBlock('figure')
|
||||||
block.functionType = 'table'
|
block.functionType = 'table'
|
||||||
this.appendChild(thead, theadRow)
|
this.appendChild(thead, theadRow)
|
||||||
this.appendChild(block, table)
|
this.appendChild(block, table)
|
||||||
this.appendChild(table, thead)
|
this.appendChild(table, thead)
|
||||||
this.appendChild(table, tbody)
|
if (tbody.children.length) {
|
||||||
|
this.appendChild(table, tbody)
|
||||||
|
}
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -358,8 +378,8 @@ const importRegister = ContentState => {
|
|||||||
html = html.replace(/<span> <\/span>/g, String.fromCharCode(160))
|
html = html.replace(/<span> <\/span>/g, String.fromCharCode(160))
|
||||||
|
|
||||||
html = turnSoftBreakToSpan(html)
|
html = turnSoftBreakToSpan(html)
|
||||||
|
|
||||||
const markdown = turndownService.turndown(html)
|
const markdown = turndownService.turndown(html)
|
||||||
|
|
||||||
return markdown
|
return markdown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,24 +385,39 @@ kbd {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table thead tr,
|
/* table thead tr,
|
||||||
table tr:nth-child(2n) {
|
table tr:nth-child(2n) {
|
||||||
background-color: var(--editorColor04);
|
background-color: var(--editorColor04);
|
||||||
}
|
} */
|
||||||
|
|
||||||
table tr th {
|
table tr th {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border: 1px solid var(--tableBorderColor);
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 6px 13px;
|
padding: 6px 13px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr td {
|
table tr td {
|
||||||
border: 1px solid var(--tableBorderColor);
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 6px 13px;
|
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,
|
table tr th:first-child,
|
||||||
@ -415,6 +430,119 @@ kbd {
|
|||||||
margin-bottom: 0;
|
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,
|
span code,
|
||||||
td code,
|
td code,
|
||||||
th code {
|
th code {
|
||||||
|
@ -86,6 +86,7 @@ import ImageToolbar from 'muya/lib/ui/imageToolbar'
|
|||||||
import Transformer from 'muya/lib/ui/transformer'
|
import Transformer from 'muya/lib/ui/transformer'
|
||||||
import FormatPicker from 'muya/lib/ui/formatPicker'
|
import FormatPicker from 'muya/lib/ui/formatPicker'
|
||||||
import LinkTools from 'muya/lib/ui/linkTools'
|
import LinkTools from 'muya/lib/ui/linkTools'
|
||||||
|
import TableBarTools from 'muya/lib/ui/tableTools'
|
||||||
import FrontMenu from 'muya/lib/ui/frontMenu'
|
import FrontMenu from 'muya/lib/ui/frontMenu'
|
||||||
import bus from '../../bus'
|
import bus from '../../bus'
|
||||||
import Search from '../search'
|
import Search from '../search'
|
||||||
@ -362,6 +363,7 @@ export default {
|
|||||||
Muya.use(LinkTools, {
|
Muya.use(LinkTools, {
|
||||||
jumpClick: this.jumpClick
|
jumpClick: this.jumpClick
|
||||||
})
|
})
|
||||||
|
Muya.use(TableBarTools)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
focusMode,
|
focusMode,
|
||||||
@ -424,9 +426,7 @@ export default {
|
|||||||
bus.$on('createParagraph', this.handleParagraph)
|
bus.$on('createParagraph', this.handleParagraph)
|
||||||
bus.$on('deleteParagraph', this.handleParagraph)
|
bus.$on('deleteParagraph', this.handleParagraph)
|
||||||
bus.$on('insertParagraph', this.handleInsertParagraph)
|
bus.$on('insertParagraph', this.handleInsertParagraph)
|
||||||
bus.$on('editTable', this.handleEditTable)
|
|
||||||
bus.$on('scroll-to-header', this.scrollToHeader)
|
bus.$on('scroll-to-header', this.scrollToHeader)
|
||||||
bus.$on('copy-block', this.handleCopyBlock)
|
|
||||||
bus.$on('print', this.handlePrint)
|
bus.$on('print', this.handlePrint)
|
||||||
bus.$on('screenshot-captured', this.handleScreenShot)
|
bus.$on('screenshot-captured', this.handleScreenShot)
|
||||||
|
|
||||||
@ -753,19 +753,10 @@ export default {
|
|||||||
editor && editor.insertParagraph(location)
|
editor && editor.insertParagraph(location)
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEditTable (data) {
|
|
||||||
const { editor } = this
|
|
||||||
editor && editor.editTable(data)
|
|
||||||
},
|
|
||||||
|
|
||||||
blurEditor () {
|
blurEditor () {
|
||||||
this.editor.blur()
|
this.editor.blur()
|
||||||
},
|
},
|
||||||
|
|
||||||
handleCopyBlock (name) {
|
|
||||||
this.editor.copy(name)
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScreenShot () {
|
handleScreenShot () {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
document.execCommand('paste')
|
document.execCommand('paste')
|
||||||
@ -795,9 +786,7 @@ export default {
|
|||||||
bus.$off('createParagraph', this.handleParagraph)
|
bus.$off('createParagraph', this.handleParagraph)
|
||||||
bus.$off('deleteParagraph', this.handleParagraph)
|
bus.$off('deleteParagraph', this.handleParagraph)
|
||||||
bus.$off('insertParagraph', this.handleInsertParagraph)
|
bus.$off('insertParagraph', this.handleInsertParagraph)
|
||||||
bus.$off('editTable', this.handleEditTable)
|
|
||||||
bus.$off('scroll-to-header', this.scrollToHeader)
|
bus.$off('scroll-to-header', this.scrollToHeader)
|
||||||
bus.$off('copy-block', this.handleCopyBlock)
|
|
||||||
bus.$off('print', this.handlePrint)
|
bus.$off('print', this.handlePrint)
|
||||||
bus.$off('screenshot-captured', this.handleScreenShot)
|
bus.$off('screenshot-captured', this.handleScreenShot)
|
||||||
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import bus from '../../bus'
|
import bus from '../../bus'
|
||||||
|
|
||||||
export const copyTable = () => {
|
|
||||||
bus.$emit('copy-block', 'table')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const copyAsMarkdown = (menuItem, browserWindow) => {
|
export const copyAsMarkdown = (menuItem, browserWindow) => {
|
||||||
bus.$emit('copyAsMarkdown', 'copyAsMarkdown')
|
bus.$emit('copyAsMarkdown', 'copyAsMarkdown')
|
||||||
}
|
}
|
||||||
@ -19,7 +15,3 @@ export const pasteAsPlainText = (menuItem, browserWindow) => {
|
|||||||
export const insertParagraph = location => {
|
export const insertParagraph = location => {
|
||||||
bus.$emit('insertParagraph', location)
|
bus.$emit('insertParagraph', location)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const editTable = data => {
|
|
||||||
bus.$emit('editTable', data)
|
|
||||||
}
|
|
||||||
|
@ -3,17 +3,12 @@ import {
|
|||||||
CUT,
|
CUT,
|
||||||
COPY,
|
COPY,
|
||||||
PASTE,
|
PASTE,
|
||||||
COPY_TABLE,
|
|
||||||
COPY_AS_MARKDOWN,
|
COPY_AS_MARKDOWN,
|
||||||
COPY_AS_HTML,
|
COPY_AS_HTML,
|
||||||
PASTE_AS_PLAIN_TEXT,
|
PASTE_AS_PLAIN_TEXT,
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
INSERT_BEFORE,
|
INSERT_BEFORE,
|
||||||
INSERT_AFTER,
|
INSERT_AFTER
|
||||||
INSERT_ROW,
|
|
||||||
REMOVE_ROW,
|
|
||||||
INSERT_COLUMN,
|
|
||||||
REMOVE_COLUMN
|
|
||||||
} from './menuItems'
|
} from './menuItems'
|
||||||
|
|
||||||
const { Menu, MenuItem } = remote
|
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 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]
|
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) {
|
;[CUT, COPY, COPY_AS_HTML, COPY_AS_MARKDOWN].forEach(item => {
|
||||||
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 => {
|
|
||||||
item.enabled = !disableCutAndCopy
|
item.enabled = !disableCutAndCopy
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -18,14 +18,6 @@ export const PASTE = {
|
|||||||
role: 'paste'
|
role: 'paste'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const COPY_TABLE = {
|
|
||||||
label: 'Copy Table',
|
|
||||||
id: 'copyTableMenuItem',
|
|
||||||
click (menuItem, browserWindow) {
|
|
||||||
contextMenu.copyTable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const COPY_AS_MARKDOWN = {
|
export const COPY_AS_MARKDOWN = {
|
||||||
label: 'Copy As Markdown',
|
label: 'Copy As Markdown',
|
||||||
id: 'copyAsMarkdownMenuItem',
|
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 = {
|
export const SEPARATOR = {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}
|
}
|
||||||
|