mirror of
https://github.com/marktext/marktext.git
synced 2025-05-04 03:32:36 +08:00
container block preview and inline syntax error (#992)
* 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
This commit is contained in:
parent
77ff23c2c8
commit
230c90c920
161
doc/Block addition properties and its value.md
Normal file
161
doc/Block addition properties and its value.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
### Block addition properties and its value
|
||||||
|
|
||||||
|
##### 1. span
|
||||||
|
|
||||||
|
- functionType
|
||||||
|
|
||||||
|
- languageInput
|
||||||
|
|
||||||
|
- codeLine
|
||||||
|
|
||||||
|
- atxLine (can not contain soft line break and hard line break use in atx heading)
|
||||||
|
|
||||||
|
- thematicBreakLine (can not contain soft line break and hard line break use in thematic break)
|
||||||
|
|
||||||
|
- paragraphContent (defaultValue use in paragraph and setext heading)
|
||||||
|
|
||||||
|
- lang - only when it's code line
|
||||||
|
|
||||||
|
- All prismjs support language or empty string
|
||||||
|
|
||||||
|
##### 2. div
|
||||||
|
|
||||||
|
used for preview `block math`, `mermaid`, `flowchart`, `vega-lite`, `sequence` and `html block`.
|
||||||
|
|
||||||
|
- functionType
|
||||||
|
|
||||||
|
- multiplemath
|
||||||
|
|
||||||
|
- mermaid
|
||||||
|
|
||||||
|
- flowchart
|
||||||
|
|
||||||
|
- vega-lite
|
||||||
|
|
||||||
|
- sequence
|
||||||
|
|
||||||
|
- html
|
||||||
|
|
||||||
|
##### 3. figure
|
||||||
|
|
||||||
|
The container block of `table`, `html`, `block math`, `mermaid`,`flowchart`,`vega-lite`,`sequence`.
|
||||||
|
|
||||||
|
- functionType
|
||||||
|
|
||||||
|
- table
|
||||||
|
|
||||||
|
- html
|
||||||
|
|
||||||
|
- multiplemath
|
||||||
|
|
||||||
|
- mermaid
|
||||||
|
|
||||||
|
- flowchart
|
||||||
|
|
||||||
|
- vega-lite
|
||||||
|
|
||||||
|
- sequence
|
||||||
|
|
||||||
|
##### 4. pre
|
||||||
|
|
||||||
|
Used for `html`,`block math`,`mermaid`,`flowchart`,`vega-lite`,`sequence` `code block`.
|
||||||
|
|
||||||
|
- functionType
|
||||||
|
|
||||||
|
- html
|
||||||
|
|
||||||
|
- multiplemath
|
||||||
|
|
||||||
|
- mermaid
|
||||||
|
|
||||||
|
- flowchart
|
||||||
|
|
||||||
|
- vega-lite
|
||||||
|
|
||||||
|
- sequence
|
||||||
|
|
||||||
|
- fencecode
|
||||||
|
|
||||||
|
- indentcode
|
||||||
|
|
||||||
|
- frontmatter
|
||||||
|
|
||||||
|
- lang
|
||||||
|
|
||||||
|
- all prismjs support language or empty string
|
||||||
|
|
||||||
|
##### 5. code
|
||||||
|
|
||||||
|
- lang
|
||||||
|
|
||||||
|
- all prismjs support language or empty string
|
||||||
|
|
||||||
|
##### ul
|
||||||
|
|
||||||
|
- listType
|
||||||
|
|
||||||
|
- bullet
|
||||||
|
|
||||||
|
- task
|
||||||
|
|
||||||
|
##### ol
|
||||||
|
|
||||||
|
- listType
|
||||||
|
|
||||||
|
- order
|
||||||
|
|
||||||
|
- start
|
||||||
|
|
||||||
|
- 0-999999999
|
||||||
|
|
||||||
|
##### li
|
||||||
|
|
||||||
|
- listItemType
|
||||||
|
|
||||||
|
- order
|
||||||
|
|
||||||
|
- bullet
|
||||||
|
|
||||||
|
- task
|
||||||
|
|
||||||
|
- isLooseListItem
|
||||||
|
|
||||||
|
- true
|
||||||
|
|
||||||
|
- false
|
||||||
|
|
||||||
|
- bulletMarkerOrDelimiter
|
||||||
|
|
||||||
|
- bulletMarker:`-`, `+`, `*`
|
||||||
|
|
||||||
|
- Delimiter: `)`, `.`
|
||||||
|
|
||||||
|
##### h1~6
|
||||||
|
|
||||||
|
- headingStyle
|
||||||
|
|
||||||
|
- atx
|
||||||
|
|
||||||
|
- setext
|
||||||
|
|
||||||
|
- marker - only setext heading has marker
|
||||||
|
|
||||||
|
##### input
|
||||||
|
|
||||||
|
- checked
|
||||||
|
|
||||||
|
- true
|
||||||
|
|
||||||
|
- false
|
||||||
|
|
||||||
|
##### table
|
||||||
|
|
||||||
|
- row
|
||||||
|
|
||||||
|
- column
|
||||||
|
|
||||||
|
##### th/td
|
||||||
|
|
||||||
|
- align
|
||||||
|
|
||||||
|
- column
|
@ -189,7 +189,7 @@
|
|||||||
"keyboard-layout": "^2.0.15",
|
"keyboard-layout": "^2.0.15",
|
||||||
"mermaid": "^8.0.0",
|
"mermaid": "^8.0.0",
|
||||||
"popper.js": "^1.15.0",
|
"popper.js": "^1.15.0",
|
||||||
"prismjs": "^1.16.0",
|
"prismjs2": "^1.15.1",
|
||||||
"snabbdom": "^0.7.3",
|
"snabbdom": "^0.7.3",
|
||||||
"snabbdom-to-html": "^5.1.1",
|
"snabbdom-to-html": "^5.1.1",
|
||||||
"snapsvg": "^0.5.1",
|
"snapsvg": "^0.5.1",
|
||||||
|
@ -35,11 +35,11 @@ const setParagraphMenuItemStatus = bool => {
|
|||||||
.forEach(item => (item.enabled = bool))
|
.forEach(item => (item.enabled = bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableNoMultiple = disableLabels => {
|
const setMultipleStatus = (list, status) => {
|
||||||
const paragraphMenuItem = getMenuItemById('paragraphMenuEntry')
|
const paragraphMenuItem = getMenuItemById('paragraphMenuEntry')
|
||||||
paragraphMenuItem.submenu.items
|
paragraphMenuItem.submenu.items
|
||||||
.filter(item => item.id && disableLabels.includes(item.id))
|
.filter(item => item.id && list.includes(item.id))
|
||||||
.forEach(item => (item.enabled = false))
|
.forEach(item => (item.enabled = status))
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCheckedMenuItem = affiliation => {
|
const setCheckedMenuItem = affiliation => {
|
||||||
@ -105,15 +105,17 @@ ipcMain.on('AGANI::selection-change', (e, { start, end, affiliation }) => {
|
|||||||
(end.type === 'span' && end.block.functionType === 'codeLine')
|
(end.type === 'span' && end.block.functionType === 'codeLine')
|
||||||
) {
|
) {
|
||||||
setParagraphMenuItemStatus(false)
|
setParagraphMenuItemStatus(false)
|
||||||
|
|
||||||
if (start.block.functionType === 'codeLine' || end.block.functionType === 'codeLine') {
|
if (start.block.functionType === 'codeLine' || end.block.functionType === 'codeLine') {
|
||||||
|
setMultipleStatus(['codeFencesMenuItem'], true)
|
||||||
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
||||||
}
|
}
|
||||||
} else if (start.key !== end.key) {
|
} else if (start.key !== end.key) {
|
||||||
formatMenuItem.submenu.items
|
formatMenuItem.submenu.items
|
||||||
.filter(item => item.id && DISABLE_LABELS.includes(item.id))
|
.filter(item => item.id && DISABLE_LABELS.includes(item.id))
|
||||||
.forEach(item => (item.enabled = false))
|
.forEach(item => (item.enabled = false))
|
||||||
disableNoMultiple(DISABLE_LABELS)
|
setMultipleStatus(DISABLE_LABELS, false)
|
||||||
} else if (!affiliation.slice(0, 3).some(p => /ul|ol/.test(p.type))) {
|
} else if (!affiliation.slice(0, 3).some(p => /ul|ol/.test(p.type))) {
|
||||||
disableNoMultiple(['looseListItemMenuItem'])
|
setMultipleStatus(['looseListItemMenuItem'], false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -22,7 +22,7 @@ pre {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-type:empty::after {
|
div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-paragraph-content:first-of-type:empty::after {
|
||||||
content: 'Type @ to insert';
|
content: 'Type @ to insert';
|
||||||
color: var(--editorColor10);
|
color: var(--editorColor10);
|
||||||
}
|
}
|
||||||
@ -31,12 +31,17 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-t
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-paragraph:empty::after,
|
.ag-atx-line:empty::after,
|
||||||
.ag-line:empty::after {
|
.ag-thematic-break-line:empty::after,
|
||||||
content: '\200B'
|
.ag-code-line:empty::after,
|
||||||
|
.ag-paragraph-content:empty::after {
|
||||||
|
content: '\200B';
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-line {
|
.ag-atx-line,
|
||||||
|
.ag-thematic-break-line,
|
||||||
|
.ag-paragraph-content,
|
||||||
|
.ag-code-line {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@ -62,11 +67,15 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-t
|
|||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-hard-line-break::after {
|
.ag-hard-line-break-space::after {
|
||||||
content: '↩';
|
content: '↩';
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-line-end {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
*:not(.ag-hide)::selection, .ag-selection {
|
*:not(.ag-hide)::selection, .ag-selection {
|
||||||
background: var(--selectionColor);
|
background: var(--selectionColor);
|
||||||
color: var(--editorColor);
|
color: var(--editorColor);
|
||||||
@ -77,8 +86,7 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-t
|
|||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
figure.ag-container-block pre,
|
figure.ag-container-block pre {
|
||||||
div.ag-function-html pre.ag-html-block {
|
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -90,7 +98,6 @@ div.ag-function-html pre.ag-html-block {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ag-function-html.ag-active pre.ag-html-block,
|
|
||||||
figure.ag-active.ag-container-block pre {
|
figure.ag-active.ag-container-block pre {
|
||||||
position: static;
|
position: static;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -100,11 +107,11 @@ figure.ag-active.ag-container-block pre {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ag-function-html .ag-html-preview {
|
figure[data-role="HTML"] .ag-html-preview {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ag-function-html.ag-active .ag-html-preview {
|
figure[data-role="HTML"].ag-active .ag-html-preview {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +481,6 @@ pre.ag-multiple-math span.ag-code-line:first-of-type:empty::after {
|
|||||||
|
|
||||||
figure,
|
figure,
|
||||||
pre.ag-html-block,
|
pre.ag-html-block,
|
||||||
div.ag-function-html,
|
|
||||||
pre.ag-fence-code,
|
pre.ag-fence-code,
|
||||||
pre.ag-indent-code,
|
pre.ag-indent-code,
|
||||||
li.ag-list-item > p.ag-paragraph {
|
li.ag-list-item > p.ag-paragraph {
|
||||||
|
@ -6,7 +6,7 @@ import voidHtmlTags from 'html-tags/void'
|
|||||||
// Electron 2.0.2 not support yet! So give a default value 4
|
// Electron 2.0.2 not support yet! So give a default value 4
|
||||||
export const DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
|
export const DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
|
||||||
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
|
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
|
||||||
export const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr)/i
|
export const HAS_TEXT_BLOCK_REG = /^(span|th|td)/i
|
||||||
export const VOID_HTML_TAGS = voidHtmlTags
|
export const VOID_HTML_TAGS = voidHtmlTags
|
||||||
export const HTML_TAGS = htmlTags
|
export const HTML_TAGS = htmlTags
|
||||||
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
||||||
@ -85,6 +85,8 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_FRONT_ICON',
|
'AG_FRONT_ICON',
|
||||||
'AG_GRAY',
|
'AG_GRAY',
|
||||||
'AG_HARD_LINE_BREAK',
|
'AG_HARD_LINE_BREAK',
|
||||||
|
'AG_HARD_LINE_BREAK_SPACE',
|
||||||
|
'AG_LINE_END',
|
||||||
'AG_HEADER_TIGHT_SPACE',
|
'AG_HEADER_TIGHT_SPACE',
|
||||||
'AG_HIDE',
|
'AG_HIDE',
|
||||||
'AG_HIGHLIGHT',
|
'AG_HIGHLIGHT',
|
||||||
@ -99,7 +101,6 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_INLINE_RULE',
|
'AG_INLINE_RULE',
|
||||||
'AG_LANGUAGE',
|
'AG_LANGUAGE',
|
||||||
'AG_LANGUAGE_INPUT',
|
'AG_LANGUAGE_INPUT',
|
||||||
'AG_LINE',
|
|
||||||
'AG_LINK',
|
'AG_LINK',
|
||||||
'AG_LINK_IN_BRACKET',
|
'AG_LINK_IN_BRACKET',
|
||||||
'AG_LIST_ITEM',
|
'AG_LIST_ITEM',
|
||||||
@ -111,6 +112,7 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_RUBY_TEXT',
|
'AG_RUBY_TEXT',
|
||||||
'AG_RUBY_RENDER',
|
'AG_RUBY_RENDER',
|
||||||
'AG_SELECTED',
|
'AG_SELECTED',
|
||||||
|
'AG_SOFT_LINE_BREAK',
|
||||||
'AG_MATH_ERROR',
|
'AG_MATH_ERROR',
|
||||||
'AG_MATH_MARKER',
|
'AG_MATH_MARKER',
|
||||||
'AG_MATH_RENDER',
|
'AG_MATH_RENDER',
|
||||||
@ -159,7 +161,18 @@ export const DEFAULT_TURNDOWN_CONFIG = {
|
|||||||
codeBlockStyle: 'fenced', // fenced or indented
|
codeBlockStyle: 'fenced', // fenced or indented
|
||||||
fence: '```', // ``` or ~~~
|
fence: '```', // ``` or ~~~
|
||||||
emDelimiter: '*', // _ or *
|
emDelimiter: '*', // _ or *
|
||||||
strongDelimiter: '**' // ** or __
|
strongDelimiter: '**', // ** or __
|
||||||
|
blankReplacement (content, node, options) {
|
||||||
|
if (node && node.classList.contains('ag-soft-line-break')) {
|
||||||
|
return LINE_BREAK
|
||||||
|
} else if (node && node.classList.contains('ag-hard-line-break')) {
|
||||||
|
return ' ' + LINE_BREAK
|
||||||
|
} else if (node && node.classList.contains('ag-hard-line-break-sapce')) {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return node.isBlock ? '\n\n' : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FORMAT_MARKER_MAP = {
|
export const FORMAT_MARKER_MAP = {
|
||||||
|
@ -4,7 +4,7 @@ import selection from '../selection'
|
|||||||
|
|
||||||
// If the next block is header, put cursor after the `#{1,6} *`
|
// If the next block is header, put cursor after the `#{1,6} *`
|
||||||
const adjustOffset = (offset, block, event) => {
|
const adjustOffset = (offset, block, event) => {
|
||||||
if (/^h\d$/.test(block.type) && event.key === EVENT_KEYS.ArrowDown) {
|
if (/^span$/.test(block.type) && block.functionType === 'atxLine' && event.key === EVENT_KEYS.ArrowDown) {
|
||||||
const match = /^\s{0,3}(?:#{1,6})(?:\s{1,}|$)/.exec(block.text)
|
const match = /^\s{0,3}(?:#{1,6})(?:\s{1,}|$)/.exec(block.text)
|
||||||
if (match) {
|
if (match) {
|
||||||
return match[0].length
|
return match[0].length
|
||||||
|
@ -106,11 +106,32 @@ const backspaceCtrl = ContentState => {
|
|||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const startBlock = this.getBlock(start.key)
|
const startBlock = this.getBlock(start.key)
|
||||||
const endBlock = this.getBlock(end.key)
|
const endBlock = this.getBlock(end.key)
|
||||||
const maybeLastRow = this.getParent(endBlock)
|
const maybeLastRow = this.getParent(endBlock)
|
||||||
const startOutmostBlock = this.findOutMostBlock(startBlock)
|
const startOutmostBlock = this.findOutMostBlock(startBlock)
|
||||||
const endOutmostBlock = this.findOutMostBlock(endBlock)
|
const endOutmostBlock = this.findOutMostBlock(endBlock)
|
||||||
|
// Just for fix delete the last `#` or all the atx heading cause error @fixme
|
||||||
|
if (
|
||||||
|
start.key === end.key &&
|
||||||
|
startBlock.type === 'span' &&
|
||||||
|
startBlock.functionType === 'atxLine'
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
start.offset === 0 && end.offset === startBlock.text.length ||
|
||||||
|
start.offset === end.offset && start.offset === 1 && startBlock.text === '#'
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
startBlock.text = ''
|
||||||
|
this.cursor = {
|
||||||
|
start: { key: start.key, offset: 0 },
|
||||||
|
end: { key: end.key, offset: 0 }
|
||||||
|
}
|
||||||
|
this.updateToParagraph(this.getParent(startBlock), startBlock)
|
||||||
|
return this.partialRender()
|
||||||
|
}
|
||||||
|
}
|
||||||
// fix: #897
|
// fix: #897
|
||||||
const { text } = startBlock
|
const { text } = startBlock
|
||||||
const tokens = tokenizer(text)
|
const tokens = tokenizer(text)
|
||||||
@ -219,7 +240,7 @@ const backspaceCtrl = ContentState => {
|
|||||||
) {
|
) {
|
||||||
const preBlock = this.getParent(parent)
|
const preBlock = this.getParent(parent)
|
||||||
const pBlock = this.createBlock('p')
|
const pBlock = this.createBlock('p')
|
||||||
const lineBlock = this.createBlock('span', block.text)
|
const lineBlock = this.createBlock('span', { text: block.text })
|
||||||
const key = lineBlock.key
|
const key = lineBlock.key
|
||||||
const offset = 0
|
const offset = 0
|
||||||
this.appendChild(pBlock, lineBlock)
|
this.appendChild(pBlock, lineBlock)
|
||||||
@ -235,10 +256,8 @@ const backspaceCtrl = ContentState => {
|
|||||||
case 'mermaid':
|
case 'mermaid':
|
||||||
case 'sequence':
|
case 'sequence':
|
||||||
case 'vega-lite':
|
case 'vega-lite':
|
||||||
referenceBlock = this.getParent(preBlock)
|
|
||||||
break
|
|
||||||
case 'html':
|
case 'html':
|
||||||
referenceBlock = this.getParent(this.getParent(preBlock))
|
referenceBlock = this.getParent(preBlock)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.insertBefore(pBlock, referenceBlock)
|
this.insertBefore(pBlock, referenceBlock)
|
||||||
|
@ -136,6 +136,7 @@ const clickCtrl = ContentState => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.cursor = cursor
|
this.cursor = cursor
|
||||||
|
|
||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,27 +72,6 @@ const codeBlockCtrl = ContentState => {
|
|||||||
this.partialRender()
|
this.partialRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.indentCodeBlockUpdate = function (block) {
|
|
||||||
const oldPBlock = this.getParent(block)
|
|
||||||
const codeBlock = this.createBlock('code')
|
|
||||||
const inputBlock = this.createBlock('span', '')
|
|
||||||
const preBlock = this.createBlock('pre')
|
|
||||||
|
|
||||||
oldPBlock.children.forEach(child => {
|
|
||||||
child.lang = ''
|
|
||||||
child.functionType = 'codeLine'
|
|
||||||
child.text = child.text.replace(/^ {4}/, '')
|
|
||||||
this.appendChild(codeBlock, child)
|
|
||||||
})
|
|
||||||
codeBlock.lang = preBlock.lang = ''
|
|
||||||
inputBlock.functionType = 'languageInput'
|
|
||||||
preBlock.functionType = 'indentcode'
|
|
||||||
this.appendChild(preBlock, inputBlock)
|
|
||||||
this.appendChild(preBlock, codeBlock)
|
|
||||||
this.insertBefore(preBlock, oldPBlock)
|
|
||||||
this.removeBlock(oldPBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [codeBlockUpdate if block updated to `pre` return true, else return false]
|
* [codeBlockUpdate if block updated to `pre` return true, else return false]
|
||||||
*/
|
*/
|
||||||
@ -108,9 +87,9 @@ const codeBlockCtrl = ContentState => {
|
|||||||
const match = CODE_UPDATE_REP.exec(text)
|
const match = CODE_UPDATE_REP.exec(text)
|
||||||
if (match || lang) {
|
if (match || lang) {
|
||||||
const codeBlock = this.createBlock('code')
|
const codeBlock = this.createBlock('code')
|
||||||
const firstLine = this.createBlock('span', code)
|
const firstLine = this.createBlock('span', { text: code })
|
||||||
const language = lang || (match ? match[1] : '')
|
const language = lang || (match ? match[1] : '')
|
||||||
const inputBlock = this.createBlock('span', language)
|
const inputBlock = this.createBlock('span', { text: language })
|
||||||
loadLanguage(language)
|
loadLanguage(language)
|
||||||
inputBlock.functionType = 'languageInput'
|
inputBlock.functionType = 'languageInput'
|
||||||
block.type = 'pre'
|
block.type = 'pre'
|
||||||
|
@ -4,44 +4,57 @@ const FUNCTION_TYPE_LANG = {
|
|||||||
'flowchart': 'yaml',
|
'flowchart': 'yaml',
|
||||||
'mermaid': 'yaml',
|
'mermaid': 'yaml',
|
||||||
'sequence': 'yaml',
|
'sequence': 'yaml',
|
||||||
'vega-lite': 'yaml'
|
'vega-lite': 'yaml',
|
||||||
|
'html': 'markup'
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerCtrl = ContentState => {
|
const containerCtrl = ContentState => {
|
||||||
ContentState.prototype.createContainerBlock = function (functionType, value = '') {
|
ContentState.prototype.createContainerBlock = function (functionType, value = '') {
|
||||||
const figureBlock = this.createBlock('figure')
|
const figureBlock = this.createBlock('figure', {
|
||||||
figureBlock.functionType = functionType
|
functionType
|
||||||
|
})
|
||||||
|
|
||||||
const { preBlock, preview } = this.createPreAndPreview(functionType, value)
|
const { preBlock, preview } = this.createPreAndPreview(functionType, value)
|
||||||
this.appendChild(figureBlock, preBlock)
|
this.appendChild(figureBlock, preBlock)
|
||||||
this.appendChild(figureBlock, preview)
|
this.appendChild(figureBlock, preview)
|
||||||
this.codeBlocks.set(preBlock.key, value)
|
|
||||||
return figureBlock
|
return figureBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.createPreAndPreview = function (functionType, value = '') {
|
ContentState.prototype.createPreAndPreview = function (functionType, value = '') {
|
||||||
const preBlock = this.createBlock('pre')
|
const lang = FUNCTION_TYPE_LANG[functionType]
|
||||||
const codeBlock = this.createBlock('code')
|
const preBlock = this.createBlock('pre', {
|
||||||
preBlock.functionType = functionType
|
functionType,
|
||||||
preBlock.lang = codeBlock.lang = FUNCTION_TYPE_LANG[functionType]
|
lang
|
||||||
|
})
|
||||||
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(preBlock, codeBlock)
|
this.appendChild(preBlock, codeBlock)
|
||||||
|
|
||||||
if (typeof value === 'string' && value) {
|
if (typeof value === 'string' && value) {
|
||||||
value.replace(/^\s+/, '').split(LINE_BREAKS_REG).forEach(line => {
|
value.replace(/^\s+/, '').split(LINE_BREAKS_REG).forEach(line => {
|
||||||
const codeLine = this.createBlock('span', line)
|
const codeLine = this.createBlock('span', {
|
||||||
codeLine.functionType = 'codeLine'
|
text: line,
|
||||||
codeLine.lang = FUNCTION_TYPE_LANG[functionType]
|
functionType: 'codeLine',
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
this.appendChild(codeBlock, codeLine)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const emptyLine = this.createBlock('span')
|
const emptyLine = this.createBlock('span', {
|
||||||
emptyLine.functionType = 'codeLine'
|
functionType: 'codeLine',
|
||||||
emptyLine.lang = FUNCTION_TYPE_LANG[functionType]
|
lang
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, emptyLine)
|
this.appendChild(codeBlock, emptyLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview = this.createBlock('div', '', false)
|
const preview = this.createBlock('div', {
|
||||||
this.codeBlocks.set(preBlock.key, '')
|
editable: false,
|
||||||
preview.functionType = functionType
|
functionType
|
||||||
|
})
|
||||||
|
|
||||||
return { preBlock, preview }
|
return { preBlock, preview }
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,12 @@ const copyCutCtrl = ContentState => {
|
|||||||
hb.replaceWith(pre)
|
hb.replaceWith(pre)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just work for turndown, turndown will add `leading` and `traling` space in line-break.
|
||||||
|
const lineBreaks = wrapper.querySelectorAll('span.ag-soft-line-break, span.ag-hard-line-break')
|
||||||
|
for (const b of lineBreaks) {
|
||||||
|
b.innerHTML = ''
|
||||||
|
}
|
||||||
|
|
||||||
const mathBlock = wrapper.querySelectorAll(`figure.ag-container-block`)
|
const mathBlock = wrapper.querySelectorAll(`figure.ag-container-block`)
|
||||||
for (const mb of mathBlock) {
|
for (const mb of mathBlock) {
|
||||||
const preElement = mb.querySelector('pre[data-role]')
|
const preElement = mb.querySelector('pre[data-role]')
|
||||||
@ -128,6 +134,7 @@ const copyCutCtrl = ContentState => {
|
|||||||
|
|
||||||
let htmlData = wrapper.innerHTML
|
let htmlData = wrapper.innerHTML
|
||||||
const textData = this.htmlToMarkdown(htmlData)
|
const textData = this.htmlToMarkdown(htmlData)
|
||||||
|
|
||||||
htmlData = marked(textData)
|
htmlData = marked(textData)
|
||||||
return { html: htmlData, text: textData }
|
return { html: htmlData, text: textData }
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ const getIndentSpace = text => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enterCtrl = ContentState => {
|
const enterCtrl = ContentState => {
|
||||||
|
// TODO@jocs this function need opti.
|
||||||
ContentState.prototype.chopBlockByCursor = function (block, key, offset) {
|
ContentState.prototype.chopBlockByCursor = function (block, key, offset) {
|
||||||
const newBlock = this.createBlock('p')
|
const newBlock = this.createBlock('p')
|
||||||
const { children } = block
|
const { children } = block
|
||||||
@ -28,7 +29,7 @@ const enterCtrl = ContentState => {
|
|||||||
this.prependChild(newBlock, activeLine)
|
this.prependChild(newBlock, activeLine)
|
||||||
} else if (offset < text.length) {
|
} else if (offset < text.length) {
|
||||||
activeLine.text = text.substring(0, offset)
|
activeLine.text = text.substring(0, offset)
|
||||||
const newLine = this.createBlock('span', text.substring(offset))
|
const newLine = this.createBlock('span', { text: text.substring(offset) })
|
||||||
this.prependChild(newBlock, newLine)
|
this.prependChild(newBlock, newLine)
|
||||||
}
|
}
|
||||||
return newBlock
|
return newBlock
|
||||||
@ -197,23 +198,39 @@ const enterCtrl = ContentState => {
|
|||||||
// handle `shift + enter` insert `soft line break` or `hard line break`
|
// 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`
|
// only cursor in `line block` can create `soft line break` and `hard line break`
|
||||||
// handle line in code block
|
// handle line in code block
|
||||||
if (
|
if (event.shiftKey && block.type === 'span' && block.functionType === 'paragraphContent') {
|
||||||
(event.shiftKey && block.type === 'span') ||
|
let { offset } = start
|
||||||
(block.type === 'span' && block.functionType === 'codeLine')
|
const { text, key } = block
|
||||||
|
const indent = getIndentSpace(text)
|
||||||
|
block.text = text.substring(0, offset) + '\n' + indent + text.substring(offset)
|
||||||
|
|
||||||
|
offset += 1 + indent.length
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
|
return this.partialRender()
|
||||||
|
} else if (
|
||||||
|
block.type === 'span' && block.functionType === 'codeLine'
|
||||||
) {
|
) {
|
||||||
const { text } = block
|
const { text } = block
|
||||||
const newLineText = text.substring(start.offset)
|
const newLineText = text.substring(start.offset)
|
||||||
const autoIndent = checkAutoIndent(text, start.offset)
|
const autoIndent = checkAutoIndent(text, start.offset)
|
||||||
const indent = getIndentSpace(text)
|
const indent = getIndentSpace(text)
|
||||||
block.text = text.substring(0, start.offset)
|
block.text = text.substring(0, start.offset)
|
||||||
const newLine = this.createBlock('span', `${indent}${newLineText}`)
|
const newLine = this.createBlock('span', {
|
||||||
newLine.functionType = block.functionType
|
text: `${indent}${newLineText}`,
|
||||||
newLine.lang = block.lang
|
functionType: block.functionType,
|
||||||
|
lang: block.lang
|
||||||
|
})
|
||||||
|
|
||||||
this.insertAfter(newLine, block)
|
this.insertAfter(newLine, block)
|
||||||
let { key } = newLine
|
let { key } = newLine
|
||||||
let offset = indent.length
|
let offset = indent.length
|
||||||
if (autoIndent) {
|
if (autoIndent) {
|
||||||
const emptyLine = this.createBlock('span', indent + ' '.repeat(this.tabSize))
|
const emptyLine = this.createBlock('span', {
|
||||||
|
text: indent + ' '.repeat(this.tabSize)
|
||||||
|
})
|
||||||
emptyLine.functionType = block.functionType
|
emptyLine.functionType = block.functionType
|
||||||
emptyLine.lang = block.lang
|
emptyLine.lang = block.lang
|
||||||
this.insertAfter(emptyLine, block)
|
this.insertAfter(emptyLine, block)
|
||||||
@ -221,11 +238,6 @@ const enterCtrl = ContentState => {
|
|||||||
offset = indent.length + this.tabSize
|
offset = indent.length + this.tabSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indent.length >= 4 && !block.preSibling) {
|
|
||||||
this.indentCodeBlockUpdate(block)
|
|
||||||
offset = offset - 4
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
@ -323,8 +335,14 @@ const enterCtrl = ContentState => {
|
|||||||
post = `${PREFIX} ${post}`
|
post = `${PREFIX} ${post}`
|
||||||
}
|
}
|
||||||
block.text = pre
|
block.text = pre
|
||||||
newBlock = this.createBlock(type, post)
|
newBlock = this.createBlock(type, {
|
||||||
newBlock.headingStyle = block.headingStyle
|
headingStyle: block.headingStyle
|
||||||
|
})
|
||||||
|
const headerContent = this.createBlock('span', {
|
||||||
|
text: post,
|
||||||
|
functionType: block.headingStyle === 'atx'? 'atxLine' : 'paragraphContent'
|
||||||
|
})
|
||||||
|
this.appendChild(newBlock, headerContent)
|
||||||
if (block.marker) {
|
if (block.marker) {
|
||||||
newBlock.marker = block.marker
|
newBlock.marker = block.marker
|
||||||
}
|
}
|
||||||
|
@ -2,61 +2,20 @@ import { VOID_HTML_TAGS, HTML_TAGS } from '../config'
|
|||||||
import { inlineRules } from '../parser/rules'
|
import { inlineRules } from '../parser/rules'
|
||||||
|
|
||||||
const HTML_BLOCK_REG = /^<([a-zA-Z\d-]+)(?=\s|>)[^<>]*?>$/
|
const HTML_BLOCK_REG = /^<([a-zA-Z\d-]+)(?=\s|>)[^<>]*?>$/
|
||||||
const LINE_BREAKS = /\n/
|
|
||||||
|
|
||||||
const htmlBlock = ContentState => {
|
const htmlBlock = ContentState => {
|
||||||
ContentState.prototype.createCodeInHtml = function (code) {
|
|
||||||
const codeContainer = this.createBlock('div')
|
|
||||||
codeContainer.functionType = 'html'
|
|
||||||
const preview = this.createBlock('div', '', false)
|
|
||||||
preview.functionType = 'preview'
|
|
||||||
const preBlock = this.createBlock('pre')
|
|
||||||
const codeBlock = this.createBlock('code')
|
|
||||||
code.split(LINE_BREAKS).forEach(line => {
|
|
||||||
const codeLine = this.createBlock('span', line)
|
|
||||||
codeLine.functionType = 'codeLine'
|
|
||||||
codeLine.lang = 'markup'
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
})
|
|
||||||
this.codeBlocks.set(preBlock.key, code)
|
|
||||||
preBlock.lang = 'markup'
|
|
||||||
codeBlock.lang = 'markup'
|
|
||||||
preBlock.functionType = 'html'
|
|
||||||
this.codeBlocks.set(preBlock.key, code)
|
|
||||||
this.appendChild(preBlock, codeBlock)
|
|
||||||
this.appendChild(codeContainer, preBlock)
|
|
||||||
this.appendChild(codeContainer, preview)
|
|
||||||
return codeContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentState.prototype.handleHtmlBlockClick = function (codeWrapper) {
|
|
||||||
const id = codeWrapper.id
|
|
||||||
const codeBlock = this.getBlock(id).children[0]
|
|
||||||
const key = codeBlock.key
|
|
||||||
const offset = 0
|
|
||||||
this.cursor = {
|
|
||||||
start: { key, offset },
|
|
||||||
end: { key, offset }
|
|
||||||
}
|
|
||||||
this.partialRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentState.prototype.createHtmlBlock = function (code) {
|
ContentState.prototype.createHtmlBlock = function (code) {
|
||||||
const block = this.createBlock('figure')
|
const block = this.createBlock('figure')
|
||||||
block.functionType = 'html'
|
block.functionType = 'html'
|
||||||
const htmlBlock = this.createCodeInHtml(code)
|
const { preBlock, preview } = this.createPreAndPreview('html', code)
|
||||||
this.appendChild(block, htmlBlock)
|
this.appendChild(block, preBlock)
|
||||||
|
this.appendChild(block, preview)
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.initHtmlBlock = function (block) {
|
ContentState.prototype.initHtmlBlock = function (block) {
|
||||||
let htmlContent = ''
|
let htmlContent = ''
|
||||||
const text = block.type === 'p'
|
const text = block.children[0].text
|
||||||
? block.children.map((child => {
|
|
||||||
return child.text
|
|
||||||
})).join('\n').trim()
|
|
||||||
: block.text
|
|
||||||
|
|
||||||
const matches = inlineRules.html_tag.exec(text)
|
const matches = inlineRules.html_tag.exec(text)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const tag = matches[3]
|
const tag = matches[3]
|
||||||
@ -83,9 +42,11 @@ const htmlBlock = ContentState => {
|
|||||||
block.functionType = 'html'
|
block.functionType = 'html'
|
||||||
block.text = htmlContent
|
block.text = htmlContent
|
||||||
block.children = []
|
block.children = []
|
||||||
const codeContainer = this.createCodeInHtml(htmlContent)
|
const { preBlock, preview } = this.createPreAndPreview('html', htmlContent)
|
||||||
this.appendChild(block, codeContainer)
|
this.appendChild(block, preBlock)
|
||||||
return codeContainer.children[0] // preBlock
|
this.appendChild(block, preview)
|
||||||
|
|
||||||
|
return preBlock // preBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateHtmlBlock = function (block) {
|
ContentState.prototype.updateHtmlBlock = function (block) {
|
||||||
|
@ -63,7 +63,6 @@ class ContentState {
|
|||||||
this.exemption = new Set()
|
this.exemption = new Set()
|
||||||
this.blocks = [ this.createBlockP() ]
|
this.blocks = [ this.createBlockP() ]
|
||||||
this.stateRender = new StateRender(muya)
|
this.stateRender = new StateRender(muya)
|
||||||
this.codeBlocks = new Map()
|
|
||||||
this.renderRange = [ null, null ]
|
this.renderRange = [ null, null ]
|
||||||
this.currentCursor = null
|
this.currentCursor = null
|
||||||
// you'll select the outmost block of current cursor when you click the front icon.
|
// you'll select the outmost block of current cursor when you click the front icon.
|
||||||
@ -183,24 +182,32 @@ class ContentState {
|
|||||||
* A block in Mark Text present a paragraph(block syntax in GFM) or a line in paragraph.
|
* A block in Mark Text present a paragraph(block syntax in GFM) or a line in paragraph.
|
||||||
* a `span` block must in a `p block` or `pre block` and `p block`'s children must be `span` blocks.
|
* a `span` block must in a `p block` or `pre block` and `p block`'s children must be `span` blocks.
|
||||||
*/
|
*/
|
||||||
createBlock (type = 'span', text = '', editable = true) {
|
createBlock (type = 'span', extras = {}) {
|
||||||
const key = getUniqueId()
|
const key = getUniqueId()
|
||||||
return {
|
const blockData = {
|
||||||
key,
|
key,
|
||||||
|
text: '',
|
||||||
type,
|
type,
|
||||||
text,
|
editable: true,
|
||||||
editable,
|
|
||||||
parent: null,
|
parent: null,
|
||||||
preSibling: null,
|
preSibling: null,
|
||||||
nextSibling: null,
|
nextSibling: null,
|
||||||
children: []
|
children: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// give span block a default functionType `paragraphContent`
|
||||||
|
if (type === 'span' && !extras.functionType) {
|
||||||
|
blockData.functionType = 'paragraphContent'
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(blockData, extras)
|
||||||
|
return blockData
|
||||||
}
|
}
|
||||||
|
|
||||||
createBlockP (text = '') {
|
createBlockP (text = '') {
|
||||||
const pBlock = this.createBlock('p')
|
const pBlock = this.createBlock('p')
|
||||||
const lineBlock = this.createBlock('span', text)
|
const contentBlock = this.createBlock('span', { text })
|
||||||
this.appendChild(pBlock, lineBlock)
|
this.appendChild(pBlock, contentBlock)
|
||||||
return pBlock
|
return pBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const inputCtrl = ContentState => {
|
|||||||
// Input @ to quick insert paragraph
|
// Input @ to quick insert paragraph
|
||||||
ContentState.prototype.checkQuickInsert = function (block) {
|
ContentState.prototype.checkQuickInsert = function (block) {
|
||||||
const { type, text, functionType } = block
|
const { type, text, functionType } = block
|
||||||
if (type !== 'span' || functionType) return false
|
if (type !== 'span' || functionType !== 'paragraphContent') return false
|
||||||
return /^@\S*$/.test(text)
|
return /^@\S*$/.test(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +88,7 @@ const inputCtrl = ContentState => {
|
|||||||
const block = this.getBlock(key)
|
const block = this.getBlock(key)
|
||||||
const paragraph = document.querySelector(`#${key}`)
|
const paragraph = document.querySelector(`#${key}`)
|
||||||
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ])
|
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ])
|
||||||
|
|
||||||
let needRender = false
|
let needRender = false
|
||||||
let needRenderAll = false
|
let needRenderAll = false
|
||||||
if (oldStart.key !== oldEnd.key) {
|
if (oldStart.key !== oldEnd.key) {
|
||||||
@ -196,7 +197,26 @@ const inputCtrl = ContentState => {
|
|||||||
if (this.checkNotSameToken(block.text, text)) {
|
if (this.checkNotSameToken(block.text, text)) {
|
||||||
needRender = true
|
needRender = true
|
||||||
}
|
}
|
||||||
|
// Just work for `Shift + Enter` to create a soft and hard line break.
|
||||||
|
if (
|
||||||
|
block.text.endsWith('\n') &&
|
||||||
|
start.offset === text.length &&
|
||||||
|
event.inputType === 'insertText'
|
||||||
|
) {
|
||||||
|
block.text += event.data
|
||||||
|
start.offset++
|
||||||
|
end.offset++
|
||||||
|
} else if (
|
||||||
|
block.text.length === oldStart.offset &&
|
||||||
|
block.text[oldStart.offset - 2] === '\n' &&
|
||||||
|
event.inputType === 'deleteContentBackward'
|
||||||
|
) {
|
||||||
|
block.text = block.text.substring(0, oldStart.offset - 1)
|
||||||
|
start.offset = block.text.length
|
||||||
|
end.offset = block.text.length
|
||||||
|
} else {
|
||||||
block.text = text
|
block.text = text
|
||||||
|
}
|
||||||
if (beginRules['reference_definition'].test(text)) {
|
if (beginRules['reference_definition'].test(text)) {
|
||||||
needRenderAll = true
|
needRenderAll = true
|
||||||
}
|
}
|
||||||
@ -226,7 +246,6 @@ const inputCtrl = ContentState => {
|
|||||||
// Update preview content of math block
|
// Update preview content of math block
|
||||||
if (block && block.type === 'span' && block.functionType === 'codeLine') {
|
if (block && block.type === 'span' && block.functionType === 'codeLine') {
|
||||||
needRender = true
|
needRender = true
|
||||||
this.updateCodeBlocks(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cursor = { start, end }
|
this.cursor = { start, end }
|
||||||
|
@ -68,12 +68,19 @@ const paragraphCtrl = ContentState => {
|
|||||||
ContentState.prototype.handleFrontMatter = function () {
|
ContentState.prototype.handleFrontMatter = function () {
|
||||||
const firstBlock = this.blocks[0]
|
const firstBlock = this.blocks[0]
|
||||||
if (firstBlock.type === 'pre' && firstBlock.functionType === 'frontmatter') return
|
if (firstBlock.type === 'pre' && firstBlock.functionType === 'frontmatter') return
|
||||||
const frontMatter = this.createBlock('pre')
|
const lang = 'yaml'
|
||||||
const codeBlock = this.createBlock('code')
|
const frontMatter = this.createBlock('pre', {
|
||||||
const emptyLine = this.createBlock('span')
|
functionType: 'frontmatter',
|
||||||
frontMatter.lang = codeBlock.lang = emptyLine.lang = 'yaml'
|
lang
|
||||||
emptyLine.functionType = 'codeLine'
|
})
|
||||||
frontMatter.functionType = 'frontmatter'
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
const emptyLine = this.createBlock('span', {
|
||||||
|
functionType: 'codeLine',
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, emptyLine)
|
this.appendChild(codeBlock, emptyLine)
|
||||||
this.appendChild(frontMatter, codeBlock)
|
this.appendChild(frontMatter, codeBlock)
|
||||||
this.insertBefore(frontMatter, firstBlock)
|
this.insertBefore(frontMatter, firstBlock)
|
||||||
@ -87,14 +94,13 @@ const paragraphCtrl = ContentState => {
|
|||||||
|
|
||||||
ContentState.prototype.handleListMenu = function (paraType, insertMode) {
|
ContentState.prototype.handleListMenu = function (paraType, insertMode) {
|
||||||
const { start, end, affiliation } = this.selectionChange(this.cursor)
|
const { start, end, affiliation } = this.selectionChange(this.cursor)
|
||||||
const { orderListMarker, bulletListMarker } = this
|
const { orderListMarker, bulletListMarker, preferLooseListItem } = this
|
||||||
const [blockType, listType] = paraType.split('-')
|
const [blockType, listType] = paraType.split('-')
|
||||||
const isListed = affiliation.slice(0, 3).filter(b => /ul|ol/.test(b.type))
|
const isListed = affiliation.slice(0, 3).filter(b => /ul|ol/.test(b.type))
|
||||||
const { preferLooseListItem } = this
|
|
||||||
|
|
||||||
if (isListed.length && !insertMode) {
|
if (isListed.length && !insertMode) {
|
||||||
const listBlock = isListed[0]
|
const listBlock = isListed[0]
|
||||||
if (listType === isListed[0].listType) {
|
if (listType === listBlock.listType) {
|
||||||
const listItems = listBlock.children
|
const listItems = listBlock.children
|
||||||
listItems.forEach(listItem => {
|
listItems.forEach(listItem => {
|
||||||
listItem.children.forEach(itemParagraph => {
|
listItem.children.forEach(itemParagraph => {
|
||||||
@ -214,41 +220,74 @@ const paragraphCtrl = ContentState => {
|
|||||||
if (affiliation.length && affiliation[0].type === 'pre' && /code/.test(affiliation[0].functionType)) {
|
if (affiliation.length && affiliation[0].type === 'pre' && /code/.test(affiliation[0].functionType)) {
|
||||||
const preBlock = affiliation[0]
|
const preBlock = affiliation[0]
|
||||||
const codeLines = preBlock.children[1].children
|
const codeLines = preBlock.children[1].children
|
||||||
this.codeBlocks.delete(preBlock.key)
|
|
||||||
preBlock.type = 'p'
|
preBlock.type = 'p'
|
||||||
preBlock.children = []
|
preBlock.children = []
|
||||||
|
|
||||||
|
const newParagraphBlock = this.createBlockP(codeLines.map(l => l.text).join('\n'))
|
||||||
|
this.insertBefore(newParagraphBlock, preBlock)
|
||||||
|
|
||||||
|
this.removeBlock(preBlock)
|
||||||
|
const { start, end } = this.cursor
|
||||||
|
|
||||||
|
const key = newParagraphBlock.children[0].key
|
||||||
|
let startOffset = 0
|
||||||
|
let endOffset = 0
|
||||||
|
let startStop = false
|
||||||
|
let endStop = false
|
||||||
for (const line of codeLines) {
|
for (const line of codeLines) {
|
||||||
delete line.lang
|
if (line.key !== start.key && !startStop) {
|
||||||
delete line.functionType
|
startOffset += line.text.length + 1
|
||||||
this.appendChild(preBlock, line)
|
} else {
|
||||||
|
startOffset += start.offset
|
||||||
|
startStop = true
|
||||||
|
}
|
||||||
|
if (line.key !== end.key && !endStop) {
|
||||||
|
endOffset += line.text.length + 1
|
||||||
|
} else {
|
||||||
|
endOffset += end.offset
|
||||||
|
endStop = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete preBlock.lang
|
|
||||||
delete preBlock.functionType
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: this.cursor.start,
|
start: { key, offset: startOffset },
|
||||||
end: this.cursor.end
|
end: { key, offset: endOffset }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (start.key === end.key) {
|
if (start.key === end.key) {
|
||||||
if (startBlock.type === 'span') {
|
if (startBlock.type === 'span') {
|
||||||
startBlock = this.getParent(startBlock)
|
const anchorBlock = this.getParent(startBlock)
|
||||||
startBlock.type = 'pre'
|
const lang = ''
|
||||||
const codeBlock = this.createBlock('code')
|
const preBlock = this.createBlock('pre', {
|
||||||
const inputBlock = this.createBlock('span', '')
|
functionType: 'fencecode',
|
||||||
inputBlock.functionType = 'languageInput'
|
lang
|
||||||
startBlock.functionType = 'fencecode'
|
|
||||||
startBlock.lang = codeBlock.lang = ''
|
|
||||||
const codeLines = startBlock.children
|
|
||||||
startBlock.children = []
|
|
||||||
codeLines.forEach(line => {
|
|
||||||
line.functionType = 'codeLine'
|
|
||||||
line.lang = ''
|
|
||||||
this.appendChild(codeBlock, line)
|
|
||||||
})
|
})
|
||||||
this.appendChild(startBlock, inputBlock)
|
|
||||||
this.appendChild(startBlock, codeBlock)
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputBlock = this.createBlock('span', {
|
||||||
|
functionType: 'languageInput'
|
||||||
|
})
|
||||||
|
|
||||||
|
const codes = startBlock.text.split('\n')
|
||||||
|
|
||||||
|
for (const code of codes) {
|
||||||
|
const codeLine = this.createBlock('span', {
|
||||||
|
text: code,
|
||||||
|
functionType: 'codeLine',
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
this.appendChild(codeBlock, codeLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.appendChild(preBlock, inputBlock)
|
||||||
|
this.appendChild(preBlock, codeBlock)
|
||||||
|
this.insertBefore(preBlock, anchorBlock)
|
||||||
|
|
||||||
|
this.removeBlock(anchorBlock)
|
||||||
|
|
||||||
const { key } = inputBlock
|
const { key } = inputBlock
|
||||||
const offset = 0
|
const offset = 0
|
||||||
|
|
||||||
@ -266,21 +305,31 @@ const paragraphCtrl = ContentState => {
|
|||||||
const { parent, startIndex, endIndex } = this.getCommonParent()
|
const { parent, startIndex, endIndex } = this.getCommonParent()
|
||||||
const children = parent ? parent.children : this.blocks
|
const children = parent ? parent.children : this.blocks
|
||||||
const referBlock = children[endIndex]
|
const referBlock = children[endIndex]
|
||||||
const preBlock = this.createBlock('pre')
|
const lang = ''
|
||||||
const codeBlock = this.createBlock('code')
|
const preBlock = this.createBlock('pre', {
|
||||||
preBlock.functionType = 'fencecode'
|
functionType: 'fencecode',
|
||||||
preBlock.lang = codeBlock.lang = ''
|
lang
|
||||||
|
})
|
||||||
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
|
||||||
const listIndentation = this.listIndentation
|
const listIndentation = this.listIndentation
|
||||||
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate()
|
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate()
|
||||||
|
|
||||||
markdown.split(LINE_BREAKS_REG).forEach(text => {
|
markdown.split(LINE_BREAKS_REG).forEach(text => {
|
||||||
const codeLine = this.createBlock('span', text)
|
const codeLine = this.createBlock('span', {
|
||||||
codeLine.lang = ''
|
text,
|
||||||
codeLine.functionType = 'codeLine'
|
lang,
|
||||||
|
functionType: 'codeLine'
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
this.appendChild(codeBlock, codeLine)
|
||||||
})
|
})
|
||||||
const inputBlock = this.createBlock('span', '')
|
const inputBlock = this.createBlock('span', {
|
||||||
inputBlock.functionType = 'languageInput'
|
functionType: 'languageInput'
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(preBlock, inputBlock)
|
this.appendChild(preBlock, inputBlock)
|
||||||
this.appendChild(preBlock, codeBlock)
|
this.appendChild(preBlock, codeBlock)
|
||||||
this.insertAfter(preBlock, referBlock)
|
this.insertAfter(preBlock, referBlock)
|
||||||
@ -392,7 +441,7 @@ const paragraphCtrl = ContentState => {
|
|||||||
ContentState.prototype.updateParagraph = function (paraType, insertMode = false) {
|
ContentState.prototype.updateParagraph = function (paraType, insertMode = false) {
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
const block = this.getBlock(start.key)
|
const block = this.getBlock(start.key)
|
||||||
const { type, text, functionType } = block
|
const { text } = block
|
||||||
|
|
||||||
switch (paraType) {
|
switch (paraType) {
|
||||||
case 'front-matter': {
|
case 'front-matter': {
|
||||||
@ -445,15 +494,19 @@ const paragraphCtrl = ContentState => {
|
|||||||
case 'degrade heading':
|
case 'degrade heading':
|
||||||
case 'paragraph': {
|
case 'paragraph': {
|
||||||
if (start.key !== end.key) return
|
if (start.key !== end.key) return
|
||||||
const [, hash, partText] = /(^#*\s*)(.*)/.exec(text)
|
const headingStyle = DEFAULT_TURNDOWN_CONFIG.headingStyle
|
||||||
|
const parent = this.getParent(block)
|
||||||
|
// \u00A0 is
|
||||||
|
const [, hash, partText] = /(^ {0,3}#*[ \u00A0]*)([\s\S]*)/.exec(text)
|
||||||
let newLevel = 0 // 1, 2, 3, 4, 5, 6
|
let newLevel = 0 // 1, 2, 3, 4, 5, 6
|
||||||
let newType = 'p'
|
let newType = 'p'
|
||||||
let key
|
let key
|
||||||
|
|
||||||
if (/\d/.test(paraType)) {
|
if (/\d/.test(paraType)) {
|
||||||
newLevel = Number(paraType.split(/\s/)[1])
|
newLevel = Number(paraType.split(/\s/)[1])
|
||||||
newType = `h${newLevel}`
|
newType = `h${newLevel}`
|
||||||
} else if (paraType === 'upgrade heading' || paraType === 'degrade heading') {
|
} else if (paraType === 'upgrade heading' || paraType === 'degrade heading') {
|
||||||
const currentLevel = getCurrentLevel(type)
|
const currentLevel = getCurrentLevel(parent.type)
|
||||||
newLevel = currentLevel
|
newLevel = currentLevel
|
||||||
if (paraType === 'upgrade heading' && currentLevel !== 1) {
|
if (paraType === 'upgrade heading' && currentLevel !== 1) {
|
||||||
if (currentLevel === 0) newLevel = 6
|
if (currentLevel === 0) newLevel = 6
|
||||||
@ -475,48 +528,35 @@ const paragraphCtrl = ContentState => {
|
|||||||
? '#'.repeat(newLevel) + `${String.fromCharCode(160)}${partText}` // code: 160
|
? '#'.repeat(newLevel) + `${String.fromCharCode(160)}${partText}` // code: 160
|
||||||
: partText
|
: partText
|
||||||
|
|
||||||
if (block.type === 'span' && newType !== 'p') {
|
// No change
|
||||||
const header = this.createBlock(newType, newText)
|
if (newType === 'p' && parent.type === newType) {
|
||||||
header.headingStyle = DEFAULT_TURNDOWN_CONFIG.headingStyle
|
return
|
||||||
key = header.key
|
}
|
||||||
const parent = this.getParent(block)
|
// No change
|
||||||
if (this.isOnlyChild(block)) {
|
if (newType !== 'p' && parent.type === newType && parent.headingStyle === headingStyle) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newType !== 'p') {
|
||||||
|
const header = this.createBlock(newType, {
|
||||||
|
headingStyle
|
||||||
|
})
|
||||||
|
const headerContent = this.createBlock('span', {
|
||||||
|
text: headingStyle === 'atx'? newText.replace(/\n/g, ' ') : newText,
|
||||||
|
functionType: headingStyle === 'atx'? 'atxLine' : 'paragraphContent'
|
||||||
|
})
|
||||||
|
this.appendChild(header, headerContent)
|
||||||
|
key = headerContent.key
|
||||||
|
|
||||||
this.insertBefore(header, parent)
|
this.insertBefore(header, parent)
|
||||||
this.removeBlock(parent)
|
this.removeBlock(parent)
|
||||||
} else if (this.isFirstChild(block)) {
|
|
||||||
this.insertBefore(header, parent)
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else if (this.isLastChild(block)) {
|
|
||||||
this.insertAfter(header, parent)
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else {
|
} else {
|
||||||
const pBlock = this.createBlock('p')
|
|
||||||
let nextSibling = this.getNextSibling(block)
|
|
||||||
while (nextSibling) {
|
|
||||||
this.appendChild(pBlock, nextSibling)
|
|
||||||
const oldNextSibling = nextSibling
|
|
||||||
nextSibling = this.getNextSibling(nextSibling)
|
|
||||||
this.removeBlock(oldNextSibling)
|
|
||||||
}
|
|
||||||
this.removeBlock(block)
|
|
||||||
this.insertAfter(header, parent)
|
|
||||||
this.insertAfter(pBlock, header)
|
|
||||||
}
|
|
||||||
} else if (/^h/.test(block.type) && newType === 'p') {
|
|
||||||
const pBlock = this.createBlockP(newText)
|
const pBlock = this.createBlockP(newText)
|
||||||
key = pBlock.children[0].key
|
key = pBlock.children[0].key
|
||||||
this.insertAfter(pBlock, block)
|
this.insertAfter(pBlock, parent)
|
||||||
this.removeBlock(block)
|
this.removeBlock(parent)
|
||||||
} else if (type === 'span' && !functionType && newType === 'p') {
|
|
||||||
// The original is a paragraph, the new type is also paragraph, no need to update.
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
const newHeader = this.createBlock(newType, newText)
|
|
||||||
newHeader.headingStyle = DEFAULT_TURNDOWN_CONFIG.headingStyle
|
|
||||||
key = newHeader.key
|
|
||||||
this.insertAfter(newHeader, block)
|
|
||||||
this.removeBlock(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset: startOffset },
|
start: { key, offset: startOffset },
|
||||||
end: { key, offset: endOffset }
|
end: { key, offset: endOffset }
|
||||||
@ -527,7 +567,11 @@ const paragraphCtrl = ContentState => {
|
|||||||
const pBlock = this.createBlockP()
|
const pBlock = this.createBlockP()
|
||||||
const archor = block.type === 'span' ? this.getParent(block) : block
|
const archor = block.type === 'span' ? this.getParent(block) : block
|
||||||
const hrBlock = this.createBlock('hr')
|
const hrBlock = this.createBlock('hr')
|
||||||
hrBlock.text = '---'
|
const thematicContent = this.createBlock('span', {
|
||||||
|
functionType: 'thematicBreakLine',
|
||||||
|
text: '---'
|
||||||
|
})
|
||||||
|
this.appendChild(hrBlock, thematicContent)
|
||||||
this.insertAfter(hrBlock, archor)
|
this.insertAfter(hrBlock, archor)
|
||||||
this.insertAfter(pBlock, hrBlock)
|
this.insertAfter(pBlock, hrBlock)
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@ -562,42 +606,23 @@ const paragraphCtrl = ContentState => {
|
|||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
// if cursor is not in one line or paragraph, can not insert paragraph
|
// if cursor is not in one line or paragraph, can not insert paragraph
|
||||||
if (start.key !== end.key) return
|
if (start.key !== end.key) return
|
||||||
let block = this.getBlock(start.key)
|
const block = this.getBlock(start.key)
|
||||||
|
let anchor = null
|
||||||
if (outMost) {
|
if (outMost) {
|
||||||
block = this.findOutMostBlock(block)
|
anchor = this.findOutMostBlock(block)
|
||||||
} else if (block.type === 'span' && !block.functionType) {
|
} else {
|
||||||
block = this.getParent(block)
|
anchor = this.getAnchor(block)
|
||||||
} else if (block.type === 'span' && block.functionType === 'codeLine') {
|
}
|
||||||
const preBlock = this.getParent(this.getParent(block))
|
|
||||||
switch (preBlock.functionType) {
|
|
||||||
case 'fencecode':
|
|
||||||
case 'indentcode':
|
|
||||||
case 'frontmatter': {
|
|
||||||
// You can not insert paragraph before frontmatter
|
// You can not insert paragraph before frontmatter
|
||||||
if (preBlock.functionType === 'frontmatter' && location === 'before') {
|
if (!anchor || anchor && anchor.functionType === 'frontmatter' && location === 'before') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
block = preBlock
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'html': {
|
|
||||||
block = this.getParent(this.getParent(preBlock))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'multiplemath': {
|
|
||||||
block = this.getParent(preBlock)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (/th|td/.test(block.type)) {
|
|
||||||
// get figure block from table cell
|
|
||||||
block = this.getParent(this.getParent(this.getParent(this.getParent(block))))
|
|
||||||
}
|
|
||||||
const newBlock = this.createBlockP(text)
|
const newBlock = this.createBlockP(text)
|
||||||
if (location === 'before') {
|
if (location === 'before') {
|
||||||
this.insertBefore(newBlock, block)
|
this.insertBefore(newBlock, anchor)
|
||||||
} else {
|
} else {
|
||||||
this.insertAfter(newBlock, block)
|
this.insertAfter(newBlock, anchor)
|
||||||
}
|
}
|
||||||
const { key } = newBlock.children[0]
|
const { key } = newBlock.children[0]
|
||||||
const offset = text.length
|
const offset = text.length
|
||||||
@ -634,12 +659,7 @@ const paragraphCtrl = ContentState => {
|
|||||||
// if copied block has pre block: html, multiplemath, vega-light, mermaid, flowchart, sequence...
|
// if copied block has pre block: html, multiplemath, vega-light, mermaid, flowchart, sequence...
|
||||||
const copiedBlock = this.copyBlock(startOutmostBlock)
|
const copiedBlock = this.copyBlock(startOutmostBlock)
|
||||||
this.insertAfter(copiedBlock, startOutmostBlock)
|
this.insertAfter(copiedBlock, startOutmostBlock)
|
||||||
if (copiedBlock.type === 'figure' && copiedBlock.functionType) {
|
|
||||||
const preBlock = this.getPreBlock(copiedBlock)
|
|
||||||
if (preBlock) {
|
|
||||||
this.updateCodeBlocks(preBlock.children[0].children[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const cursorBlock = this.firstInDescendant(copiedBlock)
|
const cursorBlock = this.firstInDescendant(copiedBlock)
|
||||||
// set cursor at the end of the first descendant of the duplicated block.
|
// set cursor at the end of the first descendant of the duplicated block.
|
||||||
const { key, text } = cursorBlock
|
const { key, text } = cursorBlock
|
||||||
|
@ -8,21 +8,33 @@ const pasteCtrl = ContentState => {
|
|||||||
// check paste type: `MERGE` or `NEWLINE`
|
// check paste type: `MERGE` or `NEWLINE`
|
||||||
ContentState.prototype.checkPasteType = function (start, fragment) {
|
ContentState.prototype.checkPasteType = function (start, fragment) {
|
||||||
const fragmentType = fragment.type
|
const fragmentType = fragment.type
|
||||||
if (start.type === 'span') {
|
const parent = this.getParent(start)
|
||||||
start = this.getParent(start)
|
|
||||||
}
|
if (fragmentType === 'p') {
|
||||||
if (fragmentType === 'p') return 'MERGE'
|
return 'MERGE'
|
||||||
if (fragmentType === 'blockquote') return 'NEWLINE'
|
} else if (/^h\d/.test(fragmentType)) {
|
||||||
let parent = this.getParent(start)
|
if (start.text) {
|
||||||
if (parent && parent.type === 'li') parent = this.getParent(parent)
|
|
||||||
let startType = start.type
|
|
||||||
if (start.type === 'p') {
|
|
||||||
startType = parent ? parent.type : startType
|
|
||||||
}
|
|
||||||
if (LIST_REG.test(fragmentType) && LIST_REG.test(startType)) {
|
|
||||||
return 'MERGE'
|
return 'MERGE'
|
||||||
} else {
|
} else {
|
||||||
return startType === fragmentType ? 'MERGE' : 'NEWLINE'
|
return 'NEWLINE'
|
||||||
|
}
|
||||||
|
} else if (LIST_REG.test(fragmentType)) {
|
||||||
|
const listItem = this.getParent(parent)
|
||||||
|
const list = listItem && listItem.type === 'li' ? this.getParent(listItem) : null
|
||||||
|
if (list) {
|
||||||
|
if (
|
||||||
|
list.listType === fragment.listType &&
|
||||||
|
listItem.bulletMarkerOrDelimiter === fragment.children[0].bulletMarkerOrDelimiter
|
||||||
|
) {
|
||||||
|
return 'MERGE'
|
||||||
|
} else {
|
||||||
|
return 'NEWLINE'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'NEWLINE'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'NEWLINE'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +149,7 @@ const pasteCtrl = ContentState => {
|
|||||||
startBlock.text = prePartText + line
|
startBlock.text = prePartText + line
|
||||||
} else {
|
} else {
|
||||||
line = i === textList.length - 1 ? line + postPartText : line
|
line = i === textList.length - 1 ? line + postPartText : line
|
||||||
const lineBlock = this.createBlock('span', line)
|
const lineBlock = this.createBlock('span', { text: line })
|
||||||
lineBlock.functionType = startBlock.functionType
|
lineBlock.functionType = startBlock.functionType
|
||||||
lineBlock.lang = startBlock.lang
|
lineBlock.lang = startBlock.lang
|
||||||
this.insertAfter(lineBlock, referenceBlock)
|
this.insertAfter(lineBlock, referenceBlock)
|
||||||
@ -161,7 +173,6 @@ const pasteCtrl = ContentState => {
|
|||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.updateCodeBlocks(startBlock)
|
|
||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,41 +190,12 @@ const pasteCtrl = ContentState => {
|
|||||||
|
|
||||||
// handle copyAsHtml
|
// handle copyAsHtml
|
||||||
if (copyType === 'copyAsHtml') {
|
if (copyType === 'copyAsHtml') {
|
||||||
// already handle code block above
|
|
||||||
if (startBlock.type === 'span' && startBlock.nextSibling) {
|
|
||||||
const afterParagraph = this.createBlock('p')
|
|
||||||
let temp = startBlock
|
|
||||||
const removeCache = []
|
|
||||||
while (temp.nextSibling) {
|
|
||||||
temp = this.getBlock(temp.nextSibling)
|
|
||||||
this.appendChild(afterParagraph, temp)
|
|
||||||
removeCache.push(temp)
|
|
||||||
}
|
|
||||||
removeCache.forEach(b => this.removeBlock(b))
|
|
||||||
this.insertAfter(afterParagraph, parent)
|
|
||||||
startBlock.nextSibling = null
|
|
||||||
}
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'normal': {
|
case 'normal': {
|
||||||
const htmlBlock = this.createBlock('p')
|
const htmlBlock = this.createBlockP(text.trim())
|
||||||
const lines = text.trim().split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
|
||||||
for (const line of lines) {
|
|
||||||
this.appendChild(htmlBlock, line)
|
|
||||||
}
|
|
||||||
if (startBlock.type === 'span') {
|
|
||||||
this.insertAfter(htmlBlock, parent)
|
this.insertAfter(htmlBlock, parent)
|
||||||
} else {
|
|
||||||
this.insertAfter(htmlBlock, startBlock)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
startBlock.type === 'span' && startBlock.text.length === 0 && this.isOnlyChild(startBlock)
|
|
||||||
) {
|
|
||||||
this.removeBlock(parent)
|
this.removeBlock(parent)
|
||||||
}
|
|
||||||
// handler heading
|
// handler heading
|
||||||
if (startBlock.text.length === 0 && startBlock.type !== 'span') {
|
|
||||||
this.removeBlock(startBlock)
|
|
||||||
}
|
|
||||||
this.insertHtmlBlock(htmlBlock)
|
this.insertHtmlBlock(htmlBlock)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -222,24 +204,16 @@ const pasteCtrl = ContentState => {
|
|||||||
let htmlBlock = null
|
let htmlBlock = null
|
||||||
|
|
||||||
if (!startBlock.text || lines.length > 1) {
|
if (!startBlock.text || lines.length > 1) {
|
||||||
htmlBlock = this.createBlock('p')
|
htmlBlock = this.createBlockP((startBlock.text ? lines.slice(1) : lines).join('\n'))
|
||||||
;(startBlock.text ? lines.slice(1) : lines).map(line => this.createBlock('span', line))
|
|
||||||
.forEach(l => {
|
|
||||||
this.appendChild(htmlBlock, l)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (htmlBlock) {
|
if (htmlBlock) {
|
||||||
if (startBlock.type === 'span') {
|
|
||||||
this.insertAfter(htmlBlock, parent)
|
this.insertAfter(htmlBlock, parent)
|
||||||
} else {
|
|
||||||
this.insertAfter(htmlBlock, startBlock)
|
|
||||||
}
|
|
||||||
this.insertHtmlBlock(htmlBlock)
|
this.insertHtmlBlock(htmlBlock)
|
||||||
}
|
}
|
||||||
if (startBlock.text) {
|
if (startBlock.text) {
|
||||||
appendHtml(lines[0])
|
appendHtml(lines[0])
|
||||||
} else {
|
} else {
|
||||||
this.removeBlock(startBlock.type === 'span' ? parent : startBlock)
|
this.removeBlock(parent)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -300,7 +274,6 @@ const pasteCtrl = ContentState => {
|
|||||||
if (liChildren[0].type === 'p') {
|
if (liChildren[0].type === 'p') {
|
||||||
// TODO @JOCS
|
// TODO @JOCS
|
||||||
startBlock.text += liChildren[0].children[0].text
|
startBlock.text += liChildren[0].children[0].text
|
||||||
liChildren[0].children.slice(1).forEach(c => this.appendChild(parent, c))
|
|
||||||
const tail = liChildren.slice(1)
|
const tail = liChildren.slice(1)
|
||||||
if (tail.length) {
|
if (tail.length) {
|
||||||
tail.forEach(t => {
|
tail.forEach(t => {
|
||||||
@ -323,33 +296,21 @@ const pasteCtrl = ContentState => {
|
|||||||
this.insertAfter(block, target)
|
this.insertAfter(block, target)
|
||||||
target = block
|
target = block
|
||||||
})
|
})
|
||||||
} else {
|
} else if (firstFragment.type === 'p' || /^h\d/.test(firstFragment.type)) {
|
||||||
if (firstFragment.type === 'p') {
|
const text = firstFragment.children[0].text
|
||||||
if (/^h\d$/.test(startBlock.type)) {
|
const lines = text.split('\n')
|
||||||
// handle paste into header
|
let target = parent
|
||||||
startBlock.text += firstFragment.children[0].text
|
if (parent.headingStyle === 'atx') {
|
||||||
if (firstFragment.children.length > 1) {
|
startBlock.text += lines[0]
|
||||||
const newParagraph = this.createBlock('p')
|
if (lines.length > 1) {
|
||||||
firstFragment.children.slice(1).forEach(line => {
|
const pBlock = this.createBlockP(lines.slice(1).join('\n'))
|
||||||
this.appendChild(newParagraph, line)
|
this.insertAfter(parent, pBlock)
|
||||||
})
|
target = pBlock
|
||||||
this.insertAfter(newParagraph, startBlock)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startBlock.text += firstFragment.children[0].text
|
startBlock.text += text
|
||||||
firstFragment.children.slice(1).forEach(line => {
|
|
||||||
if (startBlock.functionType) line.functionType = startBlock.functionType
|
|
||||||
if (startBlock.lang) line.lang = startBlock.lang
|
|
||||||
this.appendChild(parent, line)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (/^h\d$/.test(firstFragment.type)) {
|
|
||||||
startBlock.text += firstFragment.text.split(/\s+/)[1]
|
|
||||||
} else {
|
|
||||||
startBlock.text += firstFragment.text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = /^h\d$/.test(startBlock.type) ? startBlock : parent
|
|
||||||
tailFragments.forEach(block => {
|
tailFragments.forEach(block => {
|
||||||
this.insertAfter(block, target)
|
this.insertAfter(block, target)
|
||||||
target = block
|
target = block
|
||||||
@ -358,14 +319,13 @@ const pasteCtrl = ContentState => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'NEWLINE': {
|
case 'NEWLINE': {
|
||||||
let target = startBlock.type === 'span' ? parent : startBlock
|
let target = parent
|
||||||
stateFragments.forEach(block => {
|
stateFragments.forEach(block => {
|
||||||
this.insertAfter(block, target)
|
this.insertAfter(block, target)
|
||||||
target = block
|
target = block
|
||||||
})
|
})
|
||||||
if (startBlock.text.length === 0) {
|
if (startBlock.text.length === 0) {
|
||||||
this.removeBlock(startBlock)
|
this.removeBlock(parent)
|
||||||
if (this.isOnlyChild(startBlock) && startBlock.type === 'span') this.removeBlock(parent)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -380,10 +340,7 @@ const pasteCtrl = ContentState => {
|
|||||||
offset = startBlock.text.length - cacheText.length
|
offset = startBlock.text.length - cacheText.length
|
||||||
cursorBlock = startBlock
|
cursorBlock = startBlock
|
||||||
}
|
}
|
||||||
// TODO @Jocs duplicate with codes in updateCtrl.js
|
|
||||||
if (cursorBlock && cursorBlock.type === 'span' && cursorBlock.functionType === 'codeLine') {
|
|
||||||
this.updateCodeBlocks(cursorBlock)
|
|
||||||
}
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: {
|
||||||
key, offset
|
key, offset
|
||||||
|
@ -18,7 +18,9 @@ const tableBlockCtrl = ContentState => {
|
|||||||
const rowBlock = this.createBlock('tr')
|
const rowBlock = this.createBlock('tr')
|
||||||
i === 0 ? this.appendChild(tHead, rowBlock) : this.appendChild(tBody, rowBlock)
|
i === 0 ? this.appendChild(tHead, rowBlock) : this.appendChild(tBody, rowBlock)
|
||||||
for (j = 0; j < columns; j++) {
|
for (j = 0; j < columns; j++) {
|
||||||
const cell = this.createBlock(i === 0 ? 'th' : 'td', headerTexts && i === 0 ? headerTexts[j] : '')
|
const cell = this.createBlock(i === 0 ? 'th' : 'td', {
|
||||||
|
text: headerTexts && i === 0 ? headerTexts[j] : ''
|
||||||
|
})
|
||||||
this.appendChild(rowBlock, cell)
|
this.appendChild(rowBlock, cell)
|
||||||
cell.align = ''
|
cell.align = ''
|
||||||
cell.column = j
|
cell.column = j
|
||||||
@ -41,24 +43,18 @@ const tableBlockCtrl = ContentState => {
|
|||||||
|
|
||||||
ContentState.prototype.getAnchor = function (block) {
|
ContentState.prototype.getAnchor = function (block) {
|
||||||
const { type, functionType } = block
|
const { type, functionType } = block
|
||||||
switch (true) {
|
switch (type) {
|
||||||
case /^span$/.test(type): {
|
case 'span':
|
||||||
if (!functionType) {
|
if (functionType === 'codeLine') {
|
||||||
return this.closest(block, 'p')
|
|
||||||
} else if (functionType === 'codeLine') {
|
|
||||||
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
||||||
|
} else {
|
||||||
|
return this.getParent(block)
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
case 'th':
|
||||||
case /^(th|td)$/.test(type): {
|
case 'td':
|
||||||
return this.closest(block, 'figure')
|
return this.closest(block, 'figure')
|
||||||
}
|
|
||||||
case /^h\d$/.test(type): {
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
case /hr/.test(type): {
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -74,7 +70,7 @@ const tableBlockCtrl = ContentState => {
|
|||||||
|
|
||||||
if (!anchor) return
|
if (!anchor) return
|
||||||
this.insertAfter(figureBlock, anchor)
|
this.insertAfter(figureBlock, anchor)
|
||||||
if (anchor.type === 'p' && !endBlock.text) {
|
if (/p|h\d/.test(anchor.type) && !endBlock.text) {
|
||||||
this.removeBlock(anchor)
|
this.removeBlock(anchor)
|
||||||
}
|
}
|
||||||
this.appendChild(figureBlock, table)
|
this.appendChild(figureBlock, table)
|
||||||
|
@ -3,13 +3,14 @@ import { conflict } from '../utils'
|
|||||||
import { CLASS_OR_ID } from '../config'
|
import { CLASS_OR_ID } from '../config'
|
||||||
|
|
||||||
const INLINE_UPDATE_FRAGMENTS = [
|
const INLINE_UPDATE_FRAGMENTS = [
|
||||||
'^([*+-]\\s)', // Bullet list
|
'(?:^|\n) {0,3}([*+-] {1,4})', // Bullet list
|
||||||
'^(\\[[x\\s]{1}\\]\\s)', // Task list
|
'(?:^|\n)(\\[[x ]{1}\\] {1,4})', // Task list
|
||||||
'^(\\d{1,9}(?:\\.|\\))\\s)', // Order list
|
'(?:^|\n) {0,3}(\\d{1,9}(?:\\.|\\)) {1,4})', // Order list
|
||||||
'^\\s{0,3}(#{1,6})(?=\\s{1,}|$)', // ATX headings
|
'(?:^|\n) {0,3}(#{1,6})(?=\\s{1,}|$)', // ATX headings
|
||||||
'^\\s{0,3}(\\={3,}|\\-{3,})(?=\\s{1,}|$)', // Setext headings
|
'^(?:[\\s\\S]+?)\\n {0,3}(\\={3,}|\\-{3,})(?= {1,}|$)', // Setext headings **match from beginning**
|
||||||
'^(>).+', // Block quote
|
'(?:^|\n) {0,3}(>).+', // Block quote
|
||||||
'^\\s{0,3}((?:\\*\\s*\\*\\s*\\*|-\\s*-\\s*-|_\\s*_\\s*_)[\\s\\*\\-\\_]*)$' // Thematic break
|
'^( {4,})', // Indent code **match from beginning**
|
||||||
|
'(?:^|\n) {0,3}((?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*)$' // Thematic break
|
||||||
]
|
]
|
||||||
|
|
||||||
const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')
|
const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')
|
||||||
@ -21,11 +22,6 @@ const updateCtrl = ContentState => {
|
|||||||
const block = this.getBlock(id)
|
const block = this.getBlock(id)
|
||||||
block.checked = checked
|
block.checked = checked
|
||||||
checkbox.classList.toggle(CLASS_OR_ID['AG_CHECKBOX_CHECKED'])
|
checkbox.classList.toggle(CLASS_OR_ID['AG_CHECKBOX_CHECKED'])
|
||||||
// this.render()
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentState.prototype.checkSameLooseType = function (list, isLooseType) {
|
|
||||||
return list.children[0].isLooseListItem === isLooseType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.checkSameMarkerOrDelimiter = function (list, markerOrDelimiter) {
|
ContentState.prototype.checkSameMarkerOrDelimiter = function (list, markerOrDelimiter) {
|
||||||
@ -40,9 +36,10 @@ const updateCtrl = ContentState => {
|
|||||||
const endBlock = this.getBlock(cEnd ? cEnd.key : focus.key)
|
const endBlock = this.getBlock(cEnd ? cEnd.key : focus.key)
|
||||||
const startOffset = cStart ? cStart.offset : anchor.offset
|
const startOffset = cStart ? cStart.offset : anchor.offset
|
||||||
const endOffset = cEnd ? cEnd.offset : focus.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)) {
|
for (const token of tokenizer(startBlock.text, undefined, undefined, labels)) {
|
||||||
if (token.type === 'text') continue
|
if (NO_NEED_TOKEN_REG.test(token.type)) continue
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
const textLen = startBlock.text.length
|
const textLen = startBlock.text.length
|
||||||
if (
|
if (
|
||||||
@ -52,7 +49,7 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const token of tokenizer(endBlock.text, undefined, undefined, labels)) {
|
for (const token of tokenizer(endBlock.text, undefined, undefined, labels)) {
|
||||||
if (token.type === 'text') continue
|
if (NO_NEED_TOKEN_REG.test(token.type)) continue
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
const textLen = endBlock.text.length
|
const textLen = endBlock.text.length
|
||||||
if (
|
if (
|
||||||
@ -65,34 +62,35 @@ const updateCtrl = ContentState => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* block must be span block.
|
||||||
|
*/
|
||||||
ContentState.prototype.checkInlineUpdate = function (block) {
|
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||||
// table cell can not have blocks in it
|
// table cell can not have blocks in it
|
||||||
if (/th|td|figure/.test(block.type)) return false
|
if (/th|td|figure/.test(block.type)) return false
|
||||||
if (/codeLine|languageInput/.test(block.functionType)) return false
|
if (/codeLine|languageInput/.test(block.functionType)) return false
|
||||||
// line in paragraph can also update to other block. So comment bellow code.
|
|
||||||
// if (block.type === 'span' && block.preSibling) return false
|
|
||||||
const hasPreLine = !!(block.type === 'span' && block.preSibling)
|
|
||||||
let line = null
|
let line = null
|
||||||
const { text } = block
|
const { text } = block
|
||||||
if (block.type === 'span') {
|
if (block.type === 'span') {
|
||||||
line = block
|
line = block
|
||||||
block = this.getParent(block)
|
block = this.getParent(block)
|
||||||
}
|
}
|
||||||
const parent = this.getParent(block)
|
const listItem = this.getParent(block)
|
||||||
const [match, bullet, tasklist, order, atxHeader, setextHeader, blockquote, hr] = text.match(INLINE_UPDATE_REG) || []
|
const [
|
||||||
|
match, bullet, tasklist, order, atxHeader,
|
||||||
|
setextHeader, blockquote, indentCode, hr
|
||||||
|
] = text.match(INLINE_UPDATE_REG) || []
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case (
|
case (!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
||||||
(!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1) ||
|
return this.updateHr(block, hr, line)
|
||||||
(!!setextHeader && !hasPreLine)
|
|
||||||
):
|
|
||||||
return this.updateHr(block, hr || setextHeader)
|
|
||||||
|
|
||||||
case !!bullet:
|
case !!bullet:
|
||||||
return this.updateList(block, 'bullet', bullet, line)
|
return this.updateList(block, 'bullet', bullet, line)
|
||||||
|
|
||||||
// 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':
|
case !!tasklist && listItem && listItem.listItemType === 'bullet':
|
||||||
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
||||||
|
|
||||||
case !!order:
|
case !!order:
|
||||||
@ -101,75 +99,112 @@ const updateCtrl = ContentState => {
|
|||||||
case !!atxHeader:
|
case !!atxHeader:
|
||||||
return this.updateAtxHeader(block, atxHeader, line)
|
return this.updateAtxHeader(block, atxHeader, line)
|
||||||
|
|
||||||
case !!setextHeader && hasPreLine:
|
case !!setextHeader:
|
||||||
return this.updateSetextHeader(block, setextHeader, line)
|
return this.updateSetextHeader(block, setextHeader, line)
|
||||||
|
|
||||||
case !!blockquote:
|
case !!blockquote:
|
||||||
return this.updateBlockQuote(block, line)
|
return this.updateBlockQuote(block, line)
|
||||||
|
|
||||||
|
case !!indentCode:
|
||||||
|
return this.updateIndentCode(block, line)
|
||||||
|
|
||||||
case !match:
|
case !match:
|
||||||
default:
|
default:
|
||||||
return this.updateToParagraph(block)
|
return this.updateToParagraph(block, line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// thematic break
|
// Thematic break
|
||||||
ContentState.prototype.updateHr = function (block, marker) {
|
ContentState.prototype.updateHr = function (block, marker, line) {
|
||||||
if (block.type !== 'hr') {
|
// If the block is already thematic break, no need to update.
|
||||||
block.type = 'hr'
|
if (block.type === 'hr') return null
|
||||||
block.text = marker
|
const text = line.text
|
||||||
block.children.length = 0
|
const lines = text.split('\n')
|
||||||
const { key } = block
|
const preParagraphLines = []
|
||||||
this.cursor.start.key = this.cursor.end.key = key
|
let thematicLine = ''
|
||||||
return block
|
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)
|
||||||
}
|
}
|
||||||
return null
|
}
|
||||||
|
|
||||||
|
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) {
|
ContentState.prototype.updateList = function (block, type, marker = '', line) {
|
||||||
if (block.type === 'span') {
|
|
||||||
block = this.getParent(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanMarker = marker ? marker.trim() : null
|
const cleanMarker = marker ? marker.trim() : null
|
||||||
const { preferLooseListItem } = this
|
const { preferLooseListItem } = this
|
||||||
const parent = this.getParent(block)
|
|
||||||
const wrapperTag = type === 'order' ? 'ol' : 'ul' // `bullet` => `ul` and `order` => `ol`
|
const wrapperTag = type === 'order' ? 'ol' : 'ul' // `bullet` => `ul` and `order` => `ol`
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
const startOffset = start.offset
|
const startOffset = start.offset
|
||||||
const endOffset = end.offset
|
const endOffset = end.offset
|
||||||
const newBlock = this.createBlock('li')
|
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')
|
||||||
|
|
||||||
if (/^h\d$/.test(block.type)) {
|
const preParagraphLines = []
|
||||||
delete block.marker
|
const listItemLines = []
|
||||||
delete block.headingStyle
|
let isPushedListItemLine = false
|
||||||
block.type = 'p'
|
for (const l of lines) {
|
||||||
block.children = []
|
if (LIST_ITEM_REG.test(l) && !isPushedListItemLine) {
|
||||||
const line = this.createBlock('span', block.text.substring(marker.length))
|
listItemLines.push(l.replace(LIST_ITEM_REG, ''))
|
||||||
block.text = ''
|
isPushedListItemLine = true
|
||||||
this.appendChild(block, line)
|
} else if (!isPushedListItemLine) {
|
||||||
|
preParagraphLines.push(l)
|
||||||
} else {
|
} else {
|
||||||
line.text = line.text.substring(marker.length)
|
listItemLines.push(l)
|
||||||
const paragraphBefore = this.createBlock('p')
|
|
||||||
const index = block.children.indexOf(line)
|
|
||||||
if (index !== 0) {
|
|
||||||
const removeCache = []
|
|
||||||
for (const child of block.children) {
|
|
||||||
if (child === line) break
|
|
||||||
removeCache.push(child)
|
|
||||||
}
|
|
||||||
removeCache.forEach(c => {
|
|
||||||
this.removeBlock(c)
|
|
||||||
this.appendChild(paragraphBefore, c)
|
|
||||||
})
|
|
||||||
this.insertBefore(paragraphBefore, block)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 preSibling = this.getPreSibling(block)
|
||||||
const nextSibling = this.getNextSibling(block)
|
const nextSibling = this.getNextSibling(block)
|
||||||
newBlock.listItemType = type
|
newListItemBlock.listItemType = type
|
||||||
newBlock.isLooseListItem = preferLooseListItem
|
newListItemBlock.isLooseListItem = preferLooseListItem
|
||||||
|
|
||||||
let bulletMarkerOrDelimiter
|
let bulletMarkerOrDelimiter
|
||||||
if (type === 'order') {
|
if (type === 'order') {
|
||||||
@ -178,7 +213,7 @@ const updateCtrl = ContentState => {
|
|||||||
const { bulletListMarker } = this
|
const { bulletListMarker } = this
|
||||||
bulletMarkerOrDelimiter = marker ? marker.charAt(0) : bulletListMarker
|
bulletMarkerOrDelimiter = marker ? marker.charAt(0) : bulletListMarker
|
||||||
}
|
}
|
||||||
newBlock.bulletMarkerOrDelimiter = bulletMarkerOrDelimiter
|
newListItemBlock.bulletMarkerOrDelimiter = bulletMarkerOrDelimiter
|
||||||
|
|
||||||
// Special cases for CommonMark 264 and 265: Changing the bullet or ordered list delimiter starts a new list.
|
// Special cases for CommonMark 264 and 265: Changing the bullet or ordered list delimiter starts a new list.
|
||||||
// Same list type or new list
|
// Same list type or new list
|
||||||
@ -188,7 +223,7 @@ const updateCtrl = ContentState => {
|
|||||||
nextSibling &&
|
nextSibling &&
|
||||||
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
||||||
) {
|
) {
|
||||||
this.appendChild(preSibling, newBlock)
|
this.appendChild(preSibling, newListItemBlock)
|
||||||
const partChildren = nextSibling.children.splice(0)
|
const partChildren = nextSibling.children.splice(0)
|
||||||
partChildren.forEach(b => this.appendChild(preSibling, b))
|
partChildren.forEach(b => this.appendChild(preSibling, b))
|
||||||
this.removeBlock(nextSibling)
|
this.removeBlock(nextSibling)
|
||||||
@ -199,7 +234,7 @@ const updateCtrl = ContentState => {
|
|||||||
preSibling &&
|
preSibling &&
|
||||||
this.checkSameMarkerOrDelimiter(preSibling, bulletMarkerOrDelimiter)
|
this.checkSameMarkerOrDelimiter(preSibling, bulletMarkerOrDelimiter)
|
||||||
) {
|
) {
|
||||||
this.appendChild(preSibling, newBlock)
|
this.appendChild(preSibling, newListItemBlock)
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
const isLooseListItem = preSibling.children.some(c => c.isLooseListItem)
|
const isLooseListItem = preSibling.children.some(c => c.isLooseListItem)
|
||||||
preSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
preSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
||||||
@ -207,67 +242,59 @@ const updateCtrl = ContentState => {
|
|||||||
nextSibling &&
|
nextSibling &&
|
||||||
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
this.checkSameMarkerOrDelimiter(nextSibling, bulletMarkerOrDelimiter)
|
||||||
) {
|
) {
|
||||||
this.insertBefore(newBlock, nextSibling.children[0])
|
this.insertBefore(newListItemBlock, nextSibling.children[0])
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
const isLooseListItem = nextSibling.children.some(c => c.isLooseListItem)
|
const isLooseListItem = nextSibling.children.some(c => c.isLooseListItem)
|
||||||
nextSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
nextSibling.children.forEach(c => c.isLooseListItem = isLooseListItem)
|
||||||
} else if (
|
|
||||||
// todo@jocs remove this if in 0.15.xx
|
|
||||||
parent &&
|
|
||||||
parent.listType === type &&
|
|
||||||
this.checkSameLooseType(parent, preferLooseListItem)
|
|
||||||
) {
|
|
||||||
this.insertBefore(newBlock, block)
|
|
||||||
this.removeBlock(block)
|
|
||||||
} else {
|
} else {
|
||||||
// Create a new list when changing list type, bullet or list delimiter
|
// Create a new list when changing list type, bullet or list delimiter
|
||||||
const listBlock = this.createBlock(wrapperTag)
|
const listBlock = this.createBlock(wrapperTag, {
|
||||||
listBlock.listType = type
|
listType: type
|
||||||
|
})
|
||||||
|
|
||||||
if (wrapperTag === 'ol') {
|
if (wrapperTag === 'ol') {
|
||||||
const start = cleanMarker ? cleanMarker.slice(0, -1) : 1
|
const start = cleanMarker ? cleanMarker.slice(0, -1) : 1
|
||||||
listBlock.start = /^\d+$/.test(start) ? start : 1
|
listBlock.start = /^\d+$/.test(start) ? start : 1
|
||||||
}
|
}
|
||||||
this.appendChild(listBlock, newBlock)
|
this.appendChild(listBlock, newListItemBlock)
|
||||||
this.insertBefore(listBlock, block)
|
this.insertBefore(listBlock, block)
|
||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// key point
|
// key point
|
||||||
this.appendChild(newBlock, block)
|
this.appendChild(newListItemBlock, block)
|
||||||
const TASK_LIST_REG = /^\[[x ]\] /i
|
const TASK_LIST_REG = /^\[[x ]\] {1,4}/i
|
||||||
const listItemText = block.children[0].text
|
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)) {
|
if (TASK_LIST_REG.test(listItemText)) {
|
||||||
const [,,tasklist,,,,] = listItemText.match(INLINE_UPDATE_REG) || []
|
const [,,tasklist,,,,] = listItemText.match(INLINE_UPDATE_REG) || []
|
||||||
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
return this.updateTaskListItem(block, 'tasklist', tasklist)
|
||||||
} else {
|
} else {
|
||||||
const { key } = block.children[0]
|
|
||||||
this.cursor = {
|
|
||||||
start: {
|
|
||||||
key,
|
|
||||||
offset: Math.max(0, startOffset - marker.length)
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
key,
|
|
||||||
offset: Math.max(0, endOffset - marker.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
|
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
|
||||||
if (block.type === 'span') {
|
|
||||||
block = this.getParent(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { preferLooseListItem } = this
|
const { preferLooseListItem } = this
|
||||||
const parent = this.getParent(block)
|
const parent = this.getParent(block)
|
||||||
const grandpa = this.getParent(parent)
|
const grandpa = this.getParent(parent)
|
||||||
const checked = /\[x\]\s/i.test(marker) // use `i` flag to ignore upper case or lower case
|
const checked = /\[x\]\s/i.test(marker) // use `i` flag to ignore upper case or lower case
|
||||||
const checkbox = this.createBlock('input')
|
const checkbox = this.createBlock('input', {
|
||||||
|
checked
|
||||||
|
})
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
|
|
||||||
checkbox.checked = checked
|
|
||||||
this.insertBefore(checkbox, block)
|
this.insertBefore(checkbox, block)
|
||||||
block.children[0].text = block.children[0].text.substring(marker.length)
|
block.children[0].text = block.children[0].text.substring(marker.length)
|
||||||
parent.listItemType = 'task'
|
parent.listItemType = 'task'
|
||||||
@ -277,16 +304,21 @@ const updateCtrl = ContentState => {
|
|||||||
if (this.isOnlyChild(parent)) {
|
if (this.isOnlyChild(parent)) {
|
||||||
grandpa.listType = 'task'
|
grandpa.listType = 'task'
|
||||||
} else if (this.isFirstChild(parent) || this.isLastChild(parent)) {
|
} else if (this.isFirstChild(parent) || this.isLastChild(parent)) {
|
||||||
taskListWrapper = this.createBlock('ul')
|
taskListWrapper = this.createBlock('ul', {
|
||||||
taskListWrapper.listType = 'task'
|
listType: 'task'
|
||||||
|
})
|
||||||
|
|
||||||
this.isFirstChild(parent) ? this.insertBefore(taskListWrapper, grandpa) : this.insertAfter(taskListWrapper, grandpa)
|
this.isFirstChild(parent) ? this.insertBefore(taskListWrapper, grandpa) : this.insertAfter(taskListWrapper, grandpa)
|
||||||
this.removeBlock(parent)
|
this.removeBlock(parent)
|
||||||
this.appendChild(taskListWrapper, parent)
|
this.appendChild(taskListWrapper, parent)
|
||||||
} else {
|
} else {
|
||||||
taskListWrapper = this.createBlock('ul')
|
taskListWrapper = this.createBlock('ul', {
|
||||||
taskListWrapper.listType = 'task'
|
listType: 'task'
|
||||||
const bulletListWrapper = this.createBlock('ul')
|
})
|
||||||
bulletListWrapper.listType = 'bullet'
|
|
||||||
|
const bulletListWrapper = this.createBlock('ul', {
|
||||||
|
listType: 'bullet'
|
||||||
|
})
|
||||||
|
|
||||||
let preSibling = this.getPreSibling(parent)
|
let preSibling = this.getPreSibling(parent)
|
||||||
while (preSibling) {
|
while (preSibling) {
|
||||||
@ -319,145 +351,246 @@ const updateCtrl = ContentState => {
|
|||||||
return taskListWrapper || grandpa
|
return taskListWrapper || grandpa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ATX heading doesn't support soft line break and hard line break.
|
||||||
ContentState.prototype.updateAtxHeader = function (block, header, line) {
|
ContentState.prototype.updateAtxHeader = function (block, header, line) {
|
||||||
const newType = `h${header.length}`
|
const newType = `h${header.length}`
|
||||||
const text = line ? line.text : block.text
|
const headingStyle = 'atx'
|
||||||
if (line) {
|
if (block.type === newType && block.headingStyle === headingStyle) {
|
||||||
const index = block.children.indexOf(line)
|
|
||||||
const header = this.createBlock(newType, text)
|
|
||||||
header.headingStyle = 'atx'
|
|
||||||
this.insertBefore(header, block)
|
|
||||||
const paragraphBefore = this.createBlock('p')
|
|
||||||
const paragraphAfter = this.createBlock('p')
|
|
||||||
let i = 0
|
|
||||||
const len = block.children.length
|
|
||||||
for (i; i < len; i++) {
|
|
||||||
const child = block.children[i]
|
|
||||||
if (i < index) {
|
|
||||||
this.appendChild(paragraphBefore, child)
|
|
||||||
} else if (i > index) {
|
|
||||||
this.appendChild(paragraphAfter, child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (paragraphBefore.children.length) {
|
|
||||||
this.insertBefore(paragraphBefore, header)
|
|
||||||
}
|
|
||||||
if (paragraphAfter.children.length) {
|
|
||||||
this.insertAfter(paragraphAfter, header)
|
|
||||||
}
|
|
||||||
this.removeBlock(block)
|
|
||||||
this.cursor.start.key = this.cursor.end.key = header.key
|
|
||||||
return header
|
|
||||||
} else {
|
|
||||||
if (block.type === newType && block.headingStyle === 'atx') {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
block.headingStyle = 'atx'
|
const text = line.text
|
||||||
block.type = newType
|
const lines = text.split('\n')
|
||||||
block.text = text
|
const preParagraphLines = []
|
||||||
block.children.length = 0
|
let atxLine = ''
|
||||||
this.cursor.start.key = this.cursor.end.key = block.key
|
const postParagraphLines = []
|
||||||
return block
|
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) {
|
ContentState.prototype.updateSetextHeader = function (block, marker, line) {
|
||||||
const newType = /=/.test(marker) ? 'h1' : 'h2'
|
const newType = /=/.test(marker) ? 'h1' : 'h2'
|
||||||
const header = this.createBlock(newType)
|
const headingStyle = 'setext'
|
||||||
header.headingStyle = 'setext'
|
if (block.type === newType && block.headingStyle === headingStyle) {
|
||||||
header.marker = marker
|
return null
|
||||||
const index = block.children.indexOf(line)
|
|
||||||
let i = 0
|
|
||||||
let text = ''
|
|
||||||
for (i; i < index; i++) {
|
|
||||||
text += `${block.children[i].text}\n`
|
|
||||||
}
|
}
|
||||||
header.text = text.trimRight()
|
|
||||||
this.insertBefore(header, block)
|
const text = line.text
|
||||||
if (line.nextSibling) {
|
const lines = text.split('\n')
|
||||||
const removedCache = []
|
let setextLines = []
|
||||||
for (const child of block.children) {
|
const postParagraphLines = []
|
||||||
removedCache.push(child)
|
let setextLineHasPushed = false
|
||||||
if (child === line) {
|
|
||||||
break
|
for (const l of lines) {
|
||||||
}
|
if (/^ {0,3}(?:={3,}|-{3,})(?= {1,}|$)/.test(l) && !setextLineHasPushed) {
|
||||||
}
|
setextLineHasPushed = true
|
||||||
removedCache.forEach(child => this.removeBlock(child))
|
} else if (!setextLineHasPushed) {
|
||||||
|
setextLines.push(l)
|
||||||
} else {
|
} else {
|
||||||
this.removeBlock(block)
|
postParagraphLines.push(l)
|
||||||
}
|
}
|
||||||
this.cursor.start.key = this.cursor.end.key = header.key
|
}
|
||||||
this.cursor.start.offset = this.cursor.end.offset = header.text.length
|
|
||||||
return header
|
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) {
|
ContentState.prototype.updateBlockQuote = function (block, line) {
|
||||||
if (line && !this.isFirstChild(line)) {
|
const text = line.text
|
||||||
const paragraphBefore = this.createBlock('p')
|
const lines = text.split('\n')
|
||||||
const removeCache = []
|
const preParagraphLines = []
|
||||||
for (const child of block.children) {
|
let quoteLines = []
|
||||||
if (child === line) break
|
let quoteLinesHasPushed = false
|
||||||
removeCache.push(child)
|
|
||||||
}
|
|
||||||
removeCache.forEach(c => {
|
|
||||||
this.removeBlock(c)
|
|
||||||
this.appendChild(paragraphBefore, c)
|
|
||||||
})
|
|
||||||
this.insertBefore(paragraphBefore, block)
|
|
||||||
}
|
|
||||||
if (!line && /^h\d/.test(block.type)) {
|
|
||||||
block.text = block.text.substring(1).trim()
|
|
||||||
delete block.headingStyle
|
|
||||||
delete block.marker
|
|
||||||
block.type = 'p'
|
|
||||||
block.children = []
|
|
||||||
const line = this.createBlock('span', block.text.substring(1))
|
|
||||||
block.text = ''
|
|
||||||
this.appendChild(block, line)
|
|
||||||
} else {
|
|
||||||
line.text = line.text.substring(1).trim()
|
|
||||||
}
|
|
||||||
const quoteBlock = this.createBlock('blockquote')
|
|
||||||
this.insertBefore(quoteBlock, block)
|
|
||||||
this.removeBlock(block)
|
|
||||||
this.appendChild(quoteBlock, block)
|
|
||||||
|
|
||||||
|
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
|
const { start, end } = this.cursor
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: { key, offset: start.offset - 1 },
|
||||||
key: start.key,
|
end: { key, offset: end.offset - 1 }
|
||||||
offset: start.offset - 1
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
key: end.key,
|
|
||||||
offset: end.offset - 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return quoteBlock
|
return quoteBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateToParagraph = function (block) {
|
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') {
|
if (/^h\d$/.test(block.type) && block.headingStyle === 'setext') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const newType = 'p'
|
const newType = 'p'
|
||||||
if (block.type !== newType) {
|
if (block.type !== newType) {
|
||||||
block.type = newType // updateP
|
const newBlock = this.createBlockP(line.text)
|
||||||
const newLine = this.createBlock('span', block.text)
|
this.insertBefore(newBlock, block)
|
||||||
this.appendChild(block, newLine)
|
this.removeBlock(block)
|
||||||
block.text = ''
|
const { start, end } = this.cursor
|
||||||
this.cursor.start.key = this.cursor.end.key = newLine.key
|
const key = newBlock.children[0].key
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset: start.offset },
|
||||||
|
end: { key, offset: end.offset }
|
||||||
|
}
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateCodeBlocks = function (block) {
|
|
||||||
const codeBlock = this.getParent(block)
|
|
||||||
const preBlock = this.getParent(codeBlock)
|
|
||||||
const code = codeBlock.children.map(line => line.text).join('\n')
|
|
||||||
this.codeBlocks.set(preBlock.key, code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default updateCtrl
|
export default updateCtrl
|
||||||
|
@ -73,15 +73,12 @@ class ClickEvent {
|
|||||||
if (target.closest('div.ag-container-preview') || target.closest('div.ag-html-preview')) {
|
if (target.closest('div.ag-container-preview') || target.closest('div.ag-html-preview')) {
|
||||||
return event.stopPropagation()
|
return event.stopPropagation()
|
||||||
}
|
}
|
||||||
// handler html preview click
|
// handler container preview click
|
||||||
const editIcon = target.closest(`.ag-container-icon`)
|
const editIcon = target.closest(`.ag-container-icon`)
|
||||||
if (editIcon) {
|
if (editIcon) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
const nextElement = editIcon.nextElementSibling
|
if (editIcon.parentNode.classList.contains('ag-container-block')) {
|
||||||
if (nextElement && nextElement.classList.contains('ag-function-html')) {
|
|
||||||
contentState.handleHtmlBlockClick(nextElement)
|
|
||||||
} else if (editIcon.parentNode.classList.contains('ag-container-block')) {
|
|
||||||
contentState.handleContainerBlockClick(editIcon.parentNode)
|
contentState.handleContainerBlockClick(editIcon.parentNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,9 @@ class Keyboard {
|
|||||||
if (event.target.closest('[contenteditable=false]')) {
|
if (event.target.closest('[contenteditable=false]')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need check cursor is null, because we may copy the html preview content,
|
||||||
|
// and no need to dispatch change.
|
||||||
const { start, end } = selection.getCursorRange()
|
const { start, end } = selection.getCursorRange()
|
||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
return
|
return
|
||||||
|
@ -378,15 +378,37 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => {
|
|||||||
pos = pos + autoLTo[0].length
|
pos = pos + autoLTo[0].length
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// soft line break
|
||||||
|
const softTo = inlineRules['soft_line_break'].exec(src)
|
||||||
|
if (softTo) {
|
||||||
|
const len = softTo[0].length
|
||||||
|
pushPending()
|
||||||
|
tokens.push({
|
||||||
|
type: 'soft_line_break',
|
||||||
|
raw: softTo[0],
|
||||||
|
lineBreak: softTo[1],
|
||||||
|
isAtEnd: softTo.input.length === softTo[0].length,
|
||||||
|
parent: tokens,
|
||||||
|
range: {
|
||||||
|
start: pos,
|
||||||
|
end: pos + len
|
||||||
|
}
|
||||||
|
})
|
||||||
|
src = src.substring(len)
|
||||||
|
pos += len
|
||||||
|
continue
|
||||||
|
}
|
||||||
// hard line break
|
// hard line break
|
||||||
const hardTo = inlineRules['hard_line_break'].exec(src)
|
const hardTo = inlineRules['hard_line_break'].exec(src)
|
||||||
if (hardTo && top) {
|
if (hardTo) {
|
||||||
const len = hardTo[0].length
|
const len = hardTo[0].length
|
||||||
pushPending()
|
pushPending()
|
||||||
tokens.push({
|
tokens.push({
|
||||||
type: 'hard_line_break',
|
type: 'hard_line_break',
|
||||||
raw: hardTo[0],
|
raw: hardTo[0],
|
||||||
spaces: hardTo[1],
|
spaces: hardTo[1],
|
||||||
|
lineBreak: hardTo[2],
|
||||||
|
isAtEnd: hardTo.input.length === hardTo[0].length,
|
||||||
parent: tokens,
|
parent: tokens,
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
|
@ -185,9 +185,11 @@ Lexer.prototype.token = function (src, top) {
|
|||||||
// hr
|
// hr
|
||||||
cap = this.rules.hr.exec(src)
|
cap = this.rules.hr.exec(src)
|
||||||
if (cap) {
|
if (cap) {
|
||||||
|
const marker = cap[0].replace(/\n*$/, '')
|
||||||
src = src.substring(cap[0].length)
|
src = src.substring(cap[0].length)
|
||||||
this.tokens.push({
|
this.tokens.push({
|
||||||
type: 'hr'
|
type: 'hr',
|
||||||
|
marker
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import flowchart from 'flowchart.js'
|
|||||||
import Diagram from './sequence'
|
import Diagram from './sequence'
|
||||||
import vegaEmbed from 'vega-embed'
|
import vegaEmbed from 'vega-embed'
|
||||||
import { CLASS_OR_ID } from '../../config'
|
import { CLASS_OR_ID } from '../../config'
|
||||||
import { conflict, mixins } from '../../utils'
|
import { conflict, mixins, camelToSnake } from '../../utils'
|
||||||
import { patch, toVNode, toHTML, h } from './snabbdom'
|
import { patch, toVNode, toHTML, h } from './snabbdom'
|
||||||
import { beginRules } from '../rules'
|
import { beginRules } from '../rules'
|
||||||
import renderInlines from './renderInlines'
|
import renderInlines from './renderInlines'
|
||||||
@ -13,6 +13,7 @@ class StateRender {
|
|||||||
constructor (muya) {
|
constructor (muya) {
|
||||||
this.muya = muya
|
this.muya = muya
|
||||||
this.eventCenter = muya.eventCenter
|
this.eventCenter = muya.eventCenter
|
||||||
|
this.codeCache = new Map()
|
||||||
this.loadImageMap = new Map()
|
this.loadImageMap = new Map()
|
||||||
this.loadMathMap = new Map()
|
this.loadMathMap = new Map()
|
||||||
this.mermaidCache = new Set()
|
this.mermaidCache = new Set()
|
||||||
@ -85,7 +86,7 @@ class StateRender {
|
|||||||
selector += `.${CLASS_OR_ID['AG_ACTIVE']}`
|
selector += `.${CLASS_OR_ID['AG_ACTIVE']}`
|
||||||
}
|
}
|
||||||
if (type === 'span') {
|
if (type === 'span') {
|
||||||
selector += `.${CLASS_OR_ID['AG_LINE']}`
|
selector += `.ag-${camelToSnake(block.functionType)}`
|
||||||
}
|
}
|
||||||
if (!block.parent && selectedBlock && block.key === selectedBlock.key) {
|
if (!block.parent && selectedBlock && block.key === selectedBlock.key) {
|
||||||
selector += `.${CLASS_OR_ID['AG_SELECTED']}`
|
selector += `.${CLASS_OR_ID['AG_SELECTED']}`
|
||||||
@ -155,6 +156,7 @@ class StateRender {
|
|||||||
patch(oldVdom, newVdom)
|
patch(oldVdom, newVdom)
|
||||||
this.renderMermaid()
|
this.renderMermaid()
|
||||||
this.renderDiagram()
|
this.renderDiagram()
|
||||||
|
this.codeCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only render the blocks which you updated
|
// Only render the blocks which you updated
|
||||||
@ -198,6 +200,7 @@ class StateRender {
|
|||||||
|
|
||||||
this.renderMermaid()
|
this.renderMermaid()
|
||||||
this.renderDiagram()
|
this.renderDiagram()
|
||||||
|
this.codeCache.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* [renderBlock render one block, no matter it is a container block or text block]
|
* [renderBlock render one block, no matter it is a container block or text block]
|
||||||
*/
|
*/
|
||||||
export default function renderBlock (block, cursor, activeBlocks, selectedBlock, matches, useCache = false) {
|
export default function renderBlock (block, cursor, activeBlocks, selectedBlock, matches, useCache = false) {
|
||||||
const method = block.children.length > 0
|
const method = Array.isArray(block.children) && block.children.length > 0
|
||||||
? 'renderContainerBlock'
|
? 'renderContainerBlock'
|
||||||
: 'renderLeafBlock'
|
: 'renderLeafBlock'
|
||||||
|
|
||||||
|
@ -17,28 +17,47 @@ const PRE_BLOCK_HASH = {
|
|||||||
|
|
||||||
export default function renderContainerBlock (block, cursor, activeBlocks, selectedBlock, matches, useCache = false) {
|
export default function renderContainerBlock (block, cursor, activeBlocks, selectedBlock, matches, useCache = false) {
|
||||||
let selector = this.getSelector(block, cursor, activeBlocks, selectedBlock)
|
let selector = this.getSelector(block, cursor, activeBlocks, selectedBlock)
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
headingStyle,
|
||||||
|
editable,
|
||||||
|
functionType,
|
||||||
|
listType,
|
||||||
|
listItemType,
|
||||||
|
bulletMarkerOrDelimiter,
|
||||||
|
isLooseListItem,
|
||||||
|
lang
|
||||||
|
} = block
|
||||||
const children = block.children.map(child => this.renderBlock(child, cursor, activeBlocks, selectedBlock, matches, useCache))
|
const children = block.children.map(child => this.renderBlock(child, cursor, activeBlocks, selectedBlock, matches, useCache))
|
||||||
const data = {
|
const data = {
|
||||||
attrs: {},
|
attrs: {},
|
||||||
dataset: {}
|
dataset: {}
|
||||||
}
|
}
|
||||||
// handle `div` block
|
|
||||||
if (/div/.test(block.type)) {
|
if (editable === false) {
|
||||||
if (block.toolBarType) {
|
|
||||||
selector += `.${'ag-tool-' + block.toolBarType}.${CLASS_OR_ID['AG_TOOL_BAR']}`
|
|
||||||
}
|
|
||||||
if (block.functionType) {
|
|
||||||
selector += `.${'ag-function-' + block.functionType}`
|
|
||||||
}
|
|
||||||
if (block.editable !== undefined && !block.editable) {
|
|
||||||
Object.assign(data.attrs, { contenteditable: 'false' })
|
Object.assign(data.attrs, { contenteditable: 'false' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/code|pre/.test(type) && typeof lang === 'string' && !!lang) {
|
||||||
|
selector += `.language-${lang}`
|
||||||
}
|
}
|
||||||
// handle `figure` block
|
|
||||||
if (block.type === 'figure') {
|
if (/^h/.test(type)) {
|
||||||
if (block.functionType) {
|
if (/^h\d$/.test(type)) {
|
||||||
Object.assign(data.dataset, { role: block.functionType.toUpperCase() })
|
// TODO: This should be the best place to create and update the TOC.
|
||||||
if (block.functionType === 'table') {
|
// Cache `block.key` and title and update only if necessary.
|
||||||
|
Object.assign(data.dataset, {
|
||||||
|
head: type
|
||||||
|
})
|
||||||
|
selector += `.${headingStyle}`
|
||||||
|
}
|
||||||
|
Object.assign(data.dataset, {
|
||||||
|
role: type
|
||||||
|
})
|
||||||
|
} else if (type === 'figure') {
|
||||||
|
if (functionType) {
|
||||||
|
Object.assign(data.dataset, { role: functionType.toUpperCase() })
|
||||||
|
if (functionType === 'table') {
|
||||||
children.unshift(renderTableTools(activeBlocks))
|
children.unshift(renderTableTools(activeBlocks))
|
||||||
} else {
|
} else {
|
||||||
children.unshift(renderEditIcon())
|
children.unshift(renderEditIcon())
|
||||||
@ -46,61 +65,29 @@ export default function renderContainerBlock (block, cursor, activeBlocks, selec
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
/multiplemath|flowchart|mermaid|sequence|vega-lite/.test(block.functionType)
|
/html|multiplemath|flowchart|mermaid|sequence|vega-lite/.test(functionType)
|
||||||
) {
|
) {
|
||||||
selector += `.${CLASS_OR_ID['AG_CONTAINER_BLOCK']}`
|
selector += `.${CLASS_OR_ID['AG_CONTAINER_BLOCK']}`
|
||||||
}
|
}
|
||||||
}
|
} else if (/ul|ol/.test(type) && listType) {
|
||||||
// hanle list block
|
selector += `.ag-${listType}-list`
|
||||||
if (/ul|ol/.test(block.type) && block.listType) {
|
if (type === 'ol') {
|
||||||
switch (block.listType) {
|
|
||||||
case 'order':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_ORDER_LIST']}`
|
|
||||||
break
|
|
||||||
case 'bullet':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_BULLET_LIST']}`
|
|
||||||
break
|
|
||||||
case 'task':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_TASK_LIST']}`
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (block.type === 'li' && block.listItemType) {
|
|
||||||
selector += `.${CLASS_OR_ID['AG_LIST_ITEM']}`
|
|
||||||
switch (block.listItemType) {
|
|
||||||
case 'order':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_ORDER_LIST_ITEM']}`
|
|
||||||
break
|
|
||||||
case 'bullet':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_BULLET_LIST_ITEM']}`
|
|
||||||
break
|
|
||||||
case 'task':
|
|
||||||
selector += `.${CLASS_OR_ID['AG_TASK_LIST_ITEM']}`
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
Object.assign(data.dataset, { marker: block.bulletMarkerOrDelimiter })
|
|
||||||
selector += block.isLooseListItem ? `.${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}` : `.${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}`
|
|
||||||
}
|
|
||||||
if (block.type === 'ol') {
|
|
||||||
Object.assign(data.attrs, { start: block.start })
|
Object.assign(data.attrs, { start: block.start })
|
||||||
}
|
}
|
||||||
if (block.type === 'code') {
|
} else if (type === 'li' && listItemType) {
|
||||||
const { lang } = block
|
Object.assign(data.dataset, { marker: bulletMarkerOrDelimiter })
|
||||||
if (lang) {
|
selector += `.${CLASS_OR_ID['AG_LIST_ITEM']}`
|
||||||
selector += `.language-${lang}`
|
selector += `.ag-${listItemType}-list-item`
|
||||||
}
|
selector += isLooseListItem ? `.${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}` : `.${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}`
|
||||||
}
|
} else if (type === 'pre') {
|
||||||
if (block.type === 'pre') {
|
|
||||||
const { lang, functionType } = block
|
|
||||||
if (lang) {
|
|
||||||
selector += `.language-${lang}`
|
|
||||||
}
|
|
||||||
Object.assign(data.dataset, { role: functionType })
|
Object.assign(data.dataset, { role: functionType })
|
||||||
selector += PRE_BLOCK_HASH[block.functionType]
|
selector += PRE_BLOCK_HASH[block.functionType]
|
||||||
|
|
||||||
|
if (/html|multiplemath|mermaid|flowchart|wega-lite|sequence/.test(functionType)) {
|
||||||
|
const codeBlock = block.children[0]
|
||||||
|
const code = codeBlock.children.map(line => line.text).join('\n')
|
||||||
|
this.codeCache.set(block.key, code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block.parent) {
|
if (!block.parent) {
|
||||||
|
@ -57,7 +57,6 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
const {
|
const {
|
||||||
text,
|
text,
|
||||||
type,
|
type,
|
||||||
headingStyle,
|
|
||||||
align,
|
align,
|
||||||
checked,
|
checked,
|
||||||
key,
|
key,
|
||||||
@ -65,13 +64,16 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
functionType,
|
functionType,
|
||||||
editable
|
editable
|
||||||
} = block
|
} = block
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
props: {},
|
props: {},
|
||||||
attrs: {},
|
attrs: {},
|
||||||
dataset: {},
|
dataset: {},
|
||||||
style: {}
|
style: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let children = ''
|
let children = ''
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
let tokens = []
|
let tokens = []
|
||||||
if (highlights.length === 0 && this.tokenCache.has(text)) {
|
if (highlights.length === 0 && this.tokenCache.has(text)) {
|
||||||
@ -81,7 +83,7 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
functionType !== 'codeLine' &&
|
functionType !== 'codeLine' &&
|
||||||
functionType !== 'languageInput'
|
functionType !== 'languageInput'
|
||||||
) {
|
) {
|
||||||
const hasBeginRules = /^(h\d|span|hr)/.test(type)
|
const hasBeginRules = type === 'span'
|
||||||
tokens = tokenizer(text, highlights, hasBeginRules, this.labels)
|
tokens = tokenizer(text, highlights, hasBeginRules, this.labels)
|
||||||
const hasReferenceTokens = hasReferenceToken(tokens)
|
const hasReferenceTokens = hasReferenceToken(tokens)
|
||||||
if (highlights.length === 0 && useCache && DEVICE_MEMORY >= 4 && !hasReferenceTokens) {
|
if (highlights.length === 0 && useCache && DEVICE_MEMORY >= 4 && !hasReferenceTokens) {
|
||||||
@ -103,9 +105,9 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
style: `text-align:${align}`
|
style: `text-align:${align}`
|
||||||
})
|
})
|
||||||
} else if (type === 'div') {
|
} else if (type === 'div') {
|
||||||
const code = this.muya.contentState.codeBlocks.get(block.preSibling)
|
const code = this.codeCache.get(block.preSibling)
|
||||||
switch (functionType) {
|
switch (functionType) {
|
||||||
case 'preview': {
|
case 'html': {
|
||||||
selector += `.${CLASS_OR_ID['AG_HTML_PREVIEW']}`
|
selector += `.${CLASS_OR_ID['AG_HTML_PREVIEW']}`
|
||||||
const htmlContent = sanitize(code, PREVIEW_DOMPURIFY_CONFIG)
|
const htmlContent = sanitize(code, PREVIEW_DOMPURIFY_CONFIG)
|
||||||
// handle empty html bock
|
// handle empty html bock
|
||||||
@ -168,7 +170,6 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
case 'flowchart':
|
case 'flowchart':
|
||||||
case 'sequence':
|
case 'sequence':
|
||||||
case 'vega-lite': {
|
case 'vega-lite': {
|
||||||
const code = this.muya.contentState.codeBlocks.get(block.preSibling)
|
|
||||||
selector += `.${CLASS_OR_ID['AG_CONTAINER_PREVIEW']}`
|
selector += `.${CLASS_OR_ID['AG_CONTAINER_PREVIEW']}`
|
||||||
if (code === '') {
|
if (code === '') {
|
||||||
children = '< Empty Diagram Block >'
|
children = '< Empty Diagram Block >'
|
||||||
@ -183,18 +184,6 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (/^h/.test(type)) {
|
|
||||||
if (/^h\d$/.test(type)) {
|
|
||||||
// TODO: This should be the best place to create and update the TOC.
|
|
||||||
// Cache `block.key` and title and update only if necessary.
|
|
||||||
Object.assign(data.dataset, {
|
|
||||||
head: type
|
|
||||||
})
|
|
||||||
selector += `.${headingStyle}`
|
|
||||||
}
|
|
||||||
Object.assign(data.dataset, {
|
|
||||||
role: type
|
|
||||||
})
|
|
||||||
} else if (type === 'input') {
|
} else if (type === 'input') {
|
||||||
Object.assign(data.attrs, {
|
Object.assign(data.attrs, {
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
@ -214,8 +203,6 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
.replace(new RegExp(MARKER_HASK['"'], 'g'), '"')
|
.replace(new RegExp(MARKER_HASK['"'], 'g'), '"')
|
||||||
.replace(new RegExp(MARKER_HASK["'"], 'g'), "'")
|
.replace(new RegExp(MARKER_HASK["'"], 'g'), "'")
|
||||||
|
|
||||||
selector += `.${CLASS_OR_ID['AG_CODE_LINE']}`
|
|
||||||
|
|
||||||
if (lang && /\S/.test(code) && loadedCache.has(lang)) {
|
if (lang && /\S/.test(code) && loadedCache.has(lang)) {
|
||||||
const wrapper = document.createElement('div')
|
const wrapper = document.createElement('div')
|
||||||
wrapper.classList.add(`language-${lang}`)
|
wrapper.classList.add(`language-${lang}`)
|
||||||
@ -230,7 +217,6 @@ export default function renderLeafBlock (block, cursor, activeBlocks, selectedBl
|
|||||||
}
|
}
|
||||||
} else if (type === 'span' && functionType === 'languageInput') {
|
} else if (type === 'span' && functionType === 'languageInput') {
|
||||||
const html = getHighlightHtml(text, highlights)
|
const html = getHighlightHtml(text, highlights)
|
||||||
selector += `.${CLASS_OR_ID['AG_LANGUAGE_INPUT']}`
|
|
||||||
children = htmlToVNode(html)
|
children = htmlToVNode(html)
|
||||||
}
|
}
|
||||||
if (!block.parent) {
|
if (!block.parent) {
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { CLASS_OR_ID } from '../../../config'
|
import { CLASS_OR_ID } from '../../../config'
|
||||||
|
|
||||||
export default function hardLineBreak (h, cursor, block, token, outerClass) {
|
export default function softLineBreak (h, cursor, block, token, outerClass) {
|
||||||
|
const { spaces, lineBreak, isAtEnd } = token
|
||||||
const className = CLASS_OR_ID['AG_HARD_LINE_BREAK']
|
const className = CLASS_OR_ID['AG_HARD_LINE_BREAK']
|
||||||
const content = [token.spaces]
|
const spaceClass = CLASS_OR_ID['AG_HARD_LINE_BREAK_SPACE']
|
||||||
if (block.type === 'span' && block.nextSibling) {
|
if (isAtEnd) {
|
||||||
return [
|
return [
|
||||||
h(`span.${className}`, content)
|
h(`span.${className}`, h(`span.${spaceClass}`, spaces)),
|
||||||
|
h(`span.${CLASS_OR_ID['AG_LINE_END']}`, lineBreak)
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
return content
|
return [
|
||||||
|
h(`span.${className}`, [ h(`span.${spaceClass}`, spaces), lineBreak ])
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import htmlTag from './htmlTag'
|
|||||||
import hr from './hr'
|
import hr from './hr'
|
||||||
import tailHeader from './tailHeader'
|
import tailHeader from './tailHeader'
|
||||||
import hardLineBreak from './hardLineBreak'
|
import hardLineBreak from './hardLineBreak'
|
||||||
|
import softLineBreak from './softLineBreak'
|
||||||
import codeFense from './codeFense'
|
import codeFense from './codeFense'
|
||||||
import inlineMath from './inlineMath'
|
import inlineMath from './inlineMath'
|
||||||
import autoLink from './autoLink'
|
import autoLink from './autoLink'
|
||||||
@ -37,6 +38,7 @@ export default {
|
|||||||
hr,
|
hr,
|
||||||
tailHeader,
|
tailHeader,
|
||||||
hardLineBreak,
|
hardLineBreak,
|
||||||
|
softLineBreak,
|
||||||
codeFense,
|
codeFense,
|
||||||
inlineMath,
|
inlineMath,
|
||||||
autoLink,
|
autoLink,
|
||||||
|
13
src/muya/lib/parser/render/renderInlines/softLineBreak.js
Normal file
13
src/muya/lib/parser/render/renderInlines/softLineBreak.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { CLASS_OR_ID } from '../../../config'
|
||||||
|
|
||||||
|
export default function hardLineBreak (h, cursor, block, token, outerClass) {
|
||||||
|
const { lineBreak, isAtEnd } = token
|
||||||
|
let selector = `span.${CLASS_OR_ID['AG_SOFT_LINE_BREAK']}`
|
||||||
|
if (isAtEnd) {
|
||||||
|
selector += `.${CLASS_OR_ID['AG_LINE_END']}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
h(selector, lineBreak)
|
||||||
|
]
|
||||||
|
}
|
@ -25,7 +25,8 @@ export const inlineRules = {
|
|||||||
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
||||||
'html_tag': /^(<!--[\s\S]*?-->|(<([a-zA-Z]{1}[a-zA-Z\d-]*) *[_\.\-/:a-zA-Z\d='";\? *]* *(?:\/)?>)(?:([\s\S]*?)(<\/\3 *>))?)/, // row html
|
'html_tag': /^(<!--[\s\S]*?-->|(<([a-zA-Z]{1}[a-zA-Z\d-]*) *[_\.\-/:a-zA-Z\d='";\? *]* *(?:\/)?>)(?:([\s\S]*?)(<\/\3 *>))?)/, // row html
|
||||||
'html_escape': new RegExp(`^(${escapeCharacters.join('|')})`, 'i'),
|
'html_escape': new RegExp(`^(${escapeCharacters.join('|')})`, 'i'),
|
||||||
'hard_line_break': /^(\s{2,})$/,
|
'soft_line_break': /^(\n)(?!\n)/,
|
||||||
|
'hard_line_break': /^( {2,})(\n)(?!\n)/,
|
||||||
|
|
||||||
// patched math marker `$`
|
// patched math marker `$`
|
||||||
'backlash': /^(\\)([\\`*{}\[\]()#+\-.!_>~:\|\<\>$]{1})/,
|
'backlash': /^(\\)([\\`*{}\[\]()#+\-.!_>~:\|\<\>$]{1})/,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import Prism from 'prismjs'
|
import Prism from 'prismjs2'
|
||||||
import { filter } from 'fuzzaldrin'
|
import { filter } from 'fuzzaldrin'
|
||||||
import initLoadLanguage, { loadedCache } from './loadLanguage'
|
import initLoadLanguage, { loadedCache } from './loadLanguage'
|
||||||
import languages from './languages'
|
import languages from './languages'
|
||||||
|
|
||||||
const prism = Prism
|
const prism = Prism
|
||||||
window.Prism = Prism
|
window.Prism = Prism
|
||||||
import('prismjs/plugins/keep-markup/prism-keep-markup')
|
import('prismjs2/plugins/keep-markup/prism-keep-markup')
|
||||||
const langs = Object.keys(languages).map(name => (languages[name]))
|
const langs = Object.keys(languages).map(name => (languages[name]))
|
||||||
const loadLanguage = initLoadLanguage(Prism)
|
const loadLanguage = initLoadLanguage(Prism)
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ function initLoadLanguage (Prism) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete Prism.languages[language]
|
delete Prism.languages[language]
|
||||||
await import('prismjs/components/prism-' + language)
|
await import('prismjs2/components/prism-' + language)
|
||||||
loadedCache.add(language)
|
loadedCache.add(language)
|
||||||
promises.push(Promise.resolve({
|
promises.push(Promise.resolve({
|
||||||
status: 'loaded',
|
status: 'loaded',
|
||||||
|
@ -4,7 +4,12 @@ import {
|
|||||||
const CHOP_TEXT_REG = /(\*{1,3})([^*]+)(\1)/g
|
const CHOP_TEXT_REG = /(\*{1,3})([^*]+)(\1)/g
|
||||||
|
|
||||||
export const getTextContent = (node, blackList) => {
|
export const getTextContent = (node, blackList) => {
|
||||||
if (!blackList) return node.textContent
|
if (node.nodeType === 3) {
|
||||||
|
return node.textContent
|
||||||
|
} else if (!blackList) {
|
||||||
|
return node.textContent
|
||||||
|
}
|
||||||
|
|
||||||
let text = ''
|
let text = ''
|
||||||
if (blackList.some(className => node.classList && node.classList.contains(className))) {
|
if (blackList.some(className => node.classList && node.classList.contains(className))) {
|
||||||
return text
|
return text
|
||||||
|
@ -436,6 +436,7 @@ class Selection {
|
|||||||
|
|
||||||
let { node: anchorNode, offset: anchorOffset } = getNodeAndOffset(anchorParagraph, anchor.offset)
|
let { node: anchorNode, offset: anchorOffset } = getNodeAndOffset(anchorParagraph, anchor.offset)
|
||||||
let { node: focusNode, offset: focusOffset } = getNodeAndOffset(focusParagraph, focus.offset)
|
let { node: focusNode, offset: focusOffset } = getNodeAndOffset(focusParagraph, focus.offset)
|
||||||
|
|
||||||
anchorOffset = Math.min(anchorOffset, anchorNode.textContent.length)
|
anchorOffset = Math.min(anchorOffset, anchorNode.textContent.length)
|
||||||
focusOffset = Math.min(focusOffset, focusNode.textContent.length)
|
focusOffset = Math.min(focusOffset, focusNode.textContent.length)
|
||||||
// First set the anchor node and anchor offeet, make it collapsed
|
// First set the anchor node and anchor offeet, make it collapsed
|
||||||
@ -449,15 +450,14 @@ class Selection {
|
|||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3) {
|
||||||
node = node.parentNode
|
node = node.parentNode
|
||||||
}
|
}
|
||||||
return node.closest('p[data-role=hr]') ||
|
return node.closest('span.ag-paragraph') ||
|
||||||
node.closest('span.ag-paragraph.ag-line') ||
|
|
||||||
node.closest('th.ag-paragraph') ||
|
node.closest('th.ag-paragraph') ||
|
||||||
node.closest('td.ag-paragraph') ||
|
node.closest('td.ag-paragraph')
|
||||||
node.closest('.ag-paragraph[data-head]')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCursorRange () {
|
getCursorRange () {
|
||||||
let { anchorNode, anchorOffset, focusNode, focusOffset } = this.doc.getSelection()
|
let { anchorNode, anchorOffset, focusNode, focusOffset } = this.doc.getSelection()
|
||||||
|
|
||||||
const isAnchorValid = this.isValidCursorNode(anchorNode)
|
const isAnchorValid = this.isValidCursorNode(anchorNode)
|
||||||
const isFocusValid = this.isValidCursorNode(focusNode)
|
const isFocusValid = this.isValidCursorNode(focusNode)
|
||||||
let needFix = false
|
let needFix = false
|
||||||
@ -480,6 +480,16 @@ class Selection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix bug click empty line, the cursor will jump to the end of pre line.
|
||||||
|
if (
|
||||||
|
anchorNode === focusNode &&
|
||||||
|
anchorOffset === focusOffset &&
|
||||||
|
anchorNode.textContent === '\n' &&
|
||||||
|
focusOffset === 0
|
||||||
|
) {
|
||||||
|
focusOffset = anchorOffset = 1
|
||||||
|
}
|
||||||
|
|
||||||
const anchorParagraph = findNearestParagraph(anchorNode)
|
const anchorParagraph = findNearestParagraph(anchorNode)
|
||||||
const focusParagraph = findNearestParagraph(focusNode)
|
const focusParagraph = findNearestParagraph(focusNode)
|
||||||
|
|
||||||
@ -502,6 +512,7 @@ class Selection {
|
|||||||
|
|
||||||
const aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset
|
const aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset
|
||||||
const fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset
|
const fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset
|
||||||
|
|
||||||
const anchor = { key: anchorParagraph.id, offset: aOffset }
|
const anchor = { key: anchorParagraph.id, offset: aOffset }
|
||||||
const focus = { key: focusParagraph.id, offset: fOffset }
|
const focus = { key: focusParagraph.id, offset: fOffset }
|
||||||
const result = new Cursor({ anchor, focus })
|
const result = new Cursor({ anchor, focus })
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import marked from '../parser/marked'
|
import marked from '../parser/marked'
|
||||||
import Prism from 'prismjs'
|
import Prism from 'prismjs2'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import mermaid from 'mermaid'
|
import mermaid from 'mermaid'
|
||||||
import flowchart from 'flowchart.js'
|
import flowchart from 'flowchart.js'
|
||||||
import Diagram from '../parser/render/sequence'
|
import Diagram from '../parser/render/sequence'
|
||||||
import vegaEmbed from 'vega-embed'
|
import vegaEmbed from 'vega-embed'
|
||||||
import githubMarkdownCss from 'github-markdown-css/github-markdown.css'
|
import githubMarkdownCss from 'github-markdown-css/github-markdown.css'
|
||||||
import highlightCss from 'prismjs/themes/prism.css'
|
import highlightCss from 'prismjs2/themes/prism.css'
|
||||||
import katexCss from 'katex/dist/katex.css'
|
import katexCss from 'katex/dist/katex.css'
|
||||||
import { EXPORT_DOMPURIFY_CONFIG } from '../config'
|
import { EXPORT_DOMPURIFY_CONFIG } from '../config'
|
||||||
import { sanitize, unescapeHtml } from '../utils'
|
import { sanitize, unescapeHtml } from '../utils'
|
||||||
|
@ -42,7 +42,8 @@ class ExportMarkdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case 'p': {
|
case 'p':
|
||||||
|
case 'hr': {
|
||||||
this.insertLineBreak(result, indent)
|
this.insertLineBreak(result, indent)
|
||||||
result.push(this.translateBlocks2Markdown(block.children, indent))
|
result.push(this.translateBlocks2Markdown(block.children, indent))
|
||||||
break
|
break
|
||||||
@ -51,11 +52,6 @@ class ExportMarkdown {
|
|||||||
result.push(this.normalizeParagraphText(block, indent))
|
result.push(this.normalizeParagraphText(block, indent))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'hr': {
|
|
||||||
this.insertLineBreak(result, indent)
|
|
||||||
result.push(this.normalizeParagraphText(block, indent))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'h1':
|
case 'h1':
|
||||||
case 'h2':
|
case 'h2':
|
||||||
case 'h3':
|
case 'h3':
|
||||||
@ -171,17 +167,21 @@ class ExportMarkdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
normalizeParagraphText (block, indent) {
|
normalizeParagraphText (block, indent) {
|
||||||
return `${indent}${block.text}\n`
|
const { text } = block
|
||||||
|
const lines = text.split('\n')
|
||||||
|
return lines.map(line => `${indent}${line}`).join('\n') + '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeHeaderText (block, indent) {
|
normalizeHeaderText (block, indent) {
|
||||||
const { headingStyle, marker } = block
|
const { headingStyle, marker } = block
|
||||||
|
const { text } = block.children[0]
|
||||||
if (headingStyle === 'atx') {
|
if (headingStyle === 'atx') {
|
||||||
const match = block.text.match(/(#{1,6})(.*)/)
|
const match = text.match(/(#{1,6})(.*)/)
|
||||||
const text = `${match[1]} ${match[2].trim()}`
|
const atxHeadingText = `${match[1]} ${match[2].trim()}`
|
||||||
return `${indent}${text}\n`
|
return `${indent}${atxHeadingText}\n`
|
||||||
} else if (headingStyle === 'setext') {
|
} else if (headingStyle === 'setext') {
|
||||||
return `${indent}${block.text}\n${indent}${marker.trim()}\n`
|
const lines = text.trim().split('\n')
|
||||||
|
return lines.map(line => `${indent}${line}`).join('\n') + `\n${indent}${marker.trim()}\n`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ class ExportMarkdown {
|
|||||||
|
|
||||||
normalizeHTML (block, indent) { // figure
|
normalizeHTML (block, indent) { // figure
|
||||||
const result = []
|
const result = []
|
||||||
const codeLines = block.children[0].children[0].children[0].children
|
const codeLines = block.children[0].children[0].children
|
||||||
for (const line of codeLines) {
|
for (const line of codeLines) {
|
||||||
result.push(`${indent}${line.text}\n`)
|
result.push(`${indent}${line.text}\n`)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,56 @@ import { CURSOR_DNA } from '../config'
|
|||||||
|
|
||||||
const LINE_BREAKS_REG = /\n/
|
const LINE_BREAKS_REG = /\n/
|
||||||
|
|
||||||
|
// Just because turndown change `\n`(soft line break) to space, So we add `span.ag-soft-line-break` to workaround.
|
||||||
|
const turnSoftBreakToSpan = html => {
|
||||||
|
const parser = new DOMParser()
|
||||||
|
const doc = parser.parseFromString(
|
||||||
|
`<x-mt id="turn-root">${html}</x-mt>`,
|
||||||
|
'text/html'
|
||||||
|
)
|
||||||
|
const root = doc.querySelector(`#turn-root`)
|
||||||
|
const travel = childNodes => {
|
||||||
|
for (const node of childNodes) {
|
||||||
|
if (node.nodeType === 3) {
|
||||||
|
let startLen = 0
|
||||||
|
let endLen = 0
|
||||||
|
const text = node.nodeValue.replace(/^(\n+)/, (_, p) => {
|
||||||
|
startLen = p.length
|
||||||
|
return ''
|
||||||
|
}).replace(/(\n+)$/, (_, p) => {
|
||||||
|
endLen = p.length
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
if (/\n/.test(text)) {
|
||||||
|
const tokens = text.split('\n')
|
||||||
|
const params = []
|
||||||
|
let i = 0
|
||||||
|
const len = tokens.length
|
||||||
|
for (; i< len; i++) {
|
||||||
|
let text = tokens[i]
|
||||||
|
if (i === 0 && startLen !== 0) {
|
||||||
|
text = '\n'.repeat(startLen) + text
|
||||||
|
} else if (i === len - 1 && endLen !== 0) {
|
||||||
|
text = text + '\n'.repeat(endLen)
|
||||||
|
}
|
||||||
|
params.push(document.createTextNode(text))
|
||||||
|
if (i !== len - 1) {
|
||||||
|
const softBreak = document.createElement('span')
|
||||||
|
softBreak.classList.add('ag-soft-line-break')
|
||||||
|
params.push(softBreak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.replaceWith(...params)
|
||||||
|
}
|
||||||
|
} else if (node.nodeType === 1) {
|
||||||
|
travel(node.childNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
travel(root.childNodes)
|
||||||
|
return root.innerHTML.trim()
|
||||||
|
}
|
||||||
|
|
||||||
const importRegister = ContentState => {
|
const importRegister = ContentState => {
|
||||||
// turn markdown to blocks
|
// turn markdown to blocks
|
||||||
ContentState.prototype.markdownToState = function (markdown) {
|
ContentState.prototype.markdownToState = function (markdown) {
|
||||||
@ -37,37 +87,57 @@ const importRegister = ContentState => {
|
|||||||
while ((token = tokens.shift())) {
|
while ((token = tokens.shift())) {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'frontmatter': {
|
case 'frontmatter': {
|
||||||
|
const lang = 'yaml'
|
||||||
value = token.text
|
value = token.text
|
||||||
block = this.createBlock('pre')
|
block = this.createBlock('pre', {
|
||||||
const codeBlock = this.createBlock('code')
|
functionType: token.type,
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang
|
||||||
|
})
|
||||||
value
|
value
|
||||||
.replace(/^\s+/, '')
|
.replace(/^\s+/, '')
|
||||||
.replace(/\s$/, '')
|
.replace(/\s$/, '')
|
||||||
.split(LINE_BREAKS_REG).forEach(line => {
|
.split(LINE_BREAKS_REG).forEach(line => {
|
||||||
const codeLine = this.createBlock('span', line)
|
const codeLine = this.createBlock('span', {
|
||||||
codeLine.functionType = 'codeLine'
|
text: line,
|
||||||
codeLine.lang = 'yaml'
|
lang,
|
||||||
|
functionType: 'codeLine'
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
this.appendChild(codeBlock, codeLine)
|
||||||
})
|
})
|
||||||
|
|
||||||
block.functionType = token.type
|
|
||||||
block.lang = codeBlock.lang = 'yaml'
|
|
||||||
this.codeBlocks.set(block.key, value)
|
|
||||||
this.appendChild(block, codeBlock)
|
this.appendChild(block, codeBlock)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'hr': {
|
case 'hr': {
|
||||||
value = '---'
|
value = token.marker
|
||||||
block = this.createBlock('hr', value)
|
block = this.createBlock('hr')
|
||||||
|
const thematicBreakContent = this.createBlock('span', {
|
||||||
|
text: value,
|
||||||
|
functionType: 'thematicBreakLine'
|
||||||
|
})
|
||||||
|
this.appendChild(block, thematicBreakContent)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'heading': {
|
case 'heading': {
|
||||||
const { headingStyle, depth, text, marker } = token
|
const { headingStyle, depth, text, marker } = token
|
||||||
value = headingStyle === 'atx' ? '#'.repeat(+depth) + ` ${text}` : text
|
value = headingStyle === 'atx' ? '#'.repeat(+depth) + ` ${text}` : text
|
||||||
block = this.createBlock(`h${depth}`, value)
|
block = this.createBlock(`h${depth}`, {
|
||||||
block.headingStyle = headingStyle
|
headingStyle
|
||||||
|
})
|
||||||
|
|
||||||
|
const headingContent = this.createBlock('span', {
|
||||||
|
text: value,
|
||||||
|
functionType: headingStyle === 'atx'? 'atxLine' : 'paragraphContent'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.appendChild(block, headingContent)
|
||||||
|
|
||||||
if (marker) {
|
if (marker) {
|
||||||
block.marker = marker
|
block.marker = marker
|
||||||
}
|
}
|
||||||
@ -95,15 +165,25 @@ const importRegister = ContentState => {
|
|||||||
block = this.createContainerBlock(lang, value)
|
block = this.createContainerBlock(lang, value)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
} else {
|
} else {
|
||||||
block = this.createBlock('pre')
|
block = this.createBlock('pre', {
|
||||||
const codeBlock = this.createBlock('code')
|
functionType: codeBlockStyle === 'fenced' ? 'fencecode' : 'indentcode',
|
||||||
|
lang
|
||||||
|
})
|
||||||
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang
|
||||||
|
})
|
||||||
value.split(LINE_BREAKS_REG).forEach(line => {
|
value.split(LINE_BREAKS_REG).forEach(line => {
|
||||||
const codeLine = this.createBlock('span', line)
|
const codeLine = this.createBlock('span', {
|
||||||
|
text: line
|
||||||
|
})
|
||||||
codeLine.lang = lang
|
codeLine.lang = lang
|
||||||
codeLine.functionType = 'codeLine'
|
codeLine.functionType = 'codeLine'
|
||||||
this.appendChild(codeBlock, codeLine)
|
this.appendChild(codeBlock, codeLine)
|
||||||
})
|
})
|
||||||
const inputBlock = this.createBlock('span', lang)
|
const inputBlock = this.createBlock('span', {
|
||||||
|
text: lang,
|
||||||
|
functionType: 'languageInput'
|
||||||
|
})
|
||||||
if (lang && !languageLoaded.has(lang)) {
|
if (lang && !languageLoaded.has(lang)) {
|
||||||
languageLoaded.add(lang)
|
languageLoaded.add(lang)
|
||||||
loadLanguage(lang)
|
loadLanguage(lang)
|
||||||
@ -121,10 +201,7 @@ const importRegister = ContentState => {
|
|||||||
console.warn(err)
|
console.warn(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
inputBlock.functionType = 'languageInput'
|
|
||||||
this.codeBlocks.set(block.key, value)
|
|
||||||
block.functionType = codeBlockStyle === 'fenced' ? 'fencecode' : 'indentcode'
|
|
||||||
block.lang = codeBlock.lang = lang
|
|
||||||
this.appendChild(block, inputBlock)
|
this.appendChild(block, inputBlock)
|
||||||
this.appendChild(block, codeBlock)
|
this.appendChild(block, codeBlock)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
@ -144,7 +221,9 @@ const importRegister = ContentState => {
|
|||||||
}
|
}
|
||||||
for (const headText of header) {
|
for (const headText of header) {
|
||||||
const i = header.indexOf(headText)
|
const i = header.indexOf(headText)
|
||||||
const th = this.createBlock('th', restoreTableEscapeCharacters(headText))
|
const th = this.createBlock('th', {
|
||||||
|
text: restoreTableEscapeCharacters(headText)
|
||||||
|
})
|
||||||
Object.assign(th, { align: align[i] || '', column: i })
|
Object.assign(th, { align: align[i] || '', column: i })
|
||||||
this.appendChild(theadRow, th)
|
this.appendChild(theadRow, th)
|
||||||
}
|
}
|
||||||
@ -152,7 +231,9 @@ const importRegister = ContentState => {
|
|||||||
const rowBlock = this.createBlock('tr')
|
const rowBlock = this.createBlock('tr')
|
||||||
for (const cell of row) {
|
for (const cell of row) {
|
||||||
const i = row.indexOf(cell)
|
const i = row.indexOf(cell)
|
||||||
const td = this.createBlock('td', restoreTableEscapeCharacters(cell))
|
const td = this.createBlock('td', {
|
||||||
|
text: restoreTableEscapeCharacters(cell)
|
||||||
|
})
|
||||||
Object.assign(td, { align: align[i] || '', column: i })
|
Object.assign(td, { align: align[i] || '', column: i })
|
||||||
this.appendChild(rowBlock, td)
|
this.appendChild(rowBlock, td)
|
||||||
}
|
}
|
||||||
@ -181,20 +262,20 @@ const importRegister = ContentState => {
|
|||||||
value += `\n${token.text}`
|
value += `\n${token.text}`
|
||||||
}
|
}
|
||||||
block = this.createBlock('p')
|
block = this.createBlock('p')
|
||||||
const lines = value.split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
const contentBlock = this.createBlock('span', {
|
||||||
for (const line of lines) {
|
text: value
|
||||||
this.appendChild(block, line)
|
})
|
||||||
}
|
this.appendChild(block, contentBlock)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'paragraph': {
|
case 'paragraph': {
|
||||||
value = token.text
|
value = token.text
|
||||||
block = this.createBlock('p')
|
block = this.createBlock('p')
|
||||||
const lines = value.split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
const contentBlock = this.createBlock('span', {
|
||||||
for (const line of lines) {
|
text: value
|
||||||
this.appendChild(block, line)
|
})
|
||||||
}
|
this.appendChild(block, contentBlock)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -226,13 +307,17 @@ const importRegister = ContentState => {
|
|||||||
case 'loose_item_start':
|
case 'loose_item_start':
|
||||||
case 'list_item_start': {
|
case 'list_item_start': {
|
||||||
const { listItemType, bulletMarkerOrDelimiter, checked, type } = token
|
const { listItemType, bulletMarkerOrDelimiter, checked, type } = token
|
||||||
block = this.createBlock('li')
|
block = this.createBlock('li', {
|
||||||
block.listItemType = checked !== undefined ? 'task' : listItemType
|
listItemType: checked !== undefined ? 'task' : listItemType,
|
||||||
block.bulletMarkerOrDelimiter = bulletMarkerOrDelimiter
|
bulletMarkerOrDelimiter,
|
||||||
block.isLooseListItem = type === 'loose_item_start'
|
isLooseListItem: type === 'loose_item_start'
|
||||||
|
})
|
||||||
|
|
||||||
if (checked !== undefined) {
|
if (checked !== undefined) {
|
||||||
const input = this.createBlock('input')
|
const input = this.createBlock('input', {
|
||||||
input.checked = checked
|
checked
|
||||||
|
})
|
||||||
|
|
||||||
this.appendChild(block, input)
|
this.appendChild(block, input)
|
||||||
}
|
}
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
@ -260,10 +345,10 @@ const importRegister = ContentState => {
|
|||||||
const { turndownConfig } = this
|
const { turndownConfig } = this
|
||||||
const turndownService = new TurndownService(turndownConfig)
|
const turndownService = new TurndownService(turndownConfig)
|
||||||
usePluginAddRules(turndownService)
|
usePluginAddRules(turndownService)
|
||||||
// remove double `\\` in Math but I dont know why there are two '\' when paste. @jocs
|
|
||||||
// fix #752, but I don't know why the vanlished.
|
// fix #752, but I don't know why the vanlished.
|
||||||
html = html.replace(/ /g, ' ')
|
html = html.replace(/ /g, ' ')
|
||||||
const markdown = turndownService.turndown(html) // .replace(/(\\)\\/g, '$1')
|
html = turnSoftBreakToSpan(html)
|
||||||
|
const markdown = turndownService.turndown(html)
|
||||||
return markdown
|
return markdown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +393,7 @@ const importRegister = ContentState => {
|
|||||||
// set cursor
|
// set cursor
|
||||||
const travel = blocks => {
|
const travel = blocks => {
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
const { key, text, children, editable, type, functionType } = block
|
const { key, text, children, editable } = block
|
||||||
if (text) {
|
if (text) {
|
||||||
const offset = text.indexOf(CURSOR_DNA)
|
const offset = text.indexOf(CURSOR_DNA)
|
||||||
if (offset > -1) {
|
if (offset > -1) {
|
||||||
@ -318,17 +403,6 @@ const importRegister = ContentState => {
|
|||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
end: { key, offset }
|
end: { key, offset }
|
||||||
}
|
}
|
||||||
// handle cursor in Math block, need to remove `CURSOR_DNA` in preview block
|
|
||||||
if (type === 'span' && functionType === 'codeLine') {
|
|
||||||
const preBlock = this.getParent(this.getParent(block))
|
|
||||||
const code = this.codeBlocks.get(preBlock.key)
|
|
||||||
if (!code) return
|
|
||||||
const offset = code.indexOf(CURSOR_DNA)
|
|
||||||
if (offset > -1) {
|
|
||||||
const newCode = code.substring(0, offset) + code.substring(offset + CURSOR_DNA.length)
|
|
||||||
this.codeBlocks.set(preBlock.key, newCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,7 +425,6 @@ const importRegister = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.importMarkdown = function (markdown) {
|
ContentState.prototype.importMarkdown = function (markdown) {
|
||||||
this.codeBlocks = new Map()
|
|
||||||
this.blocks = this.markdownToState(markdown)
|
this.blocks = this.markdownToState(markdown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ export const isEven = number => Math.abs(number) % 2 === 0
|
|||||||
export const isLengthEven = (str = '') => str.length % 2 === 0
|
export const isLengthEven = (str = '') => str.length % 2 === 0
|
||||||
|
|
||||||
export const snakeToCamel = name => name.replace(/_([a-z])/g, (p0, p1) => p1.toUpperCase())
|
export const snakeToCamel = name => name.replace(/_([a-z])/g, (p0, p1) => p1.toUpperCase())
|
||||||
|
|
||||||
|
export const camelToSnake = name => name.replace(/([A-Z])/g, (_, p) => `-${p.toLowerCase()}`)
|
||||||
/**
|
/**
|
||||||
* Are two arrays have intersection
|
* Are two arrays have intersection
|
||||||
*/
|
*/
|
||||||
|
@ -26,11 +26,13 @@ export const usePluginAddRules = turndownService => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// handle `soft line break` and `hard line break`
|
// handle `line break` in code block
|
||||||
// add `LINE_BREAK` to the end of soft line break and hard line break.
|
// add `LINE_BREAK` to the end of every code line but not the last line.
|
||||||
turndownService.addRule('lineBreak', {
|
turndownService.addRule('codeLineBreak', {
|
||||||
filter (node, options) {
|
filter (node, options) {
|
||||||
return node.nodeName === 'SPAN' && node.classList.contains(CLASS_OR_ID['AG_LINE']) && node.nextElementSibling
|
return (
|
||||||
|
node.nodeName === 'SPAN' && node.classList.contains(CLASS_OR_ID['AG_CODE_LINE']) && node.nextElementSibling
|
||||||
|
)
|
||||||
},
|
},
|
||||||
replacement (content, node, options) {
|
replacement (content, node, options) {
|
||||||
return content + LINE_BREAK
|
return content + LINE_BREAK
|
||||||
|
@ -141,12 +141,6 @@ kbd {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 .ag-gray,
|
|
||||||
h2 .ag-gray,
|
|
||||||
h3 .ag-gray {
|
|
||||||
font-size: .6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ag-header-tight-space {
|
.ag-header-tight-space {
|
||||||
margin-left: -.3em;
|
margin-left: -.3em;
|
||||||
}
|
}
|
||||||
@ -227,29 +221,48 @@ kbd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 40px;
|
font-size: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h3 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 .ag-gray {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
h2 .ag-gray {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
h3 .ag-gray {
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
h4 .ag-gray {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
h5 .ag-gray {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
h6 .ag-gray {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
blockquote,
|
blockquote,
|
||||||
ul,
|
ul,
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
# Basic Text Formatting
|
## Basic Text Formatting
|
||||||
|
|
||||||
|
**Strong** text __Also Strong__
|
||||||
|
|
||||||
|
~~strike~~ and `inline code`
|
||||||
|
|
||||||
|
<u>under line</u> and 4<sup>3</sup> H<sub>2</sub>O
|
||||||
|
|
||||||
*this is in italic* and _so is this_
|
*this is in italic* and _so is this_
|
||||||
|
|
||||||
@ -12,17 +18,19 @@
|
|||||||
|
|
||||||
<s>this is strike through text</s>
|
<s>this is strike through text</s>
|
||||||
|
|
||||||
|
So _a_ single _word_ followed _b_y _a_nother
|
||||||
|
|
||||||
|
So __a__ single __word__ followed __b__y __a__nother
|
||||||
|
|
||||||
|
## Some markdown extentions
|
||||||
|
|
||||||
|
This is emoji :man:
|
||||||
|
|
||||||
|
This is inline math $a \ne b$
|
||||||
|
|
||||||
## Paragraph
|
## Paragraph
|
||||||
|
|
||||||
A two trailing spaces and a new line
|
A two trailing spaces and a new line
|
||||||
makes a line break.
|
makes a line break.
|
||||||
|
|
||||||
Two new lines make a new paragraph.
|
Two new lines make a new paragraph.
|
||||||
|
|
||||||
## Failing Tests
|
|
||||||
|
|
||||||
```
|
|
||||||
So _a_ single _word_ followed _b_y _a_nother
|
|
||||||
|
|
||||||
So __a__ single __word__ followed __b__y __a__nother
|
|
||||||
```
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Code Blocks
|
# Code Blocks
|
||||||
|
|
||||||
|
## Indent Code Block
|
||||||
|
|
||||||
This line won't *have any markdown* formatting applied.
|
This line won't *have any markdown* formatting applied.
|
||||||
I can even write <b>HTML</b> and it will show up as text.
|
I can even write <b>HTML</b> and it will show up as text.
|
||||||
This is great for showing program source code, or HTML or even
|
This is great for showing program source code, or HTML or even
|
||||||
@ -9,6 +11,8 @@
|
|||||||
Within a paragraph, you can use backquotes to do the same thing.
|
Within a paragraph, you can use backquotes to do the same thing.
|
||||||
`This won't be *italic* or **bold** at all.`
|
`This won't be *italic* or **bold** at all.`
|
||||||
|
|
||||||
|
## Fence Code Block
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Headings
|
# Headings
|
||||||
|
|
||||||
|
## Setext heading
|
||||||
|
|
||||||
This is a huge header
|
This is a huge header
|
||||||
===
|
===
|
||||||
|
|
||||||
@ -9,6 +11,14 @@ this is a smaller header
|
|||||||
header
|
header
|
||||||
---
|
---
|
||||||
|
|
||||||
|
This is a huge header
|
||||||
|
==================
|
||||||
|
|
||||||
|
this is a smaller header
|
||||||
|
------------------
|
||||||
|
|
||||||
|
## Atx heading
|
||||||
|
|
||||||
## ATX Headings
|
## ATX Headings
|
||||||
|
|
||||||
# foo
|
# foo
|
||||||
@ -53,20 +63,6 @@ foo
|
|||||||
|
|
||||||
bar
|
bar
|
||||||
|
|
||||||
## Failing Tests
|
|
||||||
|
|
||||||
Headings and horizontal rules are shrinked to three characters because this simplify the parsing - maybe we should change this.
|
|
||||||
|
|
||||||
```
|
|
||||||
This is a huge header
|
|
||||||
==================
|
|
||||||
|
|
||||||
this is a smaller header
|
|
||||||
------------------
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
## Horizontal Rule
|
## Horizontal Rule
|
||||||
|
|
||||||
----------------
|
- - - - -- --- --- ----
|
||||||
```
|
|
||||||
|
@ -8682,10 +8682,10 @@ pretty-error@^2.0.2:
|
|||||||
renderkid "^2.0.1"
|
renderkid "^2.0.1"
|
||||||
utila "~0.4"
|
utila "~0.4"
|
||||||
|
|
||||||
prismjs@^1.16.0:
|
prismjs2@^1.15.1:
|
||||||
version "1.16.0"
|
version "1.15.1"
|
||||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.16.0.tgz#406eb2c8aacb0f5f0f1167930cb83835d10a4308"
|
resolved "https://registry.yarnpkg.com/prismjs2/-/prismjs2-1.15.1.tgz#6dda1b9aa7e8ecddf55b145f2189b605f89e2738"
|
||||||
integrity sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA==
|
integrity sha512-tDYrcjuYxi5VceNCniF7YjxFTHJv7unA5KbN9EVZh0hnKmEaxdSSe43Gagobvue5UnbnUSB0y+l5b8Y3C1cXkA==
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
clipboard "^2.0.0"
|
clipboard "^2.0.0"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user