mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 15:23:03 +08:00

* opti: container block preview * remove unused codes * rewrite createBlock method * remove ag-line classname * just push codes * hand enter + shift in paragraph * update import markdown and export markdown * update part updateCtrl * update indent code block * auto indent when press shift + enter * update thematic break * update inline syntax update reg * update list and task list * update atx heading and setext heading * update paragraph * update block quote * adjust cursor in heading * update codes * paragraph turn into feature check * check copy paste * update turn into * fix: delete last # error * fix: turn setext heading to atx heading error * fix: delete thematic break error * paste and copy * workarond turndown to support soft line break * fix: unable create table * modify export markdown * modify test markdown * fix: cursor error when update blockquote * readd cursor check when dispatch changes * fix: inline math create a lot extra char * add code cache clear after each render * fallback to prismjs2
597 lines
19 KiB
JavaScript
597 lines
19 KiB
JavaScript
import { tokenizer } from '../parser/'
|
|
import { conflict } from '../utils'
|
|
import { CLASS_OR_ID } from '../config'
|
|
|
|
const INLINE_UPDATE_FRAGMENTS = [
|
|
'(?:^|\n) {0,3}([*+-] {1,4})', // Bullet list
|
|
'(?:^|\n)(\\[[x ]{1}\\] {1,4})', // Task list
|
|
'(?:^|\n) {0,3}(\\d{1,9}(?:\\.|\\)) {1,4})', // Order list
|
|
'(?:^|\n) {0,3}(#{1,6})(?=\\s{1,}|$)', // ATX headings
|
|
'^(?:[\\s\\S]+?)\\n {0,3}(\\={3,}|\\-{3,})(?= {1,}|$)', // Setext headings **match from beginning**
|
|
'(?:^|\n) {0,3}(>).+', // Block quote
|
|
'^( {4,})', // Indent code **match from beginning**
|
|
'(?:^|\n) {0,3}((?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*)$' // Thematic break
|
|
]
|
|
|
|
const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')
|
|
|
|
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
|
|
checkbox.classList.toggle(CLASS_OR_ID['AG_CHECKBOX_CHECKED'])
|
|
}
|
|
|
|
ContentState.prototype.checkSameMarkerOrDelimiter = function (list, markerOrDelimiter) {
|
|
if (!/ol|ul/.test(list.type)) return false
|
|
return list.children[0].bulletMarkerOrDelimiter === markerOrDelimiter
|
|
}
|
|
|
|
ContentState.prototype.checkNeedRender = function (cursor = this.cursor) {
|
|
const { labels } = this.stateRender
|
|
const { start: cStart, end: cEnd, anchor, focus } = cursor
|
|
const startBlock = this.getBlock(cStart ? cStart.key : anchor.key)
|
|
const endBlock = this.getBlock(cEnd ? cEnd.key : focus.key)
|
|
const startOffset = cStart ? cStart.offset : anchor.offset
|
|
const endOffset = cEnd ? cEnd.offset : focus.offset
|
|
const NO_NEED_TOKEN_REG = /text|hard_line_break|soft_line_break/
|
|
|
|
for (const token of tokenizer(startBlock.text, undefined, undefined, labels)) {
|
|
if (NO_NEED_TOKEN_REG.test(token.type)) continue
|
|
const { start, end } = token.range
|
|
const textLen = startBlock.text.length
|
|
if (
|
|
conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [startOffset, startOffset])
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
for (const token of tokenizer(endBlock.text, undefined, undefined, labels)) {
|
|
if (NO_NEED_TOKEN_REG.test(token.type)) continue
|
|
const { start, end } = token.range
|
|
const textLen = endBlock.text.length
|
|
if (
|
|
conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [endOffset, endOffset])
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* block must be span block.
|
|
*/
|
|
ContentState.prototype.checkInlineUpdate = function (block) {
|
|
// table cell can not have blocks in it
|
|
if (/th|td|figure/.test(block.type)) return false
|
|
if (/codeLine|languageInput/.test(block.functionType)) return false
|
|
|
|
let line = null
|
|
const { text } = block
|
|
if (block.type === 'span') {
|
|
line = block
|
|
block = this.getParent(block)
|
|
}
|
|
const listItem = this.getParent(block)
|
|
const [
|
|
match, bullet, tasklist, order, atxHeader,
|
|
setextHeader, blockquote, indentCode, hr
|
|
] = text.match(INLINE_UPDATE_REG) || []
|
|
|
|
switch (true) {
|
|
case (!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
|
return this.updateHr(block, hr, line)
|
|
|
|
case !!bullet:
|
|
return this.updateList(block, 'bullet', bullet, line)
|
|
|
|
// only `bullet` list item can be update to `task` list item
|
|
case !!tasklist && listItem && listItem.listItemType === 'bullet':
|
|
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
|
|
|
case !!order:
|
|
return this.updateList(block, 'order', order, line)
|
|
|
|
case !!atxHeader:
|
|
return this.updateAtxHeader(block, atxHeader, line)
|
|
|
|
case !!setextHeader:
|
|
return this.updateSetextHeader(block, setextHeader, line)
|
|
|
|
case !!blockquote:
|
|
return this.updateBlockQuote(block, line)
|
|
|
|
case !!indentCode:
|
|
return this.updateIndentCode(block, line)
|
|
|
|
case !match:
|
|
default:
|
|
return this.updateToParagraph(block, line)
|
|
}
|
|
}
|
|
|
|
// Thematic break
|
|
ContentState.prototype.updateHr = function (block, marker, line) {
|
|
// If the block is already thematic break, no need to update.
|
|
if (block.type === 'hr') return null
|
|
const text = line.text
|
|
const lines = text.split('\n')
|
|
const preParagraphLines = []
|
|
let thematicLine = ''
|
|
const postParagraphLines = []
|
|
let thematicLineHasPushed = false
|
|
|
|
for (const l of lines) {
|
|
if (/ {0,3}(?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*$/.test(l) && !thematicLineHasPushed) {
|
|
thematicLine = l
|
|
thematicLineHasPushed = true
|
|
} else if (!thematicLineHasPushed) {
|
|
preParagraphLines.push(l)
|
|
} else {
|
|
postParagraphLines.push(l)
|
|
}
|
|
}
|
|
|
|
const thematicBlock = this.createBlock('hr')
|
|
const thematicLineBlock = this.createBlock('span', {
|
|
text: thematicLine,
|
|
functionType: 'thematicBreakLine'
|
|
})
|
|
this.appendChild(thematicBlock, thematicLineBlock)
|
|
this.insertBefore(thematicBlock, block)
|
|
if (preParagraphLines.length) {
|
|
const preBlock = this.createBlockP(preParagraphLines.join('\n'))
|
|
this.insertBefore(preBlock, thematicBlock)
|
|
}
|
|
if (postParagraphLines.length) {
|
|
const postBlock = this.createBlockP(postParagraphLines.join('\n'))
|
|
this.insertAfter(postBlock, thematicBlock)
|
|
}
|
|
|
|
this.removeBlock(block)
|
|
const { start, end } = this.cursor
|
|
const key = thematicBlock.children[0].key
|
|
this.cursor = {
|
|
start: { key, offset: start.offset },
|
|
end: { key, offset: end.offset }
|
|
}
|
|
return thematicBlock
|
|
}
|
|
|
|
ContentState.prototype.updateList = function (block, type, marker = '', line) {
|
|
const cleanMarker = marker ? marker.trim() : null
|
|
const { preferLooseListItem } = this
|
|
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 newListItemBlock = this.createBlock('li')
|
|
const LIST_ITEM_REG = /^ {0,3}(?:[*+-]|\d{1,9}(?:\.|\))) {0,4}/
|
|
const text = line.text
|
|
const lines = text.split('\n')
|
|
|
|
const preParagraphLines = []
|
|
const listItemLines = []
|
|
let isPushedListItemLine = false
|
|
for (const l of lines) {
|
|
if (LIST_ITEM_REG.test(l) && !isPushedListItemLine) {
|
|
listItemLines.push(l.replace(LIST_ITEM_REG, ''))
|
|
isPushedListItemLine = true
|
|
} else if (!isPushedListItemLine) {
|
|
preParagraphLines.push(l)
|
|
} else {
|
|
listItemLines.push(l)
|
|
}
|
|
}
|
|
|
|
const pBlock = this.createBlockP(listItemLines.join('\n'))
|
|
this.insertBefore(pBlock, block)
|
|
|
|
if (preParagraphLines.length > 0) {
|
|
const preParagraphBlock = this.createBlockP(preParagraphLines.join('\n'))
|
|
this.insertBefore(preParagraphBlock, pBlock)
|
|
}
|
|
|
|
this.removeBlock(block)
|
|
|
|
// important!
|
|
block = pBlock
|
|
|
|
const preSibling = this.getPreSibling(block)
|
|
const nextSibling = this.getNextSibling(block)
|
|
newListItemBlock.listItemType = type
|
|
newListItemBlock.isLooseListItem = preferLooseListItem
|
|
|
|
let bulletMarkerOrDelimiter
|
|
if (type === 'order') {
|
|
bulletMarkerOrDelimiter = (cleanMarker && cleanMarker.length >= 2) ? cleanMarker.slice(-1) : '.'
|
|
} else {
|
|
const { bulletListMarker } = this
|
|
bulletMarkerOrDelimiter = marker ? marker.charAt(0) : bulletListMarker
|
|
}
|
|
newListItemBlock.bulletMarkerOrDelimiter = bulletMarkerOrDelimiter
|
|
|
|
// Special cases for CommonMark 264 and 265: Changing the bullet or ordered list delimiter starts a new list.
|
|
// Same list type or new list
|
|
if (
|
|
preSibling &&
|
|
this.checkSameMarkerOrDelimiter(preSibling, bulletMarkerOrDelimiter) &&
|
|
nextSibling &&
|
|
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
|
) {
|
|
this.appendChild(preSibling, newListItemBlock)
|
|
const partChildren = nextSibling.children.splice(0)
|
|
partChildren.forEach(b => this.appendChild(preSibling, b))
|
|
this.removeBlock(nextSibling)
|
|
this.removeBlock(block)
|
|
const isLooseListItem = preSibling.children.some(c => c.isLooseListItem)
|
|
preSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
|
} else if (
|
|
preSibling &&
|
|
this.checkSameMarkerOrDelimiter(preSibling, bulletMarkerOrDelimiter)
|
|
) {
|
|
this.appendChild(preSibling, newListItemBlock)
|
|
this.removeBlock(block)
|
|
const isLooseListItem = preSibling.children.some(c => c.isLooseListItem)
|
|
preSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
|
} else if (
|
|
nextSibling &&
|
|
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
|
) {
|
|
this.insertBefore(newListItemBlock, nextSibling.children[0])
|
|
this.removeBlock(block)
|
|
const isLooseListItem = nextSibling.children.some(c => c.isLooseListItem)
|
|
nextSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
|
} else {
|
|
// Create a new list when changing list type, bullet or list delimiter
|
|
const listBlock = this.createBlock(wrapperTag, {
|
|
listType: type
|
|
})
|
|
|
|
if (wrapperTag === 'ol') {
|
|
const start = cleanMarker ? cleanMarker.slice(0, -1) : 1
|
|
listBlock.start = /^\d+$/.test(start) ? start : 1
|
|
}
|
|
this.appendChild(listBlock, newListItemBlock)
|
|
this.insertBefore(listBlock, block)
|
|
this.removeBlock(block)
|
|
}
|
|
|
|
// key point
|
|
this.appendChild(newListItemBlock, block)
|
|
const TASK_LIST_REG = /^\[[x ]\] {1,4}/i
|
|
const listItemText = block.children[0].text
|
|
const { key } = block.children[0]
|
|
const delta = marker.length + preParagraphLines.join('\n').length + 1
|
|
this.cursor = {
|
|
start: {
|
|
key,
|
|
offset: Math.max(0, startOffset - delta)
|
|
},
|
|
end: {
|
|
key,
|
|
offset: Math.max(0, endOffset - delta)
|
|
}
|
|
}
|
|
if (TASK_LIST_REG.test(listItemText)) {
|
|
const [,,tasklist,,,,] = listItemText.match(INLINE_UPDATE_REG) || []
|
|
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
|
} else {
|
|
return block
|
|
}
|
|
}
|
|
|
|
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
|
|
const { preferLooseListItem } = this
|
|
const parent = this.getParent(block)
|
|
const grandpa = this.getParent(parent)
|
|
const checked = /\[x\]\s/i.test(marker) // use `i` flag to ignore upper case or lower case
|
|
const checkbox = this.createBlock('input', {
|
|
checked
|
|
})
|
|
const { start, end } = this.cursor
|
|
|
|
this.insertBefore(checkbox, block)
|
|
block.children[0].text = block.children[0].text.substring(marker.length)
|
|
parent.listItemType = 'task'
|
|
parent.isLooseListItem = preferLooseListItem
|
|
|
|
let taskListWrapper
|
|
if (this.isOnlyChild(parent)) {
|
|
grandpa.listType = 'task'
|
|
} else if (this.isFirstChild(parent) || this.isLastChild(parent)) {
|
|
taskListWrapper = this.createBlock('ul', {
|
|
listType: 'task'
|
|
})
|
|
|
|
this.isFirstChild(parent) ? this.insertBefore(taskListWrapper, grandpa) : this.insertAfter(taskListWrapper, grandpa)
|
|
this.removeBlock(parent)
|
|
this.appendChild(taskListWrapper, parent)
|
|
} else {
|
|
taskListWrapper = this.createBlock('ul', {
|
|
listType: 'task'
|
|
})
|
|
|
|
const bulletListWrapper = this.createBlock('ul', {
|
|
listType: 'bullet'
|
|
})
|
|
|
|
let preSibling = this.getPreSibling(parent)
|
|
while (preSibling) {
|
|
this.removeBlock(preSibling)
|
|
if (bulletListWrapper.children.length) {
|
|
const firstChild = bulletListWrapper.children[0]
|
|
this.insertBefore(preSibling, firstChild)
|
|
} else {
|
|
this.appendChild(bulletListWrapper, preSibling)
|
|
}
|
|
preSibling = this.getPreSibling(preSibling)
|
|
}
|
|
|
|
this.removeBlock(parent)
|
|
this.appendChild(taskListWrapper, parent)
|
|
this.insertBefore(taskListWrapper, grandpa)
|
|
this.insertBefore(bulletListWrapper, taskListWrapper)
|
|
}
|
|
|
|
this.cursor = {
|
|
start: {
|
|
key: start.key,
|
|
offset: Math.max(0, start.offset - marker.length)
|
|
},
|
|
end: {
|
|
key: end.key,
|
|
offset: Math.max(0, end.offset - marker.length)
|
|
}
|
|
}
|
|
return taskListWrapper || grandpa
|
|
}
|
|
|
|
// ATX heading doesn't support soft line break and hard line break.
|
|
ContentState.prototype.updateAtxHeader = function (block, header, line) {
|
|
const newType = `h${header.length}`
|
|
const headingStyle = 'atx'
|
|
if (block.type === newType && block.headingStyle === headingStyle) {
|
|
return null
|
|
}
|
|
const text = line.text
|
|
const lines = text.split('\n')
|
|
const preParagraphLines = []
|
|
let atxLine = ''
|
|
const postParagraphLines = []
|
|
let atxLineHasPushed = false
|
|
|
|
for (const l of lines) {
|
|
if (/^ {0,3}#{1,6}(?=\s{1,}|$)/.test(l) && !atxLineHasPushed) {
|
|
atxLine = l
|
|
atxLineHasPushed = true
|
|
} else if (!atxLineHasPushed) {
|
|
preParagraphLines.push(l)
|
|
} else {
|
|
postParagraphLines.push(l)
|
|
}
|
|
}
|
|
|
|
const atxBlock = this.createBlock(newType, {
|
|
headingStyle
|
|
})
|
|
const atxLineBlock = this.createBlock('span', {
|
|
text: atxLine,
|
|
functionType: 'atxLine'
|
|
})
|
|
this.appendChild(atxBlock, atxLineBlock)
|
|
this.insertBefore(atxBlock, block)
|
|
if (preParagraphLines.length) {
|
|
const preBlock = this.createBlockP(preParagraphLines.join('\n'))
|
|
this.insertBefore(preBlock, atxBlock)
|
|
}
|
|
if (postParagraphLines.length) {
|
|
const postBlock = this.createBlockP(postParagraphLines.join('\n'))
|
|
this.insertAfter(postBlock, atxBlock)
|
|
}
|
|
|
|
this.removeBlock(block)
|
|
|
|
const { start, end } = this.cursor
|
|
const key = atxBlock.children[0].key
|
|
this.cursor = {
|
|
start: { key, offset: start.offset },
|
|
end: { key, offset: end.offset }
|
|
}
|
|
return atxBlock
|
|
}
|
|
|
|
ContentState.prototype.updateSetextHeader = function (block, marker, line) {
|
|
const newType = /=/.test(marker) ? 'h1' : 'h2'
|
|
const headingStyle = 'setext'
|
|
if (block.type === newType && block.headingStyle === headingStyle) {
|
|
return null
|
|
}
|
|
|
|
const text = line.text
|
|
const lines = text.split('\n')
|
|
let setextLines = []
|
|
const postParagraphLines = []
|
|
let setextLineHasPushed = false
|
|
|
|
for (const l of lines) {
|
|
if (/^ {0,3}(?:={3,}|-{3,})(?= {1,}|$)/.test(l) && !setextLineHasPushed) {
|
|
setextLineHasPushed = true
|
|
} else if (!setextLineHasPushed) {
|
|
setextLines.push(l)
|
|
} else {
|
|
postParagraphLines.push(l)
|
|
}
|
|
}
|
|
|
|
const setextBlock = this.createBlock(newType, {
|
|
headingStyle,
|
|
marker
|
|
})
|
|
const setextLineBlock = this.createBlock('span', {
|
|
text: setextLines.join('\n'),
|
|
functionType: 'paragraphContent'
|
|
})
|
|
this.appendChild(setextBlock, setextLineBlock)
|
|
this.insertBefore(setextBlock, block)
|
|
|
|
if (postParagraphLines.length) {
|
|
const postBlock = this.createBlockP(postParagraphLines.join('\n'))
|
|
this.insertAfter(postBlock, setextBlock)
|
|
}
|
|
|
|
this.removeBlock(block)
|
|
|
|
const key = setextBlock.children[0].key
|
|
const offset = setextBlock.children[0].text.length
|
|
|
|
this.cursor = {
|
|
start: { key, offset },
|
|
end: { key, offset }
|
|
}
|
|
|
|
return setextBlock
|
|
}
|
|
|
|
ContentState.prototype.updateBlockQuote = function (block, line) {
|
|
const text = line.text
|
|
const lines = text.split('\n')
|
|
const preParagraphLines = []
|
|
let quoteLines = []
|
|
let quoteLinesHasPushed = false
|
|
|
|
for (const l of lines) {
|
|
if (/^ {0,3}>/.test(l) && !quoteLinesHasPushed) {
|
|
quoteLinesHasPushed = true
|
|
quoteLines.push(l.trimStart().substring(1).trimStart())
|
|
} else if (!quoteLinesHasPushed) {
|
|
preParagraphLines.push(l)
|
|
} else {
|
|
quoteLines.push(l)
|
|
}
|
|
}
|
|
let quoteParagraphBlock
|
|
if (/^h\d/.test(block.type)) {
|
|
quoteParagraphBlock = this.createBlock(block.type, {
|
|
headingStyle: block.headingStyle
|
|
})
|
|
if (block.headingStyle === 'setext') {
|
|
quoteParagraphBlock.marker = block.marker
|
|
}
|
|
const headerContent = this.createBlock('span', {
|
|
text: quoteLines.join('\n'),
|
|
functionType: block.headingStyle === 'setext'? 'paragraphContent' : 'atxLine'
|
|
})
|
|
this.appendChild(quoteParagraphBlock, headerContent)
|
|
} else {
|
|
quoteParagraphBlock = this.createBlockP(quoteLines.join('\n'))
|
|
}
|
|
|
|
const quoteBlock = this.createBlock('blockquote')
|
|
this.appendChild(quoteBlock, quoteParagraphBlock)
|
|
this.insertBefore(quoteBlock, block)
|
|
|
|
if (preParagraphLines.length) {
|
|
const preParagraphBlock = this.createBlockP(preParagraphLines.join('\n'))
|
|
this.insertBefore(preParagraphBlock, quoteBlock)
|
|
}
|
|
|
|
this.removeBlock(block)
|
|
|
|
const key = quoteParagraphBlock.children[0].key
|
|
const { start, end } = this.cursor
|
|
this.cursor = {
|
|
start: { key, offset: start.offset - 1 },
|
|
end: { key, offset: end.offset - 1 }
|
|
}
|
|
|
|
return quoteBlock
|
|
}
|
|
|
|
ContentState.prototype.updateIndentCode = function (block, line) {
|
|
const codeBlock = this.createBlock('code', {
|
|
lang: ''
|
|
})
|
|
const inputBlock = this.createBlock('span', {
|
|
functionType: 'languageInput'
|
|
})
|
|
const preBlock = this.createBlock('pre', {
|
|
functionType: 'indentcode',
|
|
lang: ''
|
|
})
|
|
|
|
const text = line ? line.text : block.text
|
|
|
|
const lines = text.split('\n')
|
|
const codeLines = []
|
|
const paragraphLines = []
|
|
let canBeCodeLine = true
|
|
|
|
for (const l of lines) {
|
|
if (/^ {4,}/.test(l) && canBeCodeLine) {
|
|
codeLines.push(l.replace(/^ {4}/, ''))
|
|
} else {
|
|
canBeCodeLine = false
|
|
paragraphLines.push(l)
|
|
}
|
|
}
|
|
codeLines.forEach(text => {
|
|
const codeLine = this.createBlock('span', {
|
|
text,
|
|
functionType: 'codeLine',
|
|
lang: ''
|
|
})
|
|
this.appendChild(codeBlock, codeLine)
|
|
})
|
|
|
|
this.appendChild(preBlock, inputBlock)
|
|
this.appendChild(preBlock, codeBlock)
|
|
this.insertBefore(preBlock, block)
|
|
|
|
if (paragraphLines.length > 0 && line) {
|
|
const newLine = this.createBlock('span', {
|
|
text: paragraphLines.join('\n')
|
|
})
|
|
this.insertBefore(newLine, line)
|
|
this.removeBlock(line)
|
|
} else {
|
|
this.removeBlock(block)
|
|
}
|
|
|
|
const key = codeBlock.children[0].key
|
|
const { start, end } = this.cursor
|
|
this.cursor = {
|
|
start: { key, offset: start.offset - 4 },
|
|
end: { key, offset: end.offset - 4 }
|
|
}
|
|
return preBlock
|
|
}
|
|
|
|
ContentState.prototype.updateToParagraph = function (block, line) {
|
|
if (/^h\d$/.test(block.type) && block.headingStyle === 'setext') {
|
|
return null
|
|
}
|
|
|
|
const newType = 'p'
|
|
if (block.type !== newType) {
|
|
const newBlock = this.createBlockP(line.text)
|
|
this.insertBefore(newBlock, block)
|
|
this.removeBlock(block)
|
|
const { start, end } = this.cursor
|
|
const key = newBlock.children[0].key
|
|
this.cursor = {
|
|
start: { key, offset: start.offset },
|
|
end: { key, offset: end.offset }
|
|
}
|
|
return block
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
export default updateCtrl
|