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:
冉四夕 2018-04-22 13:17:46 +08:00 committed by GitHub
parent ce6efbe5f5
commit cfd0d0a2fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 560 additions and 346 deletions

19
.github/CHANGELOG.md vendored
View File

@ -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
**:notebook_with_decorative_cover:Note**

View 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

View File

@ -60,6 +60,7 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'AG_HIDE',
'AG_WARN',
'AG_PARAGRAPH', // => 'ag-paragraph'
'AG_LINE',
'AG_ACTIVE',
'AG_EDITOR_ID',
'AG_FLOAT_BOX_ID',
@ -108,7 +109,8 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'AG_LOOSE_LIST_ITEM',
'AG_TIGHT_LIST_ITEM',
'AG_HTML_TAG',
'AG_A_LINK'
'AG_LINK',
'AG_HARD_LINE_BREAK'
])
export const codeMirrorConfig = {

View File

@ -3,7 +3,7 @@ import { isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, isCursorAtEnd
import { findNearestParagraph } from '../utils/domManipulate'
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 => {
ContentState.prototype.firstInDescendant = function (block) {
@ -119,7 +119,7 @@ const arrowCtrl = ContentState => {
) {
activeBlock = preBlock
if (/^(?:pre|th|td)$/.test(preBlock.type)) {
activeBlock = this.createBlock('p')
activeBlock = this.createBlockP()
activeBlock.temp = true
this.insertBefore(activeBlock, anchorBlock)
}
@ -134,12 +134,12 @@ const arrowCtrl = ContentState => {
if (nextBlock) {
activeBlock = nextBlock
if (/^(?:pre|th|td)$/.test(nextBlock.type)) {
activeBlock = this.createBlock('p')
activeBlock = this.createBlockP()
activeBlock.temp = true
this.insertAfter(activeBlock, anchorBlock)
}
} else {
activeBlock = this.createBlock('p')
activeBlock = this.createBlockP()
this.insertAfter(activeBlock, anchorBlock)
}
}
@ -147,8 +147,9 @@ const arrowCtrl = ContentState => {
}
if (activeBlock) {
const offset = activeBlock.text.length
const key = activeBlock.key
const cursorBlock = activeBlock.type === 'p' ? activeBlock.children[0] : activeBlock
const offset = cursorBlock.text.length
const key = cursorBlock.key
this.cursor = {
start: {
key,
@ -165,13 +166,13 @@ const arrowCtrl = ContentState => {
}
if (/th|td/.test(block.type)) {
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 (
(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)
) {
activeBlock = this.createBlock('p')
activeBlock = this.createBlockP()
activeBlock.temp = true
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.ArrowRight && right === 0)
) {
activeBlock = this.createBlock('p')
activeBlock = this.createBlockP()
activeBlock.temp = true
this.insertAfter(activeBlock, anchorBlock)
}
if (activeBlock) {
event.preventDefault()
const offset = 0
const key = activeBlock.key
const key = activeBlock.children[0].key
this.cursor = {
start: {
key,
@ -238,10 +239,9 @@ const arrowCtrl = ContentState => {
(event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
) {
event.preventDefault()
const preBlockInLocation = this.findPreBlockInLocation(block)
if (!preBlockInLocation) return
const key = preBlockInLocation.key
const offset = preBlockInLocation.text.length
if (!preBlock) return
const key = preBlock.key
const offset = preBlock.text.length
this.cursor = {
start: { key, offset },
end: { key, offset }
@ -252,15 +252,14 @@ const arrowCtrl = ContentState => {
(event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
) {
event.preventDefault()
const nextBlockInLocation = this.findNextBlockInLocation(block)
let key
if (nextBlockInLocation) {
key = nextBlockInLocation.key
if (nextBlock) {
key = nextBlock.key
} else {
const newBlock = this.createBlock('p')
const newBlock = this.createBlockP()
const lastBlock = this.blocks[this.blocks.length - 1]
this.insertAfter(newBlock, lastBlock)
key = newBlock.key
key = newBlock.children[0].key
}
const offset = 0
this.cursor = {

View File

@ -7,7 +7,8 @@ const backspaceCtrl = ContentState => {
const node = selection.getSelectionStart()
const nearestParagraph = findNearestParagraph(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 outBlock = this.findOutMostBlock(block)
const parent = this.getParent(block)
@ -85,10 +86,10 @@ const backspaceCtrl = ContentState => {
if (start.key !== end.key) {
this.removeBlocks(startBlock, endBlock)
}
let newBlock = this.getNextSibling(startBlock)
let newBlock = this.findNextBlockInLocation(startBlock)
if (!newBlock) {
this.blocks = [this.createBlock()]
newBlock = this.blocks[0]
this.blocks = [this.createBlockP()]
newBlock = this.blocks[0].children[0]
}
const key = newBlock.key
const offset = 0
@ -121,11 +122,18 @@ const backspaceCtrl = ContentState => {
delete startBlock.pos
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
this.removeBlocks(startBlock, endBlock)
this.cursor = {
start: { key, offset },
end: { key, offset }
@ -136,30 +144,13 @@ const backspaceCtrl = ContentState => {
const node = selection.getSelectionStart()
const paragraph = findNearestParagraph(node)
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 preBlock = this.findPreBlockInLocation(block)
const { left } = selection.getCaretOffsets(paragraph)
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 tHead = table.children[0]
const tBody = table.children[1]
@ -175,12 +166,11 @@ const backspaceCtrl = ContentState => {
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
event.preventDefault()
const value = cm.getValue()
const newBlock = this.createBlock('p')
if (value) newBlock.text = value
const newBlock = this.createBlockP(value)
this.insertBefore(newBlock, anchorBlock)
this.removeBlock(anchorBlock)
this.codeBlocks.delete(id)
const key = newBlock.key
const key = newBlock.children[0].key
const offset = 0
this.cursor = {
@ -192,47 +182,27 @@ const backspaceCtrl = ContentState => {
}
} else if (left === 0 && /th|td/.test(block.type)) {
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 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
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) {
this.cursor = {
start: { key, offset },
@ -310,7 +280,9 @@ const backspaceCtrl = ContentState => {
preBlock.pos = { line, ch: ch - text.length }
this.removeBlock(block)
if (block.type === 'span' && this.isOnlyChild(block)) {
this.removeBlock(parent)
}
this.cursor = {
start: { key, offset },
end: { key, offset }
@ -318,6 +290,9 @@ const backspaceCtrl = ContentState => {
this.render()
} else if (left === 0) {
this.removeBlock(block)
if (block.type === 'span' && this.isOnlyChild(block)) {
this.removeBlock(parent)
}
}
}
}

View File

@ -46,13 +46,28 @@ const codeBlockCtrl = ContentState => {
* [codeBlockUpdate if block updated to `pre` return true, else return false]
*/
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) {
block.type = 'pre'
block.functionType = 'code'
block.text = ''
block.history = null
block.lang = match[1]
block.children = []
const key = block.key
const offset = 0
this.cursor = {
start: { key, offset },
end: { key, offset }
}
}
return !!match
}

View File

@ -1,6 +1,28 @@
import selection from '../selection'
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) {
const parent = this.getParent(block)
const type = parent.type
@ -28,20 +50,25 @@ const enterCtrl = ContentState => {
return trBlock
}
ContentState.prototype.createBlockLi = function (text = '', type = 'p') {
ContentState.prototype.createBlockLi = function (paragraphInListItem) {
const liBlock = this.createBlock('li')
const pBlock = this.createBlock(type, text)
this.appendChild(liBlock, pBlock)
if (!paragraphInListItem) {
paragraphInListItem = this.createBlockP()
}
this.appendChild(liBlock, paragraphInListItem)
return liBlock
}
ContentState.prototype.createTaskItemBlock = function (text = '', checked = false) {
ContentState.prototype.createTaskItemBlock = function (paragraphInListItem, checked = false) {
const listItem = this.createBlock('li')
const paragraphInListItem = this.createBlock('p', text)
const checkboxInListItem = this.createBlock('input')
listItem.listItemType = 'task'
checkboxInListItem.checked = checked
if (!paragraphInListItem) {
paragraphInListItem = this.createBlockP()
}
this.appendChild(listItem, checkboxInListItem)
this.appendChild(listItem, paragraphInListItem)
return listItem
@ -49,53 +76,13 @@ const enterCtrl = ContentState => {
ContentState.prototype.enterHandler = function (event) {
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)
const endBlock = this.getBlock(end.key)
let parent = this.getParent(block)
// handle float box
const { list, index, show } = this.floatBox
const { floatBox } = this
const { list, index, show } = floatBox
// handle float box
if (show) {
event.preventDefault()
floatBox.cb(list[index])
@ -107,26 +94,79 @@ const enterCtrl = ContentState => {
if (block.type === 'pre') {
return
}
event.preventDefault()
const getNextBlock = row => {
let nextSibling = this.getBlock(row.nextSibling)
if (!nextSibling) {
const rowContainer = this.getBlock(row.parent)
const table = this.getBlock(rowContainer.parent)
const figure = this.getBlock(table.parent)
if (rowContainer.type === 'thead') {
nextSibling = table.children[1]
} else if (figure.nextSibling) {
nextSibling = this.getBlock(figure.nextSibling)
} else {
nextSibling = this.createBlock('p')
this.insertAfter(nextSibling, figure)
}
// handle select multiple blocks
if (start.key !== end.key) {
const key = start.key
const offset = start.offset
const startRemainText = block.type === 'pre'
? block.text.substring(0, start.offset - 1)
: block.text.substring(0, start.offset)
const endRemainText = endBlock.type === 'pre'
? endBlock.text.substring(end.offset - 1)
: endBlock.text.substring(end.offset)
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)) {
const row = this.getBlock(block.parent)
const rowContainer = this.getBlock(row.parent)
@ -143,8 +183,14 @@ const enterCtrl = ContentState => {
table.row++
}
const nextSibling = getNextBlock(row)
const key = nextSibling.key
let nextBlock = this.findNextBlockInLocation(block)
// 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
this.cursor = {
@ -153,6 +199,12 @@ const enterCtrl = ContentState => {
}
return this.render()
}
if (block.type === 'span') {
block = parent
parent = this.getParent(block)
}
const paragraph = document.querySelector(`#${block.key}`)
if (
(parent && parent.type === 'li' && this.isOnlyChild(block)) ||
(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
let { pre, post } = selection.chopHtmlByCursor(paragraph)
if (/^h\d/.test(type)) {
if (/^h\d$/.test(block.type)) {
const PREFIX = /^#+/.exec(pre)[0]
post = `${PREFIX} ${post}`
}
if (type === 'li') {
block.text = pre
newBlock = this.createBlock(type, post)
} else if (block.type === 'p') {
newBlock = this.chopBlockByCursor(block, start.key, start.offset)
} else if (type === 'li') {
// handle task item
if (block.listItemType === 'task') {
const { checked } = block.children[0] // block.children[0] is input[type=checkbox]
block.children[1].text = pre // block.children[1] is p
newBlock = this.createTaskItemBlock(post, checked)
newBlock = this.chopBlockByCursor(block.children[1], start.key, start.offset)
newBlock = this.createTaskItemBlock(newBlock, checked)
} else {
block.children[0].text = pre
newBlock = this.createBlockLi(post)
newBlock = this.chopBlockByCursor(block.children[0], start.key, start.offset)
newBlock = this.createBlockLi(newBlock)
newBlock.listItemType = block.listItemType
}
newBlock.isLooseListItem = block.isLooseListItem
} else {
block.text = pre
newBlock = this.createBlock(type, post)
}
this.insertAfter(newBlock, block)
break
case left === 0 && right === 0: // paragraph is empty
if (parent && (parent.type === 'blockquote' || parent.type === 'ul')) {
newBlock = this.createBlock('p')
newBlock = this.createBlockP()
if (this.isOnlyChild(block)) {
this.insertAfter(newBlock, parent)
@ -214,7 +265,7 @@ const enterCtrl = ContentState => {
} else if (parent && parent.type === 'li') {
if (parent.listItemType === 'task') {
const { checked } = parent.children[0]
newBlock = this.createTaskItemBlock('', checked)
newBlock = this.createTaskItemBlock(null, checked)
} else {
newBlock = this.createBlockLi()
newBlock.listItemType = parent.listItemType
@ -227,7 +278,7 @@ const enterCtrl = ContentState => {
this.removeBlock(block)
} else {
newBlock = this.createBlock('p')
newBlock = this.createBlockP()
if (preType === 'li') {
const parent = this.getParent(block)
this.insertAfter(newBlock, parent)
@ -242,34 +293,47 @@ const enterCtrl = ContentState => {
if (preType === 'li') {
if (block.listItemType === 'task') {
const { checked } = block.children[0]
newBlock = this.createTaskItemBlock('', checked)
newBlock = this.createTaskItemBlock(null, checked)
} else {
newBlock = this.createBlockLi()
newBlock.listItemType = block.listItemType
}
newBlock.isLooseListItem = block.isLooseListItem
} else {
newBlock = this.createBlock('p')
newBlock = this.createBlockP()
}
if (left === 0 && right !== 0) {
this.insertBefore(newBlock, block)
newBlock = block
} else {
if (block.type === 'p') {
const lastLine = block.children[block.children.length - 1]
if (block.text.trim() === '') this.removeBlock(lastLine)
}
this.insertAfter(newBlock, block)
}
break
default:
newBlock = this.createBlock('p')
newBlock = this.createBlockP()
this.insertAfter(newBlock, block)
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.
const blockNeedFocus = this.codeBlockUpdate(block.type === 'li' ? block.children[0] : block)
let tableNeedFocus = this.tableBlockUpdate(block.type === 'li' ? block.children[0] : block)
let htmlNeedFocus = this.updateHtmlBlock(block.type === 'li' ? block.children[0] : block)
const preParagraphBlock = getParagraphBlock(block)
const blockNeedFocus = this.codeBlockUpdate(preParagraphBlock)
let tableNeedFocus = this.tableBlockUpdate(preParagraphBlock)
let htmlNeedFocus = this.updateHtmlBlock(preParagraphBlock)
let cursorBlock
switch (true) {
@ -288,16 +352,8 @@ const enterCtrl = ContentState => {
}
let key
if (cursorBlock.type === 'li') {
if (cursorBlock.listItemType === 'task') {
key = cursorBlock.children[1].key
} else {
key = cursorBlock.children[0].key
}
} else {
key = cursorBlock.key
}
cursorBlock = getParagraphBlock(cursorBlock)
key = cursorBlock.type === 'p' ? cursorBlock.children[0].key : cursorBlock.key
const offset = 0
this.cursor = {
start: { key, offset },

View File

@ -91,7 +91,7 @@ const htmlBlock = ContentState => {
ContentState.prototype.initHtmlBlock = function (block, tagName) {
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 pos = {
@ -110,8 +110,9 @@ const htmlBlock = ContentState => {
}
ContentState.prototype.updateHtmlBlock = function (block) {
const { type, text } = block
const { type } = block
if (type !== 'li' && type !== 'p') return false
const { text } = block.children[0]
const match = HTML_BLOCK_REG.exec(text)
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

View File

@ -54,7 +54,7 @@ const convertBlocksToArray = blocks => {
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()
class ContentState {
@ -62,7 +62,7 @@ class ContentState {
const { eventCenter } = options
Object.assign(this, options)
this.keys = new Set()
this.blocks = [ this.createBlock() ]
this.blocks = [ this.createBlockP() ]
this.stateRender = new StateRender(eventCenter)
this.codeBlocks = new Map()
this.loadMathMap = new Map()
@ -72,20 +72,16 @@ class ContentState {
init () {
const lastBlock = this.getLastBlock()
const { key, text } = lastBlock
const offset = text.length
this.searchMatches = {
value: '',
matches: [],
index: -1
value: '', // the search value
matches: [], // matches
index: -1 // active match
}
this.cursor = {
start: {
key: lastBlock.key,
offset: lastBlock.text.length
},
end: {
key: lastBlock.key,
offset: lastBlock.text.length
}
start: { key, offset },
end: { key, offset }
}
this.history.push({
type: 'normal',
@ -95,8 +91,7 @@ class ContentState {
}
setCursor () {
const { cursor } = this
selection.setCursorRange(cursor)
selection.setCursorRange(this.cursor)
}
render (isRenderCursor = true) {
@ -111,7 +106,11 @@ class ContentState {
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)
return {
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 () {
let key
let cm
for ([ key, cm ] of this.codeBlocks.entries()) {
for (const [ key, cm ] of this.codeBlocks.entries()) {
const value = cm.getValue()
const block = this.getBlock(key)
if (block) block.text = value
@ -298,7 +307,7 @@ class ContentState {
}
}
removeBlock (block) {
removeBlock (block, fromBlocks = this.blocks) {
if (block.type === 'pre') {
const codeBlockId = block.key
if (this.codeBlocks.has(codeBlockId)) {
@ -328,7 +337,7 @@ class ContentState {
}
}
}
remove(this.blocks, block)
remove(fromBlocks, block)
}
getActiveBlocks () {
@ -392,6 +401,15 @@ class ContentState {
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) {
const len = parent.children.length
const lastChild = parent.children[len - 1]

View File

@ -35,11 +35,12 @@ const tableBlockCtrl = ContentState => {
let figureBlock
if (start.key === end.key) {
const startBlock = this.getBlock(start.key)
const anchor = startBlock.type === 'span' ? this.getParent(startBlock) : startBlock
if (startBlock.text) {
figureBlock = this.createBlock('figure')
this.insertAfter(figureBlock, startBlock)
this.insertAfter(figureBlock, anchor)
} else {
figureBlock = startBlock
figureBlock = anchor
figureBlock.type = 'figure'
figureBlock.functionType = 'table'
figureBlock.text = ''
@ -58,7 +59,7 @@ const tableBlockCtrl = ContentState => {
}
ContentState.prototype.initTable = function (block) {
const { text } = block
const { text } = block.children[0]
const rowHeader = []
const len = text.length
let i
@ -114,10 +115,12 @@ const tableBlockCtrl = ContentState => {
break
}
case 'delete': {
const newLine = this.createBlock('span')
figure.children = []
this.appendChild(figure, newLine)
figure.type = 'p'
figure.text = ''
const key = figure.key
const key = newLine.key
const offset = 0
this.cursor = {
start: { key, offset },
@ -197,8 +200,9 @@ const tableBlockCtrl = ContentState => {
}
ContentState.prototype.tableBlockUpdate = function (block) {
const { type, text } = block
if (type !== 'li' && type !== 'p') return false
const { type } = block
if (type !== 'p') return false
const { text } = block.children[0]
const match = TABLE_BLOCK_REG.exec(text)
return (match && isLengthEven(match[1]) && isLengthEven(match[2])) ? this.initTable(block) : false
}

View File

@ -18,6 +18,18 @@ const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FREGMENTS.join('|'), 'i')
let lastCursor = null
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) {
const { start: cStart, end: cEnd } = this.cursor
const startOffset = cStart.offset
@ -39,11 +51,16 @@ const updateCtrl = ContentState => {
}
ContentState.prototype.checkInlineUpdate = function (block) {
// table cell can not have blocks in it
if (/th|td|figure/.test(block.type)) return false
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 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) || []
let newType
switch (true) {
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)
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)
return true
@ -63,12 +81,8 @@ const updateCtrl = ContentState => {
return true
case !!header:
newType = `h${header.length}`
if (block.type !== newType) {
block.type = newType // updateHeader
return true
}
break
this.updateHeader(block, header, text)
return true
case !!blockquote:
this.updateBlockQuote(block)
@ -76,15 +90,79 @@ const updateCtrl = ContentState => {
case !match:
default:
newType = 'p'
if (block.type !== newType) {
block.type = newType // updateP
return true
}
break
return this.updateToParagraph(block)
}
}
// thematic 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 = '') {
@ -97,7 +175,7 @@ const updateCtrl = ContentState => {
checkbox.checked = checked
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.isLooseListItem = preferLooseListItem
@ -146,102 +224,47 @@ 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.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)
ContentState.prototype.updateHeader = function (block, header, text) {
const newType = `h${header.length}`
if (block.type !== newType) {
block.type = newType
block.text = text
block.children.length = 0
}
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]
this.cursor.start.key = this.cursor.end.key = block.key
}
ContentState.prototype.updateBlockQuote = function (block) {
const newText = block.text.substring(1).trim()
const newPblock = this.createBlock('p', newText)
block.type = 'blockquote'
block.text = ''
this.appendChild(block, newPblock)
block.children[0].text = block.children[0].text.substring(1).trim()
const quoteBlock = this.createBlock('blockquote')
this.insertBefore(quoteBlock, block)
this.removeBlock(block)
this.appendChild(quoteBlock, block)
const { start, end } = this.cursor
const key = newPblock.key
this.cursor = {
start: {
key,
key: start.key,
offset: start.offset - 1
},
end: {
key,
key: end.key,
offset: end.offset - 1
}
}
}
// thematic break
ContentState.prototype.updateHr = function (block, marker) {
block.type = 'hr'
ContentState.prototype.updateToParagraph = function (block) {
const newType = 'p'
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) {
@ -321,9 +344,12 @@ const updateCtrl = ContentState => {
}
// remove temp block which generated by operation on code block
if (block && block.key !== oldKey) {
const oldBlock = this.getBlock(oldKey)
let oldBlock = this.getBlock(oldKey)
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) {
delete oldBlock.temp
} else {
@ -380,9 +406,8 @@ const updateCtrl = ContentState => {
}
this.cursor = lastCursor = { start, end }
const checkMarkedUpdate = this.checkNeedRender(block)
const checkInlineUpdate = this.checkInlineUpdate(block)
const checkInlineUpdate = this.isCollapse() && this.checkInlineUpdate(block)
if (checkMarkedUpdate || checkInlineUpdate || needRender) {
this.render()

View File

@ -33,6 +33,20 @@ h6.ag-active::before {
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 {
background: #E4E7ED;
color: #303133;

View File

@ -46,7 +46,6 @@ class Aganippe {
this.ensureContainerDiv()
const { container, contentState, eventCenter } = this
contentState.stateRender.setContainer(container.children[0])
contentState.render()
eventCenter.subscribe('editEmoji', throttle(this.subscribeEditEmoji.bind(this), 200))
this.dispatchEditEmoji()

View File

@ -62,9 +62,13 @@ class StateRender {
const type = block.type === 'hr' ? 'p' : block.type
const isActive = activeBlocks.some(b => b.key === block.key) || block.key === cursor.start.key
let blockSelector = isActive
? `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}.${CLASS_OR_ID['AG_ACTIVE']}`
: `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}`
let blockSelector = `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}`
if (isActive) {
blockSelector += `.${CLASS_OR_ID['AG_ACTIVE']}`
}
if (type === 'span') {
blockSelector += `.${CLASS_OR_ID['AG_LINE']}`
}
const data = {
attrs: {},
@ -134,17 +138,15 @@ class StateRender {
if (block.type === 'ol') {
Object.assign(data.attrs, { start: block.start })
}
return h(blockSelector, data, block.children.map(child => renderBlock(child)))
} else {
// highlight search key in block
const highlights = matches.filter(m => m.key === block.key)
let children = block.text
? tokenizer(block.text, highlights).reduce((acc, token) => {
const chunk = this[token.type](h, cursor, block, token)
return Array.isArray(chunk) ? [...acc, ...chunk] : [...acc, chunk]
}, [])
: [ h(LOWERCASE_TAGS.br) ]
const { text } = block
let children = ''
if (text) {
children = tokenizer(text, highlights).reduce((acc, token) => [...acc, ...this[token.type](h, cursor, block, token)], [])
}
if (/th|td/.test(block.type)) {
const { align } = block
@ -207,8 +209,8 @@ class StateRender {
})
const newVdom = h(selector, children)
const root = document.querySelector(selector) || this.container
const oldVdom = toVNode(root)
const rootDom = document.querySelector(selector) || this.container
const oldVdom = toVNode(rootDom)
patch(oldVdom, newVdom)
}
@ -233,13 +235,25 @@ class StateRender {
['tail_header'] (h, cursor, block, token, outerClass) {
const className = this.getClassName(outerClass, block, token, cursor)
const { start, end } = token.range
const content = this.highlight(h, block, start, end, token)
if (/^h\d$/.test(block.type)) {
const content = this.highlight(h, block, start, end, token)
return [
h(`span.${className}`, content)
]
} 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)
]
}
// change text to highlight vdom
highlight (h, block, rStart, rEnd, token) {
const { text } = block
@ -668,8 +683,9 @@ class StateRender {
const className = CLASS_OR_ID['AG_HTML_TAG']
const { start, end } = token.range
const tag = this.highlight(h, block, start, end, token)
const isBr = /<br(?=\s|\/|>)/.test(token.tag)
return [
h(`span.${className}`, tag)
h(`span.${className}`, isBr ? [...tag, h('br')] : tag)
]
}

View File

@ -101,7 +101,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
pending = ''
}
if (beginRules) {
if (beginRules && pos === 0) {
const beginR = ['header', 'hr', 'code_fense', 'display_math']
for (const ruleName of beginR) {
@ -350,6 +350,25 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
pos = pos + autoLTo[0].length
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
const tailTo = inlineRules['tail_header'].exec(src)
if (tailTo && top) {
@ -442,13 +461,16 @@ export const generator = tokens => {
case 'auto_link':
result += token.href
break
case 'html-image':
case 'html_image':
case 'html_tag':
result += token.tag
break
case 'tail_header':
result += token.marker
break
case 'hard_line_break':
result += token.spaces
break
default:
throw new Error(`unhandle token type: ${token.type}`)
}

View File

@ -20,6 +20,7 @@ export const inlineRules = {
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
'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_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 */

View File

@ -720,7 +720,6 @@ class Selection {
const { start, end } = cursorRange
const startParagraph = document.querySelector(`#${start.key}`)
const endParagraph = document.querySelector(`#${end.key}`)
const getNodeAndOffset = (node, offset) => {
if (node.nodeType === 3) {
return {

View File

@ -24,6 +24,12 @@ class ExportMarkdown {
for (const block of blocks) {
switch (block.type) {
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':
this.insertLineBreak(result, indent, true)
result.push(this.normalizeParagraphText(block, indent))

View File

@ -154,6 +154,8 @@ class ExportHTML {
$(removeClassNames.join(', ')).remove()
$(`.${CLASS_OR_ID['AG_ACTIVE']}`).removeClass(CLASS_OR_ID['AG_ACTIVE'])
$(`[data-role=hr]`).replaceWith('<hr>')
// replace the `emoji text` with actual emoji
const emojis = $(`span.${CLASS_OR_ID['AG_EMOJI_MARKED_TEXT']}`)
if (emojis.length > 0) {
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]`)
if (anchors.length > 0) {
if (anchors.length) {
anchors.each((i, a) => {
const anchor = $(a)
const href = anchor.attr('data-href')
@ -184,10 +186,32 @@ class ExportHTML {
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>&nbsp;</span>').appendTo(child)
}
}
})
})
}
return $('body').html()
.replace(/<span class="ag-html-tag">([\s\S]+?)<\/span>/g, (m, p1) => {
if (/script|style|title/.test(p1)) return p1
else return unescapeHtml(p1)
return /script|style|title/.test(p1) ? p1 : unescapeHtml(p1)
})
}
}

View File

@ -13,6 +13,8 @@ import { turndownConfig, CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7 } fro
const turndownPluginGfm = require('turndown-plugin-gfm')
const LINE_BREAKS = /\n/
// turn html to markdown
const turndownService = new TurndownService(turndownConfig)
const gfm = turndownPluginGfm.gfm
@ -103,7 +105,6 @@ const importRegister = ContentState => {
switch (child.nodeName) {
case 'th':
case 'td':
case 'p':
case 'h1':
case 'h2':
case 'h3':
@ -111,10 +112,6 @@ const importRegister = ContentState => {
case 'h5':
case 'h6':
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)
value = match ? '#'.repeat(+match[0]) + ` ${textValue}` : textValue
block = this.createBlock(child.nodeName, value)
@ -134,6 +131,17 @@ const importRegister = ContentState => {
this.appendChild(parent, block)
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':
const toolBar = this.createToolBar(TABLE_TOOLS, 'table')
const table = this.createBlock('table')
@ -243,13 +251,23 @@ const importRegister = ContentState => {
this.appendChild(parent, block)
} else {
// not html block
block = this.createBlock('p', fragment)
block = this.createBlockP(fragment)
this.appendChild(parent, block)
}
})
} else {
block = this.createBlock('p', value.replace(/^\s+/, '')) // fix: #153
} else if (parentNode.nodeName === 'li') {
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)
} 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
@ -264,7 +282,7 @@ const importRegister = ContentState => {
}
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.
ContentState.prototype.html2State = function (html) {