mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 20:01:34 +08:00
Line break (#197)
* update: change log * line break * feature: line break, support event and import and export markdown * shift enter in table cell * fix: not create a new paragraph when presss enter in the last cell of end table block * fix: html block can not work * feature: line break export to html * fix: problem2 * fix: problem 4
This commit is contained in:
parent
ce6efbe5f5
commit
cfd0d0a2fa
19
.github/CHANGELOG.md
vendored
19
.github/CHANGELOG.md
vendored
@ -1,3 +1,22 @@
|
|||||||
|
### 0.11.8
|
||||||
|
|
||||||
|
**:cactus:Feature**
|
||||||
|
|
||||||
|
- feature: add editorFont setting in user preference. (#175) - Anderson
|
||||||
|
|
||||||
|
**:butterfly:Optimization**
|
||||||
|
|
||||||
|
- #177 ATX headings strictly follow the GFM Spec - Jocs
|
||||||
|
- no need to auto pair when * is to open a list item - Jocs
|
||||||
|
- optimization: add sticky to block html tag - Jocs
|
||||||
|
- Add Japanese readme (#191) - Neetshin
|
||||||
|
|
||||||
|
**:beetle:Bug fix**
|
||||||
|
|
||||||
|
- fix the error 'Cannot read property 'forEach' of undefined' (#178) - 鸿则
|
||||||
|
- fix: Change Source Code Mode Accelerator (#180) - Mice
|
||||||
|
- fix: #153 Double space between tasklist checkbox and text - Jocs
|
||||||
|
|
||||||
### 0.10.21
|
### 0.10.21
|
||||||
|
|
||||||
**:notebook_with_decorative_cover:Note**
|
**:notebook_with_decorative_cover:Note**
|
||||||
|
1
src/editor/assets/icons/enter.svg
Normal file
1
src/editor/assets/icons/enter.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1523863913565" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11902" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M900.2 385.8v-192c0-22.1-17.9-40-40-40s-40 17.9-40 40v192c0 59.4-23.1 115.3-65.2 157.4-42 42-97.9 65.2-157.4 65.2H258.2l112.4-112.3c15.6-15.7 15.6-41 0-56.6-7.8-7.8-18-11.7-28.3-11.7s-20.5 3.9-28.3 11.7L135.5 618c-7.5 7.5-11.7 17.7-11.7 28.3s4.2 20.8 11.7 28.3l183.7 183.8c15.7 15.6 41 15.7 56.6 0s15.6-41 0-56.6L262.4 688.4h335.2c80.8 0 156.8-31.4 214-88.6s88.7-133.2 88.6-214z" p-id="11903" fill="#cdcdcd"></path></svg>
|
After Width: | Height: | Size: 808 B |
@ -60,6 +60,7 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_HIDE',
|
'AG_HIDE',
|
||||||
'AG_WARN',
|
'AG_WARN',
|
||||||
'AG_PARAGRAPH', // => 'ag-paragraph'
|
'AG_PARAGRAPH', // => 'ag-paragraph'
|
||||||
|
'AG_LINE',
|
||||||
'AG_ACTIVE',
|
'AG_ACTIVE',
|
||||||
'AG_EDITOR_ID',
|
'AG_EDITOR_ID',
|
||||||
'AG_FLOAT_BOX_ID',
|
'AG_FLOAT_BOX_ID',
|
||||||
@ -108,7 +109,8 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_LOOSE_LIST_ITEM',
|
'AG_LOOSE_LIST_ITEM',
|
||||||
'AG_TIGHT_LIST_ITEM',
|
'AG_TIGHT_LIST_ITEM',
|
||||||
'AG_HTML_TAG',
|
'AG_HTML_TAG',
|
||||||
'AG_A_LINK'
|
'AG_LINK',
|
||||||
|
'AG_HARD_LINE_BREAK'
|
||||||
])
|
])
|
||||||
|
|
||||||
export const codeMirrorConfig = {
|
export const codeMirrorConfig = {
|
||||||
|
@ -3,7 +3,7 @@ import { isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, isCursorAtEnd
|
|||||||
import { findNearestParagraph } from '../utils/domManipulate'
|
import { findNearestParagraph } from '../utils/domManipulate'
|
||||||
import selection from '../selection'
|
import selection from '../selection'
|
||||||
|
|
||||||
const HAS_TEXT_BLOCK_REG = /^(h\d|p|th|td|hr|pre)/
|
const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/
|
||||||
|
|
||||||
const arrowCtrl = ContentState => {
|
const arrowCtrl = ContentState => {
|
||||||
ContentState.prototype.firstInDescendant = function (block) {
|
ContentState.prototype.firstInDescendant = function (block) {
|
||||||
@ -119,7 +119,7 @@ const arrowCtrl = ContentState => {
|
|||||||
) {
|
) {
|
||||||
activeBlock = preBlock
|
activeBlock = preBlock
|
||||||
if (/^(?:pre|th|td)$/.test(preBlock.type)) {
|
if (/^(?:pre|th|td)$/.test(preBlock.type)) {
|
||||||
activeBlock = this.createBlock('p')
|
activeBlock = this.createBlockP()
|
||||||
activeBlock.temp = true
|
activeBlock.temp = true
|
||||||
this.insertBefore(activeBlock, anchorBlock)
|
this.insertBefore(activeBlock, anchorBlock)
|
||||||
}
|
}
|
||||||
@ -134,12 +134,12 @@ const arrowCtrl = ContentState => {
|
|||||||
if (nextBlock) {
|
if (nextBlock) {
|
||||||
activeBlock = nextBlock
|
activeBlock = nextBlock
|
||||||
if (/^(?:pre|th|td)$/.test(nextBlock.type)) {
|
if (/^(?:pre|th|td)$/.test(nextBlock.type)) {
|
||||||
activeBlock = this.createBlock('p')
|
activeBlock = this.createBlockP()
|
||||||
activeBlock.temp = true
|
activeBlock.temp = true
|
||||||
this.insertAfter(activeBlock, anchorBlock)
|
this.insertAfter(activeBlock, anchorBlock)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activeBlock = this.createBlock('p')
|
activeBlock = this.createBlockP()
|
||||||
this.insertAfter(activeBlock, anchorBlock)
|
this.insertAfter(activeBlock, anchorBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,8 +147,9 @@ const arrowCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activeBlock) {
|
if (activeBlock) {
|
||||||
const offset = activeBlock.text.length
|
const cursorBlock = activeBlock.type === 'p' ? activeBlock.children[0] : activeBlock
|
||||||
const key = activeBlock.key
|
const offset = cursorBlock.text.length
|
||||||
|
const key = cursorBlock.key
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: {
|
||||||
key,
|
key,
|
||||||
@ -165,13 +166,13 @@ const arrowCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
if (/th|td/.test(block.type)) {
|
if (/th|td/.test(block.type)) {
|
||||||
let activeBlock
|
let activeBlock
|
||||||
const anchorBlock = this.getParent(this.getParent(this.getParent(this.getParent(block))))
|
const anchorBlock = this.getParent(this.getParent(this.getParent(this.getParent(block)))) // figure
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(block.type === 'th' && preBlock && /^(?:pre|td)$/.test(preBlock.type) && event.key === EVENT_KEYS.ArrowUp) ||
|
(block.type === 'th' && preBlock && /^(?:pre|td)$/.test(preBlock.type) && event.key === EVENT_KEYS.ArrowUp) ||
|
||||||
(block.type === 'th' && preBlock && /^(?:pre|td)$/.test(preBlock.type) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
|
(block.type === 'th' && preBlock && /^(?:pre|td)$/.test(preBlock.type) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
|
||||||
) {
|
) {
|
||||||
activeBlock = this.createBlock('p')
|
activeBlock = this.createBlockP()
|
||||||
activeBlock.temp = true
|
activeBlock.temp = true
|
||||||
this.insertBefore(activeBlock, anchorBlock)
|
this.insertBefore(activeBlock, anchorBlock)
|
||||||
}
|
}
|
||||||
@ -180,14 +181,14 @@ const arrowCtrl = ContentState => {
|
|||||||
(block.type === 'td' && nextBlock && /^(?:pre|th)$/.test(nextBlock.type) && event.key === EVENT_KEYS.ArrowDown) ||
|
(block.type === 'td' && nextBlock && /^(?:pre|th)$/.test(nextBlock.type) && event.key === EVENT_KEYS.ArrowDown) ||
|
||||||
(block.type === 'td' && nextBlock && /^(?:pre|th)$/.test(nextBlock.type) && event.key === EVENT_KEYS.ArrowRight && right === 0)
|
(block.type === 'td' && nextBlock && /^(?:pre|th)$/.test(nextBlock.type) && event.key === EVENT_KEYS.ArrowRight && right === 0)
|
||||||
) {
|
) {
|
||||||
activeBlock = this.createBlock('p')
|
activeBlock = this.createBlockP()
|
||||||
activeBlock.temp = true
|
activeBlock.temp = true
|
||||||
this.insertAfter(activeBlock, anchorBlock)
|
this.insertAfter(activeBlock, anchorBlock)
|
||||||
}
|
}
|
||||||
if (activeBlock) {
|
if (activeBlock) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const offset = 0
|
const offset = 0
|
||||||
const key = activeBlock.key
|
const key = activeBlock.children[0].key
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: {
|
||||||
key,
|
key,
|
||||||
@ -238,10 +239,9 @@ const arrowCtrl = ContentState => {
|
|||||||
(event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
|
(event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const preBlockInLocation = this.findPreBlockInLocation(block)
|
if (!preBlock) return
|
||||||
if (!preBlockInLocation) return
|
const key = preBlock.key
|
||||||
const key = preBlockInLocation.key
|
const offset = preBlock.text.length
|
||||||
const offset = preBlockInLocation.text.length
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
@ -252,15 +252,14 @@ const arrowCtrl = ContentState => {
|
|||||||
(event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
|
(event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const nextBlockInLocation = this.findNextBlockInLocation(block)
|
|
||||||
let key
|
let key
|
||||||
if (nextBlockInLocation) {
|
if (nextBlock) {
|
||||||
key = nextBlockInLocation.key
|
key = nextBlock.key
|
||||||
} else {
|
} else {
|
||||||
const newBlock = this.createBlock('p')
|
const newBlock = this.createBlockP()
|
||||||
const lastBlock = this.blocks[this.blocks.length - 1]
|
const lastBlock = this.blocks[this.blocks.length - 1]
|
||||||
this.insertAfter(newBlock, lastBlock)
|
this.insertAfter(newBlock, lastBlock)
|
||||||
key = newBlock.key
|
key = newBlock.children[0].key
|
||||||
}
|
}
|
||||||
const offset = 0
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
|
@ -7,7 +7,8 @@ const backspaceCtrl = ContentState => {
|
|||||||
const node = selection.getSelectionStart()
|
const node = selection.getSelectionStart()
|
||||||
const nearestParagraph = findNearestParagraph(node)
|
const nearestParagraph = findNearestParagraph(node)
|
||||||
const outMostParagraph = findOutMostParagraph(node)
|
const outMostParagraph = findOutMostParagraph(node)
|
||||||
const block = this.getBlock(nearestParagraph.id)
|
let block = this.getBlock(nearestParagraph.id)
|
||||||
|
if (block.type === 'span') block = this.getParent(block)
|
||||||
const preBlock = this.getPreSibling(block)
|
const preBlock = this.getPreSibling(block)
|
||||||
const outBlock = this.findOutMostBlock(block)
|
const outBlock = this.findOutMostBlock(block)
|
||||||
const parent = this.getParent(block)
|
const parent = this.getParent(block)
|
||||||
@ -85,10 +86,10 @@ const backspaceCtrl = ContentState => {
|
|||||||
if (start.key !== end.key) {
|
if (start.key !== end.key) {
|
||||||
this.removeBlocks(startBlock, endBlock)
|
this.removeBlocks(startBlock, endBlock)
|
||||||
}
|
}
|
||||||
let newBlock = this.getNextSibling(startBlock)
|
let newBlock = this.findNextBlockInLocation(startBlock)
|
||||||
if (!newBlock) {
|
if (!newBlock) {
|
||||||
this.blocks = [this.createBlock()]
|
this.blocks = [this.createBlockP()]
|
||||||
newBlock = this.blocks[0]
|
newBlock = this.blocks[0].children[0]
|
||||||
}
|
}
|
||||||
const key = newBlock.key
|
const key = newBlock.key
|
||||||
const offset = 0
|
const offset = 0
|
||||||
@ -121,11 +122,18 @@ const backspaceCtrl = ContentState => {
|
|||||||
delete startBlock.pos
|
delete startBlock.pos
|
||||||
this.codeBlocks.delete(key)
|
this.codeBlocks.delete(key)
|
||||||
}
|
}
|
||||||
startBlock.type = 'p'
|
if (startBlock.type !== 'span') {
|
||||||
|
startBlock.type = 'span'
|
||||||
|
const pBlock = this.createBlock('p')
|
||||||
|
this.insertBefore(pBlock, startBlock)
|
||||||
|
this.removeBlock(startBlock)
|
||||||
|
this.appendChild(pBlock, startBlock)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
startBlock.text = startRemainText + endRemainText
|
startBlock.text = startRemainText + endRemainText
|
||||||
|
|
||||||
this.removeBlocks(startBlock, endBlock)
|
this.removeBlocks(startBlock, endBlock)
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
@ -136,30 +144,13 @@ const backspaceCtrl = ContentState => {
|
|||||||
const node = selection.getSelectionStart()
|
const node = selection.getSelectionStart()
|
||||||
const paragraph = findNearestParagraph(node)
|
const paragraph = findNearestParagraph(node)
|
||||||
const id = paragraph.id
|
const id = paragraph.id
|
||||||
const block = this.getBlock(id)
|
let block = this.getBlock(id)
|
||||||
|
if (block.type === 'span') block = this.getParent(block)
|
||||||
const parent = this.getBlock(block.parent)
|
const parent = this.getBlock(block.parent)
|
||||||
const preBlock = this.findPreBlockInLocation(block)
|
const preBlock = this.findPreBlockInLocation(block)
|
||||||
const { left } = selection.getCaretOffsets(paragraph)
|
const { left } = selection.getCaretOffsets(paragraph)
|
||||||
const inlineDegrade = this.checkBackspaceCase()
|
const inlineDegrade = this.checkBackspaceCase()
|
||||||
|
|
||||||
const getPreRow = row => {
|
|
||||||
const preSibling = this.getBlock(row.preSibling)
|
|
||||||
if (preSibling) {
|
|
||||||
return preSibling
|
|
||||||
} else {
|
|
||||||
const rowParent = this.getBlock(row.parent)
|
|
||||||
if (rowParent.type === 'tbody') {
|
|
||||||
const tHead = this.getBlock(rowParent.preSibling)
|
|
||||||
return tHead.children[0]
|
|
||||||
} else {
|
|
||||||
const table = this.getBlock(rowParent.parent)
|
|
||||||
const figure = this.getBlock(table.parent)
|
|
||||||
const figurePreSibling = this.getBlock(figure.preSibling)
|
|
||||||
return figurePreSibling ? this.lastInDescendant(figurePreSibling) : false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
||||||
@ -175,12 +166,11 @@ const backspaceCtrl = ContentState => {
|
|||||||
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
|
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const value = cm.getValue()
|
const value = cm.getValue()
|
||||||
const newBlock = this.createBlock('p')
|
const newBlock = this.createBlockP(value)
|
||||||
if (value) newBlock.text = value
|
|
||||||
this.insertBefore(newBlock, anchorBlock)
|
this.insertBefore(newBlock, anchorBlock)
|
||||||
this.removeBlock(anchorBlock)
|
this.removeBlock(anchorBlock)
|
||||||
this.codeBlocks.delete(id)
|
this.codeBlocks.delete(id)
|
||||||
const key = newBlock.key
|
const key = newBlock.children[0].key
|
||||||
const offset = 0
|
const offset = 0
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
@ -192,47 +182,27 @@ const backspaceCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
} else if (left === 0 && /th|td/.test(block.type)) {
|
} else if (left === 0 && /th|td/.test(block.type)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
const tHead = this.getBlock(parent.parent)
|
||||||
|
const table = this.getBlock(tHead.parent)
|
||||||
|
const figure = this.getBlock(table.parent)
|
||||||
|
const hasContent = tableHasContent(table)
|
||||||
let key
|
let key
|
||||||
let offset
|
let offset
|
||||||
if (preBlock) {
|
|
||||||
|
if ((!preBlock || !/th|td/.test(preBlock.type)) && !hasContent) {
|
||||||
|
const newLine = this.createBlock('span')
|
||||||
|
delete figure.functionType
|
||||||
|
figure.children = []
|
||||||
|
this.appendChild(figure, newLine)
|
||||||
|
figure.text = ''
|
||||||
|
figure.type = 'p'
|
||||||
|
key = newLine.key
|
||||||
|
offset = 0
|
||||||
|
} else if (preBlock) {
|
||||||
key = preBlock.key
|
key = preBlock.key
|
||||||
offset = preBlock.text.length
|
offset = preBlock.text.length
|
||||||
} else if (parent) {
|
|
||||||
const preRow = getPreRow(parent)
|
|
||||||
const tHead = this.getBlock(parent.parent)
|
|
||||||
const table = this.getBlock(tHead.parent)
|
|
||||||
const figure = this.getBlock(table.parent)
|
|
||||||
const hasContent = tableHasContent(table)
|
|
||||||
|
|
||||||
if (preRow) {
|
|
||||||
if (preRow.type === 'tr') {
|
|
||||||
const lastCell = preRow.children[preRow.children.length - 1]
|
|
||||||
key = lastCell.key
|
|
||||||
offset = lastCell.text.length
|
|
||||||
} else {
|
|
||||||
// if the table is empty change the table to a `p` paragraph
|
|
||||||
// else set the cursor to the pre block
|
|
||||||
if (!hasContent) {
|
|
||||||
figure.children = []
|
|
||||||
figure.text = ''
|
|
||||||
figure.type = 'p'
|
|
||||||
key = figure.key
|
|
||||||
offset = 0
|
|
||||||
} else {
|
|
||||||
key = preRow.key
|
|
||||||
offset = preRow.text.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!hasContent) {
|
|
||||||
figure.children = []
|
|
||||||
figure.text = ''
|
|
||||||
figure.type = 'p'
|
|
||||||
key = figure.key
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key !== undefined && offset !== undefined) {
|
if (key !== undefined && offset !== undefined) {
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
@ -310,7 +280,9 @@ const backspaceCtrl = ContentState => {
|
|||||||
preBlock.pos = { line, ch: ch - text.length }
|
preBlock.pos = { line, ch: ch - text.length }
|
||||||
|
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
|
if (block.type === 'span' && this.isOnlyChild(block)) {
|
||||||
|
this.removeBlock(parent)
|
||||||
|
}
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
@ -318,6 +290,9 @@ const backspaceCtrl = ContentState => {
|
|||||||
this.render()
|
this.render()
|
||||||
} else if (left === 0) {
|
} else if (left === 0) {
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
|
if (block.type === 'span' && this.isOnlyChild(block)) {
|
||||||
|
this.removeBlock(parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,13 +46,28 @@ const codeBlockCtrl = ContentState => {
|
|||||||
* [codeBlockUpdate if block updated to `pre` return true, else return false]
|
* [codeBlockUpdate if block updated to `pre` return true, else return false]
|
||||||
*/
|
*/
|
||||||
ContentState.prototype.codeBlockUpdate = function (block) {
|
ContentState.prototype.codeBlockUpdate = function (block) {
|
||||||
const match = CODE_UPDATE_REP.exec(block.text)
|
if (block.type === 'span') {
|
||||||
|
block = this.getParent(block)
|
||||||
|
}
|
||||||
|
// if it's not a p block, no need to update
|
||||||
|
if (block.type !== 'p') return false
|
||||||
|
// if p block's children are more than one, no need to update
|
||||||
|
if (block.children.length !== 1) return false
|
||||||
|
const { text } = block.children[0]
|
||||||
|
const match = CODE_UPDATE_REP.exec(text)
|
||||||
if (match) {
|
if (match) {
|
||||||
block.type = 'pre'
|
block.type = 'pre'
|
||||||
block.functionType = 'code'
|
block.functionType = 'code'
|
||||||
block.text = ''
|
block.text = ''
|
||||||
block.history = null
|
block.history = null
|
||||||
block.lang = match[1]
|
block.lang = match[1]
|
||||||
|
block.children = []
|
||||||
|
const key = block.key
|
||||||
|
const offset = 0
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !!match
|
return !!match
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,28 @@
|
|||||||
import selection from '../selection'
|
import selection from '../selection'
|
||||||
|
|
||||||
const enterCtrl = ContentState => {
|
const enterCtrl = ContentState => {
|
||||||
|
ContentState.prototype.chopBlockByCursor = function (block, key, offset) {
|
||||||
|
const newBlock = this.createBlock('p')
|
||||||
|
const children = block.children
|
||||||
|
const index = children.findIndex(child => child.key === key)
|
||||||
|
const activeLine = this.getBlock(key)
|
||||||
|
const text = activeLine.text
|
||||||
|
newBlock.children = children.splice(index + 1)
|
||||||
|
children[index].nextSibling = null
|
||||||
|
if (newBlock.children.length) {
|
||||||
|
newBlock.children[0].preSibling = null
|
||||||
|
}
|
||||||
|
if (offset === 0) {
|
||||||
|
this.removeBlock(activeLine, children)
|
||||||
|
this.prependChild(newBlock, activeLine)
|
||||||
|
} else if (offset < text.length) {
|
||||||
|
activeLine.text = text.substring(0, offset)
|
||||||
|
const newLine = this.createBlock('span', text.substring(offset))
|
||||||
|
this.prependChild(newBlock, newLine)
|
||||||
|
}
|
||||||
|
return newBlock
|
||||||
|
}
|
||||||
|
|
||||||
ContentState.prototype.chopBlock = function (block) {
|
ContentState.prototype.chopBlock = function (block) {
|
||||||
const parent = this.getParent(block)
|
const parent = this.getParent(block)
|
||||||
const type = parent.type
|
const type = parent.type
|
||||||
@ -28,20 +50,25 @@ const enterCtrl = ContentState => {
|
|||||||
return trBlock
|
return trBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.createBlockLi = function (text = '', type = 'p') {
|
ContentState.prototype.createBlockLi = function (paragraphInListItem) {
|
||||||
const liBlock = this.createBlock('li')
|
const liBlock = this.createBlock('li')
|
||||||
const pBlock = this.createBlock(type, text)
|
if (!paragraphInListItem) {
|
||||||
this.appendChild(liBlock, pBlock)
|
paragraphInListItem = this.createBlockP()
|
||||||
|
}
|
||||||
|
this.appendChild(liBlock, paragraphInListItem)
|
||||||
return liBlock
|
return liBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.createTaskItemBlock = function (text = '', checked = false) {
|
ContentState.prototype.createTaskItemBlock = function (paragraphInListItem, checked = false) {
|
||||||
const listItem = this.createBlock('li')
|
const listItem = this.createBlock('li')
|
||||||
const paragraphInListItem = this.createBlock('p', text)
|
|
||||||
const checkboxInListItem = this.createBlock('input')
|
const checkboxInListItem = this.createBlock('input')
|
||||||
|
|
||||||
listItem.listItemType = 'task'
|
listItem.listItemType = 'task'
|
||||||
checkboxInListItem.checked = checked
|
checkboxInListItem.checked = checked
|
||||||
|
|
||||||
|
if (!paragraphInListItem) {
|
||||||
|
paragraphInListItem = this.createBlockP()
|
||||||
|
}
|
||||||
this.appendChild(listItem, checkboxInListItem)
|
this.appendChild(listItem, checkboxInListItem)
|
||||||
this.appendChild(listItem, paragraphInListItem)
|
this.appendChild(listItem, paragraphInListItem)
|
||||||
return listItem
|
return listItem
|
||||||
@ -49,53 +76,13 @@ const enterCtrl = ContentState => {
|
|||||||
|
|
||||||
ContentState.prototype.enterHandler = function (event) {
|
ContentState.prototype.enterHandler = function (event) {
|
||||||
const { start, end } = selection.getCursorRange()
|
const { start, end } = selection.getCursorRange()
|
||||||
|
|
||||||
if (start.key !== end.key) {
|
|
||||||
event.preventDefault()
|
|
||||||
const startBlock = this.getBlock(start.key)
|
|
||||||
const endBlock = this.getBlock(end.key)
|
|
||||||
const key = start.key
|
|
||||||
const offset = start.offset
|
|
||||||
|
|
||||||
const startRemainText = startBlock.type === 'pre'
|
|
||||||
? startBlock.text.substring(0, start.offset - 1)
|
|
||||||
: startBlock.text.substring(0, start.offset)
|
|
||||||
|
|
||||||
const endRemainText = endBlock.type === 'pre'
|
|
||||||
? endBlock.text.substring(end.offset - 1)
|
|
||||||
: endBlock.text.substring(end.offset)
|
|
||||||
|
|
||||||
startBlock.text = startRemainText + endRemainText
|
|
||||||
|
|
||||||
this.removeBlocks(startBlock, endBlock)
|
|
||||||
this.cursor = {
|
|
||||||
start: { key, offset },
|
|
||||||
end: { key, offset }
|
|
||||||
}
|
|
||||||
this.render()
|
|
||||||
return this.enterHandler(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start.key === end.key && start.offset !== end.offset) {
|
|
||||||
event.preventDefault()
|
|
||||||
const key = start.key
|
|
||||||
const offset = start.offset
|
|
||||||
const block = this.getBlock(key)
|
|
||||||
block.text = block.text.substring(0, start.offset) + block.text.substring(end.offset)
|
|
||||||
this.cursor = {
|
|
||||||
start: { key, offset },
|
|
||||||
end: { key, offset }
|
|
||||||
}
|
|
||||||
this.render()
|
|
||||||
return this.enterHandler(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
let paragraph = document.querySelector(`#${start.key}`)
|
|
||||||
let block = this.getBlock(start.key)
|
let block = this.getBlock(start.key)
|
||||||
|
const endBlock = this.getBlock(end.key)
|
||||||
let parent = this.getParent(block)
|
let parent = this.getParent(block)
|
||||||
// handle float box
|
|
||||||
const { list, index, show } = this.floatBox
|
|
||||||
const { floatBox } = this
|
const { floatBox } = this
|
||||||
|
const { list, index, show } = floatBox
|
||||||
|
|
||||||
|
// handle float box
|
||||||
if (show) {
|
if (show) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
floatBox.cb(list[index])
|
floatBox.cb(list[index])
|
||||||
@ -107,26 +94,79 @@ const enterCtrl = ContentState => {
|
|||||||
if (block.type === 'pre') {
|
if (block.type === 'pre') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const getNextBlock = row => {
|
// handle select multiple blocks
|
||||||
let nextSibling = this.getBlock(row.nextSibling)
|
if (start.key !== end.key) {
|
||||||
if (!nextSibling) {
|
const key = start.key
|
||||||
const rowContainer = this.getBlock(row.parent)
|
const offset = start.offset
|
||||||
const table = this.getBlock(rowContainer.parent)
|
|
||||||
const figure = this.getBlock(table.parent)
|
const startRemainText = block.type === 'pre'
|
||||||
if (rowContainer.type === 'thead') {
|
? block.text.substring(0, start.offset - 1)
|
||||||
nextSibling = table.children[1]
|
: block.text.substring(0, start.offset)
|
||||||
} else if (figure.nextSibling) {
|
|
||||||
nextSibling = this.getBlock(figure.nextSibling)
|
const endRemainText = endBlock.type === 'pre'
|
||||||
} else {
|
? endBlock.text.substring(end.offset - 1)
|
||||||
nextSibling = this.createBlock('p')
|
: endBlock.text.substring(end.offset)
|
||||||
this.insertAfter(nextSibling, figure)
|
|
||||||
}
|
block.text = startRemainText + endRemainText
|
||||||
|
|
||||||
|
this.removeBlocks(block, endBlock)
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
}
|
}
|
||||||
return this.firstInDescendant(nextSibling)
|
this.render()
|
||||||
|
return this.enterHandler(event)
|
||||||
}
|
}
|
||||||
// enter in table
|
|
||||||
|
// handle select multiple charactors
|
||||||
|
if (start.key === end.key && start.offset !== end.offset) {
|
||||||
|
const key = start.key
|
||||||
|
const offset = start.offset
|
||||||
|
block.text = block.text.substring(0, start.offset) + block.text.substring(end.offset)
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
|
this.render()
|
||||||
|
return this.enterHandler(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle `shift + enter` insert `soft line break` or `hard line break`
|
||||||
|
// only cursor in `line block` can create `soft line break` and `hard line break`
|
||||||
|
if (event.shiftKey && block.type === 'span') {
|
||||||
|
const { text } = block
|
||||||
|
const newLineText = text.substring(start.offset)
|
||||||
|
block.text = text.substring(0, start.offset)
|
||||||
|
const newLine = this.createBlock('span', newLineText)
|
||||||
|
this.insertAfter(newLine, block)
|
||||||
|
const { key } = newLine
|
||||||
|
const offset = 0
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
|
return this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert `<br/>` in table cell if you want to open a new line.
|
||||||
|
// Why not use `soft line break` or `hard line break` ?
|
||||||
|
// Becasuse table cell only have one line.
|
||||||
|
if (event.shiftKey && /th|td/.test(block.type)) {
|
||||||
|
const { text, key } = block
|
||||||
|
const brTag = '<br/>'
|
||||||
|
block.text = text.substring(0, start.offset) + brTag + text.substring(start.offset)
|
||||||
|
const offset = start.offset + brTag.length
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
|
return this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle enter in table
|
||||||
if (/th|td/.test(block.type)) {
|
if (/th|td/.test(block.type)) {
|
||||||
const row = this.getBlock(block.parent)
|
const row = this.getBlock(block.parent)
|
||||||
const rowContainer = this.getBlock(row.parent)
|
const rowContainer = this.getBlock(row.parent)
|
||||||
@ -143,8 +183,14 @@ const enterCtrl = ContentState => {
|
|||||||
table.row++
|
table.row++
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSibling = getNextBlock(row)
|
let nextBlock = this.findNextBlockInLocation(block)
|
||||||
const key = nextSibling.key
|
// if table(figure block) is the last block, create a new P block after table(figure block).
|
||||||
|
if (!nextBlock) {
|
||||||
|
const newBlock = this.createBlockP()
|
||||||
|
this.insertAfter(newBlock, this.getParent(table))
|
||||||
|
nextBlock = newBlock.children[0]
|
||||||
|
}
|
||||||
|
const key = nextBlock.key
|
||||||
const offset = 0
|
const offset = 0
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
@ -153,6 +199,12 @@ const enterCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
return this.render()
|
return this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (block.type === 'span') {
|
||||||
|
block = parent
|
||||||
|
parent = this.getParent(block)
|
||||||
|
}
|
||||||
|
const paragraph = document.querySelector(`#${block.key}`)
|
||||||
if (
|
if (
|
||||||
(parent && parent.type === 'li' && this.isOnlyChild(block)) ||
|
(parent && parent.type === 'li' && this.isOnlyChild(block)) ||
|
||||||
(parent && parent.type === 'li' && parent.listItemType === 'task' && parent.children.length === 2) // one `input` and one `p`
|
(parent && parent.type === 'li' && parent.listItemType === 'task' && parent.children.length === 2) // one `input` and one `p`
|
||||||
@ -171,32 +223,31 @@ const enterCtrl = ContentState => {
|
|||||||
type = preType
|
type = preType
|
||||||
let { pre, post } = selection.chopHtmlByCursor(paragraph)
|
let { pre, post } = selection.chopHtmlByCursor(paragraph)
|
||||||
|
|
||||||
if (/^h\d/.test(type)) {
|
if (/^h\d$/.test(block.type)) {
|
||||||
const PREFIX = /^#+/.exec(pre)[0]
|
const PREFIX = /^#+/.exec(pre)[0]
|
||||||
post = `${PREFIX} ${post}`
|
post = `${PREFIX} ${post}`
|
||||||
}
|
block.text = pre
|
||||||
|
newBlock = this.createBlock(type, post)
|
||||||
if (type === 'li') {
|
} else if (block.type === 'p') {
|
||||||
|
newBlock = this.chopBlockByCursor(block, start.key, start.offset)
|
||||||
|
} else if (type === 'li') {
|
||||||
// handle task item
|
// handle task item
|
||||||
if (block.listItemType === 'task') {
|
if (block.listItemType === 'task') {
|
||||||
const { checked } = block.children[0] // block.children[0] is input[type=checkbox]
|
const { checked } = block.children[0] // block.children[0] is input[type=checkbox]
|
||||||
block.children[1].text = pre // block.children[1] is p
|
newBlock = this.chopBlockByCursor(block.children[1], start.key, start.offset)
|
||||||
newBlock = this.createTaskItemBlock(post, checked)
|
newBlock = this.createTaskItemBlock(newBlock, checked)
|
||||||
} else {
|
} else {
|
||||||
block.children[0].text = pre
|
newBlock = this.chopBlockByCursor(block.children[0], start.key, start.offset)
|
||||||
newBlock = this.createBlockLi(post)
|
newBlock = this.createBlockLi(newBlock)
|
||||||
newBlock.listItemType = block.listItemType
|
newBlock.listItemType = block.listItemType
|
||||||
}
|
}
|
||||||
newBlock.isLooseListItem = block.isLooseListItem
|
newBlock.isLooseListItem = block.isLooseListItem
|
||||||
} else {
|
|
||||||
block.text = pre
|
|
||||||
newBlock = this.createBlock(type, post)
|
|
||||||
}
|
}
|
||||||
this.insertAfter(newBlock, block)
|
this.insertAfter(newBlock, block)
|
||||||
break
|
break
|
||||||
case left === 0 && right === 0: // paragraph is empty
|
case left === 0 && right === 0: // paragraph is empty
|
||||||
if (parent && (parent.type === 'blockquote' || parent.type === 'ul')) {
|
if (parent && (parent.type === 'blockquote' || parent.type === 'ul')) {
|
||||||
newBlock = this.createBlock('p')
|
newBlock = this.createBlockP()
|
||||||
|
|
||||||
if (this.isOnlyChild(block)) {
|
if (this.isOnlyChild(block)) {
|
||||||
this.insertAfter(newBlock, parent)
|
this.insertAfter(newBlock, parent)
|
||||||
@ -214,7 +265,7 @@ const enterCtrl = ContentState => {
|
|||||||
} else if (parent && parent.type === 'li') {
|
} else if (parent && parent.type === 'li') {
|
||||||
if (parent.listItemType === 'task') {
|
if (parent.listItemType === 'task') {
|
||||||
const { checked } = parent.children[0]
|
const { checked } = parent.children[0]
|
||||||
newBlock = this.createTaskItemBlock('', checked)
|
newBlock = this.createTaskItemBlock(null, checked)
|
||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlockLi()
|
newBlock = this.createBlockLi()
|
||||||
newBlock.listItemType = parent.listItemType
|
newBlock.listItemType = parent.listItemType
|
||||||
@ -227,7 +278,7 @@ const enterCtrl = ContentState => {
|
|||||||
|
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlock('p')
|
newBlock = this.createBlockP()
|
||||||
if (preType === 'li') {
|
if (preType === 'li') {
|
||||||
const parent = this.getParent(block)
|
const parent = this.getParent(block)
|
||||||
this.insertAfter(newBlock, parent)
|
this.insertAfter(newBlock, parent)
|
||||||
@ -242,34 +293,47 @@ const enterCtrl = ContentState => {
|
|||||||
if (preType === 'li') {
|
if (preType === 'li') {
|
||||||
if (block.listItemType === 'task') {
|
if (block.listItemType === 'task') {
|
||||||
const { checked } = block.children[0]
|
const { checked } = block.children[0]
|
||||||
newBlock = this.createTaskItemBlock('', checked)
|
newBlock = this.createTaskItemBlock(null, checked)
|
||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlockLi()
|
newBlock = this.createBlockLi()
|
||||||
newBlock.listItemType = block.listItemType
|
newBlock.listItemType = block.listItemType
|
||||||
}
|
}
|
||||||
newBlock.isLooseListItem = block.isLooseListItem
|
newBlock.isLooseListItem = block.isLooseListItem
|
||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlock('p')
|
newBlock = this.createBlockP()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left === 0 && right !== 0) {
|
if (left === 0 && right !== 0) {
|
||||||
this.insertBefore(newBlock, block)
|
this.insertBefore(newBlock, block)
|
||||||
newBlock = block
|
newBlock = block
|
||||||
} else {
|
} else {
|
||||||
|
if (block.type === 'p') {
|
||||||
|
const lastLine = block.children[block.children.length - 1]
|
||||||
|
if (block.text.trim() === '') this.removeBlock(lastLine)
|
||||||
|
}
|
||||||
this.insertAfter(newBlock, block)
|
this.insertAfter(newBlock, block)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
newBlock = this.createBlock('p')
|
newBlock = this.createBlockP()
|
||||||
this.insertAfter(newBlock, block)
|
this.insertAfter(newBlock, block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
this.codeBlockUpdate(newBlock.type === 'li' ? newBlock.children[0] : newBlock)
|
const getParagraphBlock = block => {
|
||||||
|
if (block.type === 'li') {
|
||||||
|
return block.listItemType === 'task' ? block.children[1] : block.children[0]
|
||||||
|
} else {
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.codeBlockUpdate(getParagraphBlock(newBlock))
|
||||||
// If block is pre block when updated, need to focus it.
|
// If block is pre block when updated, need to focus it.
|
||||||
const blockNeedFocus = this.codeBlockUpdate(block.type === 'li' ? block.children[0] : block)
|
const preParagraphBlock = getParagraphBlock(block)
|
||||||
let tableNeedFocus = this.tableBlockUpdate(block.type === 'li' ? block.children[0] : block)
|
const blockNeedFocus = this.codeBlockUpdate(preParagraphBlock)
|
||||||
let htmlNeedFocus = this.updateHtmlBlock(block.type === 'li' ? block.children[0] : block)
|
let tableNeedFocus = this.tableBlockUpdate(preParagraphBlock)
|
||||||
|
let htmlNeedFocus = this.updateHtmlBlock(preParagraphBlock)
|
||||||
let cursorBlock
|
let cursorBlock
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
@ -288,16 +352,8 @@ const enterCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key
|
let key
|
||||||
if (cursorBlock.type === 'li') {
|
cursorBlock = getParagraphBlock(cursorBlock)
|
||||||
if (cursorBlock.listItemType === 'task') {
|
key = cursorBlock.type === 'p' ? cursorBlock.children[0].key : cursorBlock.key
|
||||||
key = cursorBlock.children[1].key
|
|
||||||
} else {
|
|
||||||
key = cursorBlock.children[0].key
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key = cursorBlock.key
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = 0
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
|
@ -91,7 +91,7 @@ const htmlBlock = ContentState => {
|
|||||||
|
|
||||||
ContentState.prototype.initHtmlBlock = function (block, tagName) {
|
ContentState.prototype.initHtmlBlock = function (block, tagName) {
|
||||||
const isVoidTag = VOID_HTML_TAGS.indexOf(tagName) > -1
|
const isVoidTag = VOID_HTML_TAGS.indexOf(tagName) > -1
|
||||||
const { text } = block
|
const { text } = block.children[0]
|
||||||
const htmlContent = isVoidTag ? text : `${text}\n\n</${tagName}>`
|
const htmlContent = isVoidTag ? text : `${text}\n\n</${tagName}>`
|
||||||
|
|
||||||
const pos = {
|
const pos = {
|
||||||
@ -110,8 +110,9 @@ const htmlBlock = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateHtmlBlock = function (block) {
|
ContentState.prototype.updateHtmlBlock = function (block) {
|
||||||
const { type, text } = block
|
const { type } = block
|
||||||
if (type !== 'li' && type !== 'p') return false
|
if (type !== 'li' && type !== 'p') return false
|
||||||
|
const { text } = block.children[0]
|
||||||
const match = HTML_BLOCK_REG.exec(text)
|
const match = HTML_BLOCK_REG.exec(text)
|
||||||
const tagName = match && match[1] && HTML_TAGS.find(t => t === match[1])
|
const tagName = match && match[1] && HTML_TAGS.find(t => t === match[1])
|
||||||
return VOID_HTML_TAGS.indexOf(tagName) === -1 && tagName ? this.initHtmlBlock(block, tagName) : false
|
return VOID_HTML_TAGS.indexOf(tagName) === -1 && tagName ? this.initHtmlBlock(block, tagName) : false
|
||||||
|
@ -54,7 +54,7 @@ const convertBlocksToArray = blocks => {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// use to cache the keys which you dont want to remove.
|
// use to cache the keys which you don't want to remove.
|
||||||
const exemption = new Set()
|
const exemption = new Set()
|
||||||
|
|
||||||
class ContentState {
|
class ContentState {
|
||||||
@ -62,7 +62,7 @@ class ContentState {
|
|||||||
const { eventCenter } = options
|
const { eventCenter } = options
|
||||||
Object.assign(this, options)
|
Object.assign(this, options)
|
||||||
this.keys = new Set()
|
this.keys = new Set()
|
||||||
this.blocks = [ this.createBlock() ]
|
this.blocks = [ this.createBlockP() ]
|
||||||
this.stateRender = new StateRender(eventCenter)
|
this.stateRender = new StateRender(eventCenter)
|
||||||
this.codeBlocks = new Map()
|
this.codeBlocks = new Map()
|
||||||
this.loadMathMap = new Map()
|
this.loadMathMap = new Map()
|
||||||
@ -72,20 +72,16 @@ class ContentState {
|
|||||||
|
|
||||||
init () {
|
init () {
|
||||||
const lastBlock = this.getLastBlock()
|
const lastBlock = this.getLastBlock()
|
||||||
|
const { key, text } = lastBlock
|
||||||
|
const offset = text.length
|
||||||
this.searchMatches = {
|
this.searchMatches = {
|
||||||
value: '',
|
value: '', // the search value
|
||||||
matches: [],
|
matches: [], // matches
|
||||||
index: -1
|
index: -1 // active match
|
||||||
}
|
}
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: { key, offset },
|
||||||
key: lastBlock.key,
|
end: { key, offset }
|
||||||
offset: lastBlock.text.length
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
key: lastBlock.key,
|
|
||||||
offset: lastBlock.text.length
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.history.push({
|
this.history.push({
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
@ -95,8 +91,7 @@ class ContentState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCursor () {
|
setCursor () {
|
||||||
const { cursor } = this
|
selection.setCursorRange(this.cursor)
|
||||||
selection.setCursorRange(cursor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render (isRenderCursor = true) {
|
render (isRenderCursor = true) {
|
||||||
@ -111,7 +106,11 @@ class ContentState {
|
|||||||
this.renderMath()
|
this.renderMath()
|
||||||
}
|
}
|
||||||
|
|
||||||
createBlock (type = 'p', text = '') {
|
/**
|
||||||
|
* A block in Aganippe present a paragraph(block syntax in GFM) or a line in paragraph.
|
||||||
|
* a line block must in a `p block` and `p block`'s children must be line blocks.
|
||||||
|
*/
|
||||||
|
createBlock (type = 'span', text = '') { // span type means it is a line block.
|
||||||
const key = getUniqueId(this.keys)
|
const key = getUniqueId(this.keys)
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
@ -124,11 +123,21 @@ class ContentState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createBlockP (text = '') {
|
||||||
|
const pBlock = this.createBlock('p')
|
||||||
|
const lineBlock = this.createBlock('span', text)
|
||||||
|
this.appendChild(pBlock, lineBlock)
|
||||||
|
return pBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
isCollapse (cursor = this.cursor) {
|
||||||
|
const { start, end } = cursor
|
||||||
|
return start.key === end.key && start.offset === end.offset
|
||||||
|
}
|
||||||
|
|
||||||
// getBlocks
|
// getBlocks
|
||||||
getBlocks () {
|
getBlocks () {
|
||||||
let key
|
for (const [ key, cm ] of this.codeBlocks.entries()) {
|
||||||
let cm
|
|
||||||
for ([ key, cm ] of this.codeBlocks.entries()) {
|
|
||||||
const value = cm.getValue()
|
const value = cm.getValue()
|
||||||
const block = this.getBlock(key)
|
const block = this.getBlock(key)
|
||||||
if (block) block.text = value
|
if (block) block.text = value
|
||||||
@ -298,7 +307,7 @@ class ContentState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBlock (block) {
|
removeBlock (block, fromBlocks = this.blocks) {
|
||||||
if (block.type === 'pre') {
|
if (block.type === 'pre') {
|
||||||
const codeBlockId = block.key
|
const codeBlockId = block.key
|
||||||
if (this.codeBlocks.has(codeBlockId)) {
|
if (this.codeBlocks.has(codeBlockId)) {
|
||||||
@ -328,7 +337,7 @@ class ContentState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remove(this.blocks, block)
|
remove(fromBlocks, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveBlocks () {
|
getActiveBlocks () {
|
||||||
@ -392,6 +401,15 @@ class ContentState {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prependChild (parent, block) {
|
||||||
|
if (parent.children.length) {
|
||||||
|
const firstChild = parent.children[0]
|
||||||
|
this.insertBefore(block, firstChild)
|
||||||
|
} else {
|
||||||
|
this.appendChild(parent, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
appendChild (parent, block) {
|
appendChild (parent, block) {
|
||||||
const len = parent.children.length
|
const len = parent.children.length
|
||||||
const lastChild = parent.children[len - 1]
|
const lastChild = parent.children[len - 1]
|
||||||
|
@ -35,11 +35,12 @@ const tableBlockCtrl = ContentState => {
|
|||||||
let figureBlock
|
let figureBlock
|
||||||
if (start.key === end.key) {
|
if (start.key === end.key) {
|
||||||
const startBlock = this.getBlock(start.key)
|
const startBlock = this.getBlock(start.key)
|
||||||
|
const anchor = startBlock.type === 'span' ? this.getParent(startBlock) : startBlock
|
||||||
if (startBlock.text) {
|
if (startBlock.text) {
|
||||||
figureBlock = this.createBlock('figure')
|
figureBlock = this.createBlock('figure')
|
||||||
this.insertAfter(figureBlock, startBlock)
|
this.insertAfter(figureBlock, anchor)
|
||||||
} else {
|
} else {
|
||||||
figureBlock = startBlock
|
figureBlock = anchor
|
||||||
figureBlock.type = 'figure'
|
figureBlock.type = 'figure'
|
||||||
figureBlock.functionType = 'table'
|
figureBlock.functionType = 'table'
|
||||||
figureBlock.text = ''
|
figureBlock.text = ''
|
||||||
@ -58,7 +59,7 @@ const tableBlockCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.initTable = function (block) {
|
ContentState.prototype.initTable = function (block) {
|
||||||
const { text } = block
|
const { text } = block.children[0]
|
||||||
const rowHeader = []
|
const rowHeader = []
|
||||||
const len = text.length
|
const len = text.length
|
||||||
let i
|
let i
|
||||||
@ -114,10 +115,12 @@ const tableBlockCtrl = ContentState => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
|
const newLine = this.createBlock('span')
|
||||||
figure.children = []
|
figure.children = []
|
||||||
|
this.appendChild(figure, newLine)
|
||||||
figure.type = 'p'
|
figure.type = 'p'
|
||||||
figure.text = ''
|
figure.text = ''
|
||||||
const key = figure.key
|
const key = newLine.key
|
||||||
const offset = 0
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
@ -197,8 +200,9 @@ const tableBlockCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.tableBlockUpdate = function (block) {
|
ContentState.prototype.tableBlockUpdate = function (block) {
|
||||||
const { type, text } = block
|
const { type } = block
|
||||||
if (type !== 'li' && type !== 'p') return false
|
if (type !== 'p') return false
|
||||||
|
const { text } = block.children[0]
|
||||||
const match = TABLE_BLOCK_REG.exec(text)
|
const match = TABLE_BLOCK_REG.exec(text)
|
||||||
return (match && isLengthEven(match[1]) && isLengthEven(match[2])) ? this.initTable(block) : false
|
return (match && isLengthEven(match[1]) && isLengthEven(match[2])) ? this.initTable(block) : false
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,18 @@ const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FREGMENTS.join('|'), 'i')
|
|||||||
let lastCursor = null
|
let lastCursor = null
|
||||||
|
|
||||||
const updateCtrl = ContentState => {
|
const updateCtrl = ContentState => {
|
||||||
|
// handle task list item checkbox click
|
||||||
|
ContentState.prototype.listItemCheckBoxClick = function (checkbox) {
|
||||||
|
const { checked, id } = checkbox
|
||||||
|
const block = this.getBlock(id)
|
||||||
|
block.checked = checked
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentState.prototype.checkSameLooseType = function (list, isLooseType) {
|
||||||
|
return list.children[0].isLooseListItem === isLooseType
|
||||||
|
}
|
||||||
|
|
||||||
ContentState.prototype.checkNeedRender = function (block) {
|
ContentState.prototype.checkNeedRender = function (block) {
|
||||||
const { start: cStart, end: cEnd } = this.cursor
|
const { start: cStart, end: cEnd } = this.cursor
|
||||||
const startOffset = cStart.offset
|
const startOffset = cStart.offset
|
||||||
@ -39,11 +51,16 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.checkInlineUpdate = function (block) {
|
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||||
|
// table cell can not have blocks in it
|
||||||
if (/th|td|figure/.test(block.type)) return false
|
if (/th|td|figure/.test(block.type)) return false
|
||||||
const { text } = block
|
// only first line block can update to other block
|
||||||
|
if (block.type === 'span' && block.preSibling) return false
|
||||||
|
if (block.type === 'span') {
|
||||||
|
block = this.getParent(block)
|
||||||
|
}
|
||||||
const parent = this.getParent(block)
|
const parent = this.getParent(block)
|
||||||
|
const text = block.type === 'p' ? block.children.map(child => child.text).join('\n') : block.text
|
||||||
const [match, bullet, tasklist, order, header, blockquote, hr] = text.match(INLINE_UPDATE_REG) || []
|
const [match, bullet, tasklist, order, header, blockquote, hr] = text.match(INLINE_UPDATE_REG) || []
|
||||||
let newType
|
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case (hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
case (hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
||||||
@ -54,7 +71,8 @@ const updateCtrl = ContentState => {
|
|||||||
this.updateList(block, 'bullet', bullet)
|
this.updateList(block, 'bullet', bullet)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case !!tasklist && parent && parent.listItemType === 'bullet': // only `bullet` list item can be update to `task` list item
|
// only `bullet` list item can be update to `task` list item
|
||||||
|
case !!tasklist && parent && parent.listItemType === 'bullet':
|
||||||
this.updateTaskListItem(block, 'tasklist', tasklist)
|
this.updateTaskListItem(block, 'tasklist', tasklist)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@ -63,12 +81,8 @@ const updateCtrl = ContentState => {
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
case !!header:
|
case !!header:
|
||||||
newType = `h${header.length}`
|
this.updateHeader(block, header, text)
|
||||||
if (block.type !== newType) {
|
return true
|
||||||
block.type = newType // updateHeader
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case !!blockquote:
|
case !!blockquote:
|
||||||
this.updateBlockQuote(block)
|
this.updateBlockQuote(block)
|
||||||
@ -76,15 +90,79 @@ const updateCtrl = ContentState => {
|
|||||||
|
|
||||||
case !match:
|
case !match:
|
||||||
default:
|
default:
|
||||||
newType = 'p'
|
return this.updateToParagraph(block)
|
||||||
if (block.type !== newType) {
|
}
|
||||||
block.type = newType // updateP
|
}
|
||||||
return true
|
|
||||||
}
|
// thematic break
|
||||||
break
|
ContentState.prototype.updateHr = function (block, marker) {
|
||||||
|
block.type = 'hr'
|
||||||
|
block.text = marker
|
||||||
|
block.children.length = 0
|
||||||
|
const { key } = block
|
||||||
|
this.cursor.start.key = this.cursor.end.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentState.prototype.updateList = function (block, type, marker = '') {
|
||||||
|
const { preferLooseListItem } = this
|
||||||
|
const parent = this.getParent(block)
|
||||||
|
const preSibling = this.getPreSibling(block)
|
||||||
|
const nextSibling = this.getNextSibling(block)
|
||||||
|
const wrapperTag = type === 'order' ? 'ol' : 'ul' // `bullet` => `ul` and `order` => `ol`
|
||||||
|
const { start, end } = this.cursor
|
||||||
|
const startOffset = start.offset
|
||||||
|
const endOffset = end.offset
|
||||||
|
const newBlock = this.createBlock('li')
|
||||||
|
block.children[0].text = block.children[0].text.substring(marker.length)
|
||||||
|
newBlock.listItemType = type
|
||||||
|
newBlock.isLooseListItem = preferLooseListItem
|
||||||
|
|
||||||
|
if (
|
||||||
|
preSibling && preSibling.listType === type && this.checkSameLooseType(preSibling, preferLooseListItem) &&
|
||||||
|
nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)
|
||||||
|
) {
|
||||||
|
this.appendChild(preSibling, newBlock)
|
||||||
|
const partChildren = nextSibling.children.splice(0)
|
||||||
|
partChildren.forEach(b => this.appendChild(preSibling, b))
|
||||||
|
this.removeBlock(nextSibling)
|
||||||
|
this.removeBlock(block)
|
||||||
|
} else if (preSibling && preSibling.type === wrapperTag && this.checkSameLooseType(preSibling, preferLooseListItem)) {
|
||||||
|
this.appendChild(preSibling, newBlock)
|
||||||
|
|
||||||
|
this.removeBlock(block)
|
||||||
|
} else if (nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)) {
|
||||||
|
this.insertBefore(newBlock, nextSibling.children[0])
|
||||||
|
|
||||||
|
this.removeBlock(block)
|
||||||
|
} else if (parent && parent.listType === type && this.checkSameLooseType(parent, preferLooseListItem)) {
|
||||||
|
this.insertBefore(newBlock, block)
|
||||||
|
|
||||||
|
this.removeBlock(block)
|
||||||
|
} else {
|
||||||
|
const listBlock = this.createBlock(wrapperTag)
|
||||||
|
listBlock.listType = type
|
||||||
|
if (wrapperTag === 'ol') {
|
||||||
|
const start = marker.split('.')[0]
|
||||||
|
listBlock.start = /^\d+$/.test(start) ? start : 1
|
||||||
|
}
|
||||||
|
this.appendChild(listBlock, newBlock)
|
||||||
|
this.insertBefore(listBlock, block)
|
||||||
|
this.removeBlock(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
this.appendChild(newBlock, block)
|
||||||
|
|
||||||
|
const key = block.children[0].key
|
||||||
|
this.cursor = {
|
||||||
|
start: {
|
||||||
|
key,
|
||||||
|
offset: Math.max(0, startOffset - marker.length)
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
key,
|
||||||
|
offset: Math.max(0, endOffset - marker.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
|
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
|
||||||
@ -97,7 +175,7 @@ const updateCtrl = ContentState => {
|
|||||||
|
|
||||||
checkbox.checked = checked
|
checkbox.checked = checked
|
||||||
this.insertBefore(checkbox, block)
|
this.insertBefore(checkbox, block)
|
||||||
block.text = block.text.substring(marker.length)
|
block.children[0].text = block.children[0].text.substring(marker.length)
|
||||||
parent.listItemType = 'task'
|
parent.listItemType = 'task'
|
||||||
parent.isLooseListItem = preferLooseListItem
|
parent.isLooseListItem = preferLooseListItem
|
||||||
|
|
||||||
@ -146,102 +224,47 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle task list item checkbox click
|
ContentState.prototype.updateHeader = function (block, header, text) {
|
||||||
ContentState.prototype.listItemCheckBoxClick = function (checkbox) {
|
const newType = `h${header.length}`
|
||||||
const { checked, id } = checkbox
|
if (block.type !== newType) {
|
||||||
const block = this.getBlock(id)
|
block.type = newType
|
||||||
block.checked = checked
|
block.text = text
|
||||||
this.render()
|
block.children.length = 0
|
||||||
}
|
|
||||||
|
|
||||||
ContentState.prototype.checkSameLooseType = function (list, isLooseType) {
|
|
||||||
return list.children[0].isLooseListItem === isLooseType
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentState.prototype.updateList = function (block, type, marker = '') {
|
|
||||||
const { preferLooseListItem } = this
|
|
||||||
const parent = this.getParent(block)
|
|
||||||
const preSibling = this.getPreSibling(block)
|
|
||||||
const nextSibling = this.getNextSibling(block)
|
|
||||||
const wrapperTag = type === 'order' ? 'ol' : 'ul' // `bullet` => `ul` and `order` => `ol`
|
|
||||||
const newText = block.text.substring(marker.length)
|
|
||||||
const { start, end } = this.cursor
|
|
||||||
const startOffset = start.offset
|
|
||||||
const endOffset = end.offset
|
|
||||||
const newBlock = this.createBlockLi(newText, block.type)
|
|
||||||
newBlock.listItemType = type
|
|
||||||
newBlock.isLooseListItem = preferLooseListItem
|
|
||||||
|
|
||||||
if (
|
|
||||||
preSibling && preSibling.listType === type && this.checkSameLooseType(preSibling, preferLooseListItem) &&
|
|
||||||
nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)
|
|
||||||
) {
|
|
||||||
this.appendChild(preSibling, newBlock)
|
|
||||||
const partChildren = nextSibling.children.splice(0)
|
|
||||||
partChildren.forEach(b => this.appendChild(preSibling, b))
|
|
||||||
this.removeBlock(nextSibling)
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else if (preSibling && preSibling.type === wrapperTag && this.checkSameLooseType(preSibling, preferLooseListItem)) {
|
|
||||||
this.appendChild(preSibling, newBlock)
|
|
||||||
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else if (nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)) {
|
|
||||||
this.insertBefore(newBlock, nextSibling.children[0])
|
|
||||||
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else if (parent && parent.listType === type && this.checkSameLooseType(parent, preferLooseListItem)) {
|
|
||||||
this.insertBefore(newBlock, block)
|
|
||||||
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else {
|
|
||||||
block.type = wrapperTag
|
|
||||||
block.listType = type // `bullet` or `order`
|
|
||||||
block.text = ''
|
|
||||||
if (wrapperTag === 'ol') {
|
|
||||||
const start = marker.split('.')[0]
|
|
||||||
block.start = /^\d+$/.test(start) ? start : 1
|
|
||||||
}
|
|
||||||
this.appendChild(block, newBlock)
|
|
||||||
}
|
}
|
||||||
|
this.cursor.start.key = this.cursor.end.key = block.key
|
||||||
const key = newBlock.type === 'li' ? newBlock.children[0].key : newBlock.key
|
|
||||||
this.cursor = {
|
|
||||||
start: {
|
|
||||||
key,
|
|
||||||
offset: Math.max(0, startOffset - marker.length)
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
key,
|
|
||||||
offset: Math.max(0, endOffset - marker.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newBlock.children[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateBlockQuote = function (block) {
|
ContentState.prototype.updateBlockQuote = function (block) {
|
||||||
const newText = block.text.substring(1).trim()
|
block.children[0].text = block.children[0].text.substring(1).trim()
|
||||||
const newPblock = this.createBlock('p', newText)
|
const quoteBlock = this.createBlock('blockquote')
|
||||||
block.type = 'blockquote'
|
this.insertBefore(quoteBlock, block)
|
||||||
block.text = ''
|
this.removeBlock(block)
|
||||||
this.appendChild(block, newPblock)
|
this.appendChild(quoteBlock, block)
|
||||||
|
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
const key = newPblock.key
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: {
|
||||||
key,
|
key: start.key,
|
||||||
offset: start.offset - 1
|
offset: start.offset - 1
|
||||||
},
|
},
|
||||||
end: {
|
end: {
|
||||||
key,
|
key: end.key,
|
||||||
offset: end.offset - 1
|
offset: end.offset - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// thematic break
|
ContentState.prototype.updateToParagraph = function (block) {
|
||||||
ContentState.prototype.updateHr = function (block, marker) {
|
const newType = 'p'
|
||||||
block.type = 'hr'
|
if (block.type !== newType) {
|
||||||
|
block.type = newType // updateP
|
||||||
|
const newLine = this.createBlock('span', block.text)
|
||||||
|
this.appendChild(block, newLine)
|
||||||
|
block.text = ''
|
||||||
|
this.cursor.start.key = this.cursor.end.key = newLine.key
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateState = function (event) {
|
ContentState.prototype.updateState = function (event) {
|
||||||
@ -321,9 +344,12 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
// remove temp block which generated by operation on code block
|
// remove temp block which generated by operation on code block
|
||||||
if (block && block.key !== oldKey) {
|
if (block && block.key !== oldKey) {
|
||||||
const oldBlock = this.getBlock(oldKey)
|
let oldBlock = this.getBlock(oldKey)
|
||||||
if (oldBlock) this.codeBlockUpdate(oldBlock)
|
if (oldBlock) this.codeBlockUpdate(oldBlock)
|
||||||
if (oldBlock && oldBlock.temp) {
|
if (oldBlock && oldBlock.type === 'span') {
|
||||||
|
oldBlock = this.getParent(oldBlock)
|
||||||
|
}
|
||||||
|
if (oldBlock && oldBlock.temp && oldBlock.type === 'p') {
|
||||||
if (oldBlock.text || oldBlock.children.length) {
|
if (oldBlock.text || oldBlock.children.length) {
|
||||||
delete oldBlock.temp
|
delete oldBlock.temp
|
||||||
} else {
|
} else {
|
||||||
@ -380,9 +406,8 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.cursor = lastCursor = { start, end }
|
this.cursor = lastCursor = { start, end }
|
||||||
|
|
||||||
const checkMarkedUpdate = this.checkNeedRender(block)
|
const checkMarkedUpdate = this.checkNeedRender(block)
|
||||||
const checkInlineUpdate = this.checkInlineUpdate(block)
|
const checkInlineUpdate = this.isCollapse() && this.checkInlineUpdate(block)
|
||||||
|
|
||||||
if (checkMarkedUpdate || checkInlineUpdate || needRender) {
|
if (checkMarkedUpdate || checkInlineUpdate || needRender) {
|
||||||
this.render()
|
this.render()
|
||||||
|
@ -33,6 +33,20 @@ h6.ag-active::before {
|
|||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-paragraph:empty::after,
|
||||||
|
.ag-line:empty:after {
|
||||||
|
content: '\200B'
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-line {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-hard-line-break::after {
|
||||||
|
content: '↓';
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
|
||||||
*::selection, .ag-selection {
|
*::selection, .ag-selection {
|
||||||
background: #E4E7ED;
|
background: #E4E7ED;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
|
@ -46,7 +46,6 @@ class Aganippe {
|
|||||||
this.ensureContainerDiv()
|
this.ensureContainerDiv()
|
||||||
const { container, contentState, eventCenter } = this
|
const { container, contentState, eventCenter } = this
|
||||||
contentState.stateRender.setContainer(container.children[0])
|
contentState.stateRender.setContainer(container.children[0])
|
||||||
contentState.render()
|
|
||||||
|
|
||||||
eventCenter.subscribe('editEmoji', throttle(this.subscribeEditEmoji.bind(this), 200))
|
eventCenter.subscribe('editEmoji', throttle(this.subscribeEditEmoji.bind(this), 200))
|
||||||
this.dispatchEditEmoji()
|
this.dispatchEditEmoji()
|
||||||
|
@ -62,9 +62,13 @@ class StateRender {
|
|||||||
const type = block.type === 'hr' ? 'p' : block.type
|
const type = block.type === 'hr' ? 'p' : block.type
|
||||||
const isActive = activeBlocks.some(b => b.key === block.key) || block.key === cursor.start.key
|
const isActive = activeBlocks.some(b => b.key === block.key) || block.key === cursor.start.key
|
||||||
|
|
||||||
let blockSelector = isActive
|
let blockSelector = `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}`
|
||||||
? `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}.${CLASS_OR_ID['AG_ACTIVE']}`
|
if (isActive) {
|
||||||
: `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}`
|
blockSelector += `.${CLASS_OR_ID['AG_ACTIVE']}`
|
||||||
|
}
|
||||||
|
if (type === 'span') {
|
||||||
|
blockSelector += `.${CLASS_OR_ID['AG_LINE']}`
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
attrs: {},
|
attrs: {},
|
||||||
@ -134,17 +138,15 @@ class StateRender {
|
|||||||
if (block.type === 'ol') {
|
if (block.type === 'ol') {
|
||||||
Object.assign(data.attrs, { start: block.start })
|
Object.assign(data.attrs, { start: block.start })
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(blockSelector, data, block.children.map(child => renderBlock(child)))
|
return h(blockSelector, data, block.children.map(child => renderBlock(child)))
|
||||||
} else {
|
} else {
|
||||||
// highlight search key in block
|
// highlight search key in block
|
||||||
const highlights = matches.filter(m => m.key === block.key)
|
const highlights = matches.filter(m => m.key === block.key)
|
||||||
let children = block.text
|
const { text } = block
|
||||||
? tokenizer(block.text, highlights).reduce((acc, token) => {
|
let children = ''
|
||||||
const chunk = this[token.type](h, cursor, block, token)
|
if (text) {
|
||||||
return Array.isArray(chunk) ? [...acc, ...chunk] : [...acc, chunk]
|
children = tokenizer(text, highlights).reduce((acc, token) => [...acc, ...this[token.type](h, cursor, block, token)], [])
|
||||||
}, [])
|
}
|
||||||
: [ h(LOWERCASE_TAGS.br) ]
|
|
||||||
|
|
||||||
if (/th|td/.test(block.type)) {
|
if (/th|td/.test(block.type)) {
|
||||||
const { align } = block
|
const { align } = block
|
||||||
@ -207,8 +209,8 @@ class StateRender {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const newVdom = h(selector, children)
|
const newVdom = h(selector, children)
|
||||||
const root = document.querySelector(selector) || this.container
|
const rootDom = document.querySelector(selector) || this.container
|
||||||
const oldVdom = toVNode(root)
|
const oldVdom = toVNode(rootDom)
|
||||||
|
|
||||||
patch(oldVdom, newVdom)
|
patch(oldVdom, newVdom)
|
||||||
}
|
}
|
||||||
@ -233,13 +235,25 @@ class StateRender {
|
|||||||
['tail_header'] (h, cursor, block, token, outerClass) {
|
['tail_header'] (h, cursor, block, token, outerClass) {
|
||||||
const className = this.getClassName(outerClass, block, token, cursor)
|
const className = this.getClassName(outerClass, block, token, cursor)
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
|
const content = this.highlight(h, block, start, end, token)
|
||||||
if (/^h\d$/.test(block.type)) {
|
if (/^h\d$/.test(block.type)) {
|
||||||
const content = this.highlight(h, block, start, end, token)
|
|
||||||
return [
|
return [
|
||||||
h(`span.${className}`, content)
|
h(`span.${className}`, content)
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
return this.highlight(h, block, start, end, token)
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
['hard_line_break'] (h, cursor, block, token, outerClass) {
|
||||||
|
const className = CLASS_OR_ID['AG_HARD_LINE_BREAK']
|
||||||
|
const content = [ token.spaces ]
|
||||||
|
if (block.type === 'span' && block.nextSibling) {
|
||||||
|
return [
|
||||||
|
h(`span.${className}`, content)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,6 +323,7 @@ class StateRender {
|
|||||||
h(`span.${className}.${CLASS_OR_ID['AG_REMOVE']}`, endMarker)
|
h(`span.${className}.${CLASS_OR_ID['AG_REMOVE']}`, endMarker)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// change text to highlight vdom
|
// change text to highlight vdom
|
||||||
highlight (h, block, rStart, rEnd, token) {
|
highlight (h, block, rStart, rEnd, token) {
|
||||||
const { text } = block
|
const { text } = block
|
||||||
@ -668,8 +683,9 @@ class StateRender {
|
|||||||
const className = CLASS_OR_ID['AG_HTML_TAG']
|
const className = CLASS_OR_ID['AG_HTML_TAG']
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
const tag = this.highlight(h, block, start, end, token)
|
const tag = this.highlight(h, block, start, end, token)
|
||||||
|
const isBr = /<br(?=\s|\/|>)/.test(token.tag)
|
||||||
return [
|
return [
|
||||||
h(`span.${className}`, tag)
|
h(`span.${className}`, isBr ? [...tag, h('br')] : tag)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
|
|||||||
pending = ''
|
pending = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beginRules) {
|
if (beginRules && pos === 0) {
|
||||||
const beginR = ['header', 'hr', 'code_fense', 'display_math']
|
const beginR = ['header', 'hr', 'code_fense', 'display_math']
|
||||||
|
|
||||||
for (const ruleName of beginR) {
|
for (const ruleName of beginR) {
|
||||||
@ -350,6 +350,25 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
|
|||||||
pos = pos + autoLTo[0].length
|
pos = pos + autoLTo[0].length
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// hard line break
|
||||||
|
const hardTo = inlineRules['hard_line_break'].exec(src)
|
||||||
|
if (hardTo && top) {
|
||||||
|
const len = hardTo[0].length
|
||||||
|
pushPending()
|
||||||
|
tokens.push({
|
||||||
|
type: 'hard_line_break',
|
||||||
|
spaces: hardTo[1],
|
||||||
|
parent: tokens,
|
||||||
|
range: {
|
||||||
|
start: pos,
|
||||||
|
end: pos + len
|
||||||
|
}
|
||||||
|
})
|
||||||
|
src = src.substring(len)
|
||||||
|
pos += len
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// tail header
|
// tail header
|
||||||
const tailTo = inlineRules['tail_header'].exec(src)
|
const tailTo = inlineRules['tail_header'].exec(src)
|
||||||
if (tailTo && top) {
|
if (tailTo && top) {
|
||||||
@ -442,13 +461,16 @@ export const generator = tokens => {
|
|||||||
case 'auto_link':
|
case 'auto_link':
|
||||||
result += token.href
|
result += token.href
|
||||||
break
|
break
|
||||||
case 'html-image':
|
case 'html_image':
|
||||||
case 'html_tag':
|
case 'html_tag':
|
||||||
result += token.tag
|
result += token.tag
|
||||||
break
|
break
|
||||||
case 'tail_header':
|
case 'tail_header':
|
||||||
result += token.marker
|
result += token.marker
|
||||||
break
|
break
|
||||||
|
case 'hard_line_break':
|
||||||
|
result += token.spaces
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`unhandle token type: ${token.type}`)
|
throw new Error(`unhandle token type: ${token.type}`)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ export const inlineRules = {
|
|||||||
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
||||||
'a_link': /^(<a[\s\S]*href\s*=\s*("|')(.+?)\2(?=\s|>)[\s\S]*(?!\\)>)([\s\S]*)(<\/a>)/, // can nest
|
'a_link': /^(<a[\s\S]*href\s*=\s*("|')(.+?)\2(?=\s|>)[\s\S]*(?!\\)>)([\s\S]*)(<\/a>)/, // can nest
|
||||||
'html_image': /^(<img\s([\s\S]*?src[\s\S]+?)(?!\\)>)/,
|
'html_image': /^(<img\s([\s\S]*?src[\s\S]+?)(?!\\)>)/,
|
||||||
'html_tag': /^(<!--[\s\S]*?-->|<\/?[a-zA-Z\d-]+[\s\S]*?(?!\\)>)/
|
'html_tag': /^(<!--[\s\S]*?-->|<\/?[a-zA-Z\d-]+[\s\S]*?(?!\\)>)/,
|
||||||
|
'hard_line_break': /^(\s{2,})$/
|
||||||
}
|
}
|
||||||
/* eslint-enable no-useless-escape */
|
/* eslint-enable no-useless-escape */
|
||||||
|
@ -720,7 +720,6 @@ class Selection {
|
|||||||
const { start, end } = cursorRange
|
const { start, end } = cursorRange
|
||||||
const startParagraph = document.querySelector(`#${start.key}`)
|
const startParagraph = document.querySelector(`#${start.key}`)
|
||||||
const endParagraph = document.querySelector(`#${end.key}`)
|
const endParagraph = document.querySelector(`#${end.key}`)
|
||||||
|
|
||||||
const getNodeAndOffset = (node, offset) => {
|
const getNodeAndOffset = (node, offset) => {
|
||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3) {
|
||||||
return {
|
return {
|
||||||
|
@ -24,6 +24,12 @@ class ExportMarkdown {
|
|||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case 'p':
|
case 'p':
|
||||||
|
this.insertLineBreak(result, indent, true)
|
||||||
|
result.push(this.translateBlocks2Markdown(block.children, indent))
|
||||||
|
break
|
||||||
|
case 'span':
|
||||||
|
result.push(this.normalizeParagraphText(block, indent))
|
||||||
|
break
|
||||||
case 'hr':
|
case 'hr':
|
||||||
this.insertLineBreak(result, indent, true)
|
this.insertLineBreak(result, indent, true)
|
||||||
result.push(this.normalizeParagraphText(block, indent))
|
result.push(this.normalizeParagraphText(block, indent))
|
||||||
|
@ -154,6 +154,8 @@ class ExportHTML {
|
|||||||
$(removeClassNames.join(', ')).remove()
|
$(removeClassNames.join(', ')).remove()
|
||||||
$(`.${CLASS_OR_ID['AG_ACTIVE']}`).removeClass(CLASS_OR_ID['AG_ACTIVE'])
|
$(`.${CLASS_OR_ID['AG_ACTIVE']}`).removeClass(CLASS_OR_ID['AG_ACTIVE'])
|
||||||
$(`[data-role=hr]`).replaceWith('<hr>')
|
$(`[data-role=hr]`).replaceWith('<hr>')
|
||||||
|
|
||||||
|
// replace the `emoji text` with actual emoji
|
||||||
const emojis = $(`span.${CLASS_OR_ID['AG_EMOJI_MARKED_TEXT']}`)
|
const emojis = $(`span.${CLASS_OR_ID['AG_EMOJI_MARKED_TEXT']}`)
|
||||||
if (emojis.length > 0) {
|
if (emojis.length > 0) {
|
||||||
emojis.each((i, e) => {
|
emojis.each((i, e) => {
|
||||||
@ -173,9 +175,9 @@ class ExportHTML {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// change `data-href` to `href` attribute
|
// change `data-href` to `href` attribute, so the anchor can be clicked.
|
||||||
const anchors = $(`a[data-href]`)
|
const anchors = $(`a[data-href]`)
|
||||||
if (anchors.length > 0) {
|
if (anchors.length) {
|
||||||
anchors.each((i, a) => {
|
anchors.each((i, a) => {
|
||||||
const anchor = $(a)
|
const anchor = $(a)
|
||||||
const href = anchor.attr('data-href')
|
const href = anchor.attr('data-href')
|
||||||
@ -184,10 +186,32 @@ class ExportHTML {
|
|||||||
anchor.attr('target', '_blank')
|
anchor.attr('target', '_blank')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// soft line break render to html is a space, and hard line break render to html is `<br>`
|
||||||
|
const paragraphs = $(`p.${CLASS_OR_ID['AG_PARAGRAPH']}`)
|
||||||
|
if (paragraphs.length) {
|
||||||
|
paragraphs.each((i, p) => {
|
||||||
|
const paragraph = $(p)
|
||||||
|
const children = paragraph.children()
|
||||||
|
const len = children.length
|
||||||
|
children.each((i, c) => {
|
||||||
|
const child = $(c)
|
||||||
|
child.removeClass(CLASS_OR_ID['AG_LINE'])
|
||||||
|
if (i < len - 1) { // no need to handle the last line
|
||||||
|
const hardLineBreak = $(`.${CLASS_OR_ID['AG_HARD_LINE_BREAK']}`, child)
|
||||||
|
if (hardLineBreak.length) {
|
||||||
|
hardLineBreak.removeClass(CLASS_OR_ID['AG_HARD_LINE_BREAK'])
|
||||||
|
hardLineBreak.append('<br/>')
|
||||||
|
} else {
|
||||||
|
$('<span> </span>').appendTo(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return $('body').html()
|
return $('body').html()
|
||||||
.replace(/<span class="ag-html-tag">([\s\S]+?)<\/span>/g, (m, p1) => {
|
.replace(/<span class="ag-html-tag">([\s\S]+?)<\/span>/g, (m, p1) => {
|
||||||
if (/script|style|title/.test(p1)) return p1
|
return /script|style|title/.test(p1) ? p1 : unescapeHtml(p1)
|
||||||
else return unescapeHtml(p1)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import { turndownConfig, CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7 } fro
|
|||||||
|
|
||||||
const turndownPluginGfm = require('turndown-plugin-gfm')
|
const turndownPluginGfm = require('turndown-plugin-gfm')
|
||||||
|
|
||||||
|
const LINE_BREAKS = /\n/
|
||||||
|
|
||||||
// turn html to markdown
|
// turn html to markdown
|
||||||
const turndownService = new TurndownService(turndownConfig)
|
const turndownService = new TurndownService(turndownConfig)
|
||||||
const gfm = turndownPluginGfm.gfm
|
const gfm = turndownPluginGfm.gfm
|
||||||
@ -103,7 +105,6 @@ const importRegister = ContentState => {
|
|||||||
switch (child.nodeName) {
|
switch (child.nodeName) {
|
||||||
case 'th':
|
case 'th':
|
||||||
case 'td':
|
case 'td':
|
||||||
case 'p':
|
|
||||||
case 'h1':
|
case 'h1':
|
||||||
case 'h2':
|
case 'h2':
|
||||||
case 'h3':
|
case 'h3':
|
||||||
@ -111,10 +112,6 @@ const importRegister = ContentState => {
|
|||||||
case 'h5':
|
case 'h5':
|
||||||
case 'h6':
|
case 'h6':
|
||||||
const textValue = child.childNodes.length ? child.childNodes[0].value : ''
|
const textValue = child.childNodes.length ? child.childNodes[0].value : ''
|
||||||
if (checkIsHTML(textValue) && child.nodeName === 'p') {
|
|
||||||
travel(parent, child.childNodes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
const match = /\d/.exec(child.nodeName)
|
const match = /\d/.exec(child.nodeName)
|
||||||
value = match ? '#'.repeat(+match[0]) + ` ${textValue}` : textValue
|
value = match ? '#'.repeat(+match[0]) + ` ${textValue}` : textValue
|
||||||
block = this.createBlock(child.nodeName, value)
|
block = this.createBlock(child.nodeName, value)
|
||||||
@ -134,6 +131,17 @@ const importRegister = ContentState => {
|
|||||||
this.appendChild(parent, block)
|
this.appendChild(parent, block)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
value = child.childNodes.length ? child.childNodes[0].value : ''
|
||||||
|
if (checkIsHTML(value)) {
|
||||||
|
travel(parent, child.childNodes)
|
||||||
|
} else {
|
||||||
|
block = this.createBlock('p')
|
||||||
|
travel(block, child.childNodes)
|
||||||
|
this.appendChild(parent, block)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
case 'table':
|
case 'table':
|
||||||
const toolBar = this.createToolBar(TABLE_TOOLS, 'table')
|
const toolBar = this.createToolBar(TABLE_TOOLS, 'table')
|
||||||
const table = this.createBlock('table')
|
const table = this.createBlock('table')
|
||||||
@ -243,13 +251,23 @@ const importRegister = ContentState => {
|
|||||||
this.appendChild(parent, block)
|
this.appendChild(parent, block)
|
||||||
} else {
|
} else {
|
||||||
// not html block
|
// not html block
|
||||||
block = this.createBlock('p', fragment)
|
block = this.createBlockP(fragment)
|
||||||
this.appendChild(parent, block)
|
this.appendChild(parent, block)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else if (parentNode.nodeName === 'li') {
|
||||||
block = this.createBlock('p', value.replace(/^\s+/, '')) // fix: #153
|
block = this.createBlock('p')
|
||||||
|
// fix: #153
|
||||||
|
const lines = value.replace(/^\s+/, '').split(LINE_BREAKS).map(line => this.createBlock('span', line))
|
||||||
|
for (const line of lines) {
|
||||||
|
this.appendChild(block, line)
|
||||||
|
}
|
||||||
this.appendChild(parent, block)
|
this.appendChild(parent, block)
|
||||||
|
} else if (parentNode.nodeName === 'p') {
|
||||||
|
const lines = value.split(LINE_BREAKS).map(line => this.createBlock('span', line))
|
||||||
|
for (const line of lines) {
|
||||||
|
this.appendChild(parent, line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -264,7 +282,7 @@ const importRegister = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
travel(rootState, childNodes)
|
travel(rootState, childNodes)
|
||||||
return rootState.children.length ? rootState.children : [this.createBlock()]
|
return rootState.children.length ? rootState.children : [this.createBlockP()]
|
||||||
}
|
}
|
||||||
// transform `paste's text/html data` to content state blocks.
|
// transform `paste's text/html data` to content state blocks.
|
||||||
ContentState.prototype.html2State = function (html) {
|
ContentState.prototype.html2State = function (html) {
|
||||||
|
Loading…
Reference in New Issue
Block a user