mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 22:22:18 +08:00
Optimization of code block (#1445)
* duplicate css rule * remove all codeLine * Fix: #1446 * Fix #942 #1310 * Fix copy paste will add one more empty line in code block * remove debug codes * Fix update thematic break error * fix: #1447 * Update octokit/rest and url-loader * Fix: CI test error * Fix comment issue1 * Fix: escape charachters in code block
This commit is contained in:
parent
4a24ff0954
commit
5b8da2cdf4
@ -116,6 +116,7 @@ function greeting () {
|
|||||||
font: 'simple3d',
|
font: 'simple3d',
|
||||||
space: false
|
space: false
|
||||||
})
|
})
|
||||||
} else console.log(chalk.yellow.bold('\n lets-build'))
|
} else {
|
||||||
console.log()
|
console.log(chalk.yellow.bold('\n lets-build'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
- languageInput
|
- languageInput
|
||||||
|
|
||||||
- codeLine
|
- codeContent (used in code block)
|
||||||
|
|
||||||
- atxLine (can not contain soft line break and hard line break use in atx heading)
|
- atxLine (can not contain soft line break and hard line break use in atx heading)
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
- paragraphContent (defaultValue use in paragraph and setext heading)
|
- paragraphContent (defaultValue use in paragraph and setext heading)
|
||||||
|
|
||||||
- lang - only when it's code line
|
- lang - only when it's functionType is `codeContent`
|
||||||
|
|
||||||
- All prismjs support language or empty string
|
- All prismjs support language or empty string
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hfelix/electron-localshortcut": "^3.1.1",
|
"@hfelix/electron-localshortcut": "^3.1.1",
|
||||||
"@octokit/rest": "^16.30.1",
|
"@octokit/rest": "^16.31.0",
|
||||||
"arg": "^4.1.1",
|
"arg": "^4.1.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"ced": "^1.0.0",
|
"ced": "^1.0.0",
|
||||||
@ -155,7 +155,7 @@
|
|||||||
"svgo": "^1.3.0",
|
"svgo": "^1.3.0",
|
||||||
"svgo-loader": "^2.2.1",
|
"svgo-loader": "^2.2.1",
|
||||||
"to-string-loader": "^1.1.5",
|
"to-string-loader": "^1.1.5",
|
||||||
"url-loader": "^2.1.0",
|
"url-loader": "^2.2.0",
|
||||||
"vue-html-loader": "^1.2.4",
|
"vue-html-loader": "^1.2.4",
|
||||||
"vue-loader": "^15.7.1",
|
"vue-loader": "^15.7.1",
|
||||||
"vue-style-loader": "^4.1.2",
|
"vue-style-loader": "^4.1.2",
|
||||||
|
@ -103,12 +103,12 @@ export const updateSelectionMenus = (applicationMenu, { start, end, affiliation
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(/th|td/.test(start.type) && /th|td/.test(end.type)) ||
|
(/th|td/.test(start.type) && /th|td/.test(end.type)) ||
|
||||||
(start.type === 'span' && start.block.functionType === 'codeLine') ||
|
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
||||||
(end.type === 'span' && end.block.functionType === 'codeLine')
|
(end.type === 'span' && end.block.functionType === 'codeContent')
|
||||||
) {
|
) {
|
||||||
setParagraphMenuItemStatus(applicationMenu, false)
|
setParagraphMenuItemStatus(applicationMenu, false)
|
||||||
|
|
||||||
if (start.block.functionType === 'codeLine' || end.block.functionType === 'codeLine') {
|
if (start.block.functionType === 'codeContent' || end.block.functionType === 'codeContent') {
|
||||||
setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
||||||
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-paragraph-conte
|
|||||||
|
|
||||||
.ag-atx-line:empty::after,
|
.ag-atx-line:empty::after,
|
||||||
.ag-thematic-break-line:empty::after,
|
.ag-thematic-break-line:empty::after,
|
||||||
.ag-code-line:empty::after,
|
.ag-code-content:empty::after,
|
||||||
.ag-paragraph-content:empty::after {
|
.ag-paragraph-content:empty::after {
|
||||||
content: '\200B';
|
content: '\200B';
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-paragraph-conte
|
|||||||
.ag-atx-line,
|
.ag-atx-line,
|
||||||
.ag-thematic-break-line,
|
.ag-thematic-break-line,
|
||||||
.ag-paragraph-content,
|
.ag-paragraph-content,
|
||||||
.ag-code-line {
|
.ag-code-content {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@ -76,6 +76,7 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-paragraph-conte
|
|||||||
.ag-hard-line-break-space::after {
|
.ag-hard-line-break-space::after {
|
||||||
content: '↩';
|
content: '↩';
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-line-end {
|
.ag-line-end {
|
||||||
@ -402,7 +403,6 @@ li.ag-task-list-item > input[type=checkbox]::before {
|
|||||||
content: '';
|
content: '';
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 2px solid var(--editorColor50);
|
border: 2px solid var(--editorColor50);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -477,7 +477,7 @@ pre.ag-front-matter {
|
|||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.ag-front-matter span.ag-code-line:first-of-type:empty::after {
|
pre.ag-front-matter span.ag-code-content:first-of-type:empty::after {
|
||||||
content: 'Input YAML Front Matter...';
|
content: 'Input YAML Front Matter...';
|
||||||
color: var(--editorColor10);
|
color: var(--editorColor10);
|
||||||
}
|
}
|
||||||
@ -487,7 +487,7 @@ pre[data-role$='code'] span.ag-language-input:empty::after {
|
|||||||
color: var(--editorColor10);
|
color: var(--editorColor10);
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.ag-multiple-math span.ag-code-line:first-of-type:empty::after {
|
pre.ag-multiple-math span.ag-code-content:first-of-type:empty::after {
|
||||||
content: 'Input Mathematical Formula...';
|
content: 'Input Mathematical Formula...';
|
||||||
color: var(--editorColor10);
|
color: var(--editorColor10);
|
||||||
}
|
}
|
||||||
@ -673,7 +673,7 @@ span.ag-emoji-marked-text {
|
|||||||
.ag-emoji-marked-text::before {
|
.ag-emoji-marked-text::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: attr(data-emoji);
|
content: attr(data-emoji);
|
||||||
color: var(--editorColor);
|
color: #000000;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: -1.3em;
|
left: -1.3em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
@ -70,9 +70,6 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_BULLET_LIST',
|
'AG_BULLET_LIST',
|
||||||
'AG_BULLET_LIST_ITEM',
|
'AG_BULLET_LIST_ITEM',
|
||||||
'AG_CHECKBOX_CHECKED',
|
'AG_CHECKBOX_CHECKED',
|
||||||
'AG_CODE_LINE',
|
|
||||||
'AG_CODE_LINE_ADD',
|
|
||||||
'AG_CODE_LINE_MINUS',
|
|
||||||
'AG_CONTAINER_BLOCK',
|
'AG_CONTAINER_BLOCK',
|
||||||
'AG_CONTAINER_PREVIEW',
|
'AG_CONTAINER_PREVIEW',
|
||||||
'AG_CONTAINER_ICON',
|
'AG_CONTAINER_ICON',
|
||||||
|
@ -308,7 +308,7 @@ const backspaceCtrl = ContentState => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
block.type === 'span' &&
|
block.type === 'span' &&
|
||||||
block.functionType === 'codeLine' &&
|
block.functionType === 'codeContent' &&
|
||||||
left === 0 &&
|
left === 0 &&
|
||||||
!block.preSibling
|
!block.preSibling
|
||||||
) {
|
) {
|
||||||
|
@ -136,7 +136,7 @@ const clickCtrl = ContentState => {
|
|||||||
start.key === end.key &&
|
start.key === end.key &&
|
||||||
start.offset !== end.offset &&
|
start.offset !== end.offset &&
|
||||||
HAS_TEXT_BLOCK_REG.test(block.type) &&
|
HAS_TEXT_BLOCK_REG.test(block.type) &&
|
||||||
block.functionType !== 'codeLine' &&
|
block.functionType !== 'codeContent' &&
|
||||||
block.functionType !== 'languageInput'
|
block.functionType !== 'languageInput'
|
||||||
) {
|
) {
|
||||||
const reference = this.getPositionReference()
|
const reference = this.getPositionReference()
|
||||||
|
@ -79,32 +79,41 @@ const codeBlockCtrl = ContentState => {
|
|||||||
if (block.type === 'span') {
|
if (block.type === 'span') {
|
||||||
block = this.getParent(block)
|
block = this.getParent(block)
|
||||||
}
|
}
|
||||||
// if it's not a p block, no need to update
|
// If it's not a p block, no need to update
|
||||||
if (block.type !== 'p') return false
|
if (block.type !== 'p') return false
|
||||||
// if p block's children are more than one, no need to update
|
// If p block's children are more than one, no need to update
|
||||||
if (block.children.length !== 1) return false
|
if (block.children.length !== 1) return false
|
||||||
|
|
||||||
const { text } = block.children[0]
|
const { text } = block.children[0]
|
||||||
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 firstLine = this.createBlock('span', { text: code })
|
|
||||||
const language = lang || (match ? match[1] : '')
|
const language = lang || (match ? match[1] : '')
|
||||||
const inputBlock = this.createBlock('span', { text: language })
|
const codeBlock = this.createBlock('code', {
|
||||||
|
lang: language
|
||||||
|
})
|
||||||
|
const codeContent = this.createBlock('span', {
|
||||||
|
text: code,
|
||||||
|
lang: language,
|
||||||
|
functionType: 'codeContent'
|
||||||
|
})
|
||||||
|
const inputBlock = this.createBlock('span', {
|
||||||
|
text: language,
|
||||||
|
functionType: 'languageInput'
|
||||||
|
})
|
||||||
|
|
||||||
loadLanguage(language)
|
loadLanguage(language)
|
||||||
inputBlock.functionType = 'languageInput'
|
|
||||||
block.type = 'pre'
|
block.type = 'pre'
|
||||||
block.functionType = 'fencecode'
|
block.functionType = 'fencecode'
|
||||||
block.lang = language
|
block.lang = language
|
||||||
block.text = ''
|
block.text = ''
|
||||||
block.history = null
|
block.history = null
|
||||||
block.children = []
|
block.children = []
|
||||||
codeBlock.lang = language
|
|
||||||
firstLine.lang = language
|
this.appendChild(codeBlock, codeContent)
|
||||||
firstLine.functionType = 'codeLine'
|
|
||||||
this.appendChild(codeBlock, firstLine)
|
|
||||||
this.appendChild(block, inputBlock)
|
this.appendChild(block, inputBlock)
|
||||||
this.appendChild(block, codeBlock)
|
this.appendChild(block, codeBlock)
|
||||||
const { key } = firstLine
|
const { key } = codeContent
|
||||||
const offset = code.length
|
const offset = code.length
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
const LINE_BREAKS_REG = /\n/
|
|
||||||
const FUNCTION_TYPE_LANG = {
|
const FUNCTION_TYPE_LANG = {
|
||||||
multiplemath: 'latex',
|
multiplemath: 'latex',
|
||||||
flowchart: 'yaml',
|
flowchart: 'yaml',
|
||||||
@ -33,22 +32,20 @@ const containerCtrl = ContentState => {
|
|||||||
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 = value.replace(/^\s+/, '')
|
||||||
const codeLine = this.createBlock('span', {
|
const codeContent = this.createBlock('span', {
|
||||||
text: line,
|
text: value,
|
||||||
functionType: 'codeLine',
|
lang,
|
||||||
lang
|
functionType: 'codeContent'
|
||||||
})
|
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
})
|
})
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
} else {
|
} else {
|
||||||
const emptyLine = this.createBlock('span', {
|
const emptyCodeContent = this.createBlock('span', {
|
||||||
functionType: 'codeLine',
|
functionType: 'codeContent',
|
||||||
lang
|
lang
|
||||||
})
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, emptyLine)
|
this.appendChild(codeBlock, emptyCodeContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview = this.createBlock('div', {
|
const preview = this.createBlock('div', {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import selection from '../selection'
|
import selection from '../selection'
|
||||||
import { CLASS_OR_ID } from '../config'
|
import { CLASS_OR_ID } from '../config'
|
||||||
|
import { escapeHtml } from '../utils'
|
||||||
import { getSanitizeHtml } from '../utils/exportHtml'
|
import { getSanitizeHtml } from '../utils/exportHtml'
|
||||||
import ExportMarkdown from '../utils/exportMarkdown'
|
import ExportMarkdown from '../utils/exportMarkdown'
|
||||||
import marked from '../parser/marked'
|
import marked from '../parser/marked'
|
||||||
@ -34,6 +35,19 @@ const copyCutCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.getClipBoradData = function () {
|
ContentState.prototype.getClipBoradData = function () {
|
||||||
|
const { start, end } = selection.getCursorRange()
|
||||||
|
if (start.key === end.key) {
|
||||||
|
const startBlock = this.getBlock(start.key)
|
||||||
|
const { type, text, functionType } = startBlock
|
||||||
|
// Fix issue #942
|
||||||
|
if (type === 'span' && functionType === 'codeContent') {
|
||||||
|
const selectedText = escapeHtml(text.substring(start.offset, end.offset))
|
||||||
|
return {
|
||||||
|
html: marked(selectedText),
|
||||||
|
text: selectedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const html = selection.getSelectionHtml()
|
const html = selection.getSelectionHtml()
|
||||||
const wrapper = document.createElement('div')
|
const wrapper = document.createElement('div')
|
||||||
wrapper.innerHTML = html
|
wrapper.innerHTML = html
|
||||||
@ -109,8 +123,8 @@ const copyCutCtrl = ContentState => {
|
|||||||
const id = cf.id
|
const id = cf.id
|
||||||
const block = this.getBlock(id)
|
const block = this.getBlock(id)
|
||||||
const language = block.lang || ''
|
const language = block.lang || ''
|
||||||
const selectedCodeLines = cf.querySelectorAll('.ag-code-line')
|
const codeContent = cf.querySelector('.ag-code-content')
|
||||||
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
|
const value = escapeHtml(codeContent.textContent)
|
||||||
cf.innerHTML = `<code class="language-${language}">${value}</code>`
|
cf.innerHTML = `<code class="language-${language}">${value}</code>`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +139,9 @@ const copyCutCtrl = ContentState => {
|
|||||||
|
|
||||||
const htmlBlock = wrapper.querySelectorAll('figure[data-role=\'HTML\']')
|
const htmlBlock = wrapper.querySelectorAll('figure[data-role=\'HTML\']')
|
||||||
for (const hb of htmlBlock) {
|
for (const hb of htmlBlock) {
|
||||||
const selectedCodeLines = hb.querySelectorAll('span.ag-code-line')
|
const codeContent = hb.querySelector('.ag-code-content')
|
||||||
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
|
|
||||||
const pre = document.createElement('pre')
|
const pre = document.createElement('pre')
|
||||||
pre.textContent = value
|
pre.textContent = codeContent.textContent
|
||||||
hb.replaceWith(pre)
|
hb.replaceWith(pre)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +155,8 @@ const copyCutCtrl = ContentState => {
|
|||||||
for (const mb of mathBlock) {
|
for (const mb of mathBlock) {
|
||||||
const preElement = mb.querySelector('pre[data-role]')
|
const preElement = mb.querySelector('pre[data-role]')
|
||||||
const functionType = preElement.getAttribute('data-role')
|
const functionType = preElement.getAttribute('data-role')
|
||||||
const selectedCodeLines = mb.querySelectorAll('span.ag-code-line')
|
const codeContent = mb.querySelector('.ag-code-content')
|
||||||
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
|
const value = codeContent.textContent
|
||||||
let pre
|
let pre
|
||||||
switch (functionType) {
|
switch (functionType) {
|
||||||
case 'multiplemath':
|
case 'multiplemath':
|
||||||
@ -165,7 +178,6 @@ 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 }
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,11 @@ const deleteCtrl = ContentState => {
|
|||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (nextBlock && /h\d|span/.test(nextBlock.type)) {
|
if (nextBlock && /h\d|span/.test(nextBlock.type)) {
|
||||||
if (nextBlock.functionType === 'codeLine' && nextBlock.nextSibling) {
|
// if cursor at the end of code block-language input, do nothing!
|
||||||
// if code block more than one line, do nothing!
|
if (nextBlock.functionType === 'codeContent' && startBlock.functionType === 'languageInput') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startBlock.text += nextBlock.text
|
startBlock.text += nextBlock.text
|
||||||
|
|
||||||
const toBeRemoved = [nextBlock]
|
const toBeRemoved = [nextBlock]
|
||||||
|
@ -237,31 +237,21 @@ const enterCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
} else if (
|
} else if (
|
||||||
block.type === 'span' && block.functionType === 'codeLine'
|
block.type === 'span' && block.functionType === 'codeContent'
|
||||||
) {
|
) {
|
||||||
const { text } = block
|
const { text, key } = block
|
||||||
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', {
|
'\n' +
|
||||||
text: `${indent}${newLineText}`,
|
(autoIndent ? indent + ' '.repeat(this.tabSize) + '\n' : '') +
|
||||||
functionType: block.functionType,
|
indent +
|
||||||
lang: block.lang
|
text.substring(start.offset)
|
||||||
})
|
|
||||||
|
let offset = start.offset + 1 + indent.length
|
||||||
|
|
||||||
this.insertAfter(newLine, block)
|
|
||||||
let { key } = newLine
|
|
||||||
let offset = indent.length
|
|
||||||
if (autoIndent) {
|
if (autoIndent) {
|
||||||
const emptyLine = this.createBlock('span', {
|
offset += this.tabSize
|
||||||
text: indent + ' '.repeat(this.tabSize)
|
|
||||||
})
|
|
||||||
emptyLine.functionType = block.functionType
|
|
||||||
emptyLine.lang = block.lang
|
|
||||||
this.insertAfter(emptyLine, block)
|
|
||||||
key = emptyLine.key
|
|
||||||
offset = indent.length + this.tabSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
|
@ -16,7 +16,7 @@ const imageCtrl = ContentState => {
|
|||||||
if (
|
if (
|
||||||
block.type === 'span' &&
|
block.type === 'span' &&
|
||||||
(
|
(
|
||||||
block.functionType === 'codeLine' ||
|
block.functionType === 'codeContent' ||
|
||||||
block.functionType === 'languageInput' ||
|
block.functionType === 'languageInput' ||
|
||||||
block.functionType === 'thematicBreakLine'
|
block.functionType === 'thematicBreakLine'
|
||||||
)
|
)
|
||||||
|
@ -28,6 +28,7 @@ import linkCtrl from './linkCtrl'
|
|||||||
import dragDropCtrl from './dragDropCtrl'
|
import dragDropCtrl from './dragDropCtrl'
|
||||||
import importMarkdown from '../utils/importMarkdown'
|
import importMarkdown from '../utils/importMarkdown'
|
||||||
import Cursor from '../selection/cursor'
|
import Cursor from '../selection/cursor'
|
||||||
|
import escapeCharactersMap, { escapeCharacters } from '../parser/escapeCharacter'
|
||||||
|
|
||||||
const prototypes = [
|
const prototypes = [
|
||||||
tabCtrl,
|
tabCtrl,
|
||||||
@ -253,6 +254,13 @@ class ContentState {
|
|||||||
blockData.functionType = 'paragraphContent'
|
blockData.functionType = 'paragraphContent'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extras.functionType === 'codeContent' && extras.text) {
|
||||||
|
const CHAR_REG = new RegExp(`(${escapeCharacters.join('|')})`, 'gi')
|
||||||
|
extras.text = extras.text.replace(CHAR_REG, (_, p) => {
|
||||||
|
return escapeCharactersMap[p]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(blockData, extras)
|
Object.assign(blockData, extras)
|
||||||
return blockData
|
return blockData
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ const inputCtrl = ContentState => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.inputHandler = function (event) {
|
ContentState.prototype.inputHandler = function (event, notEqual = false) {
|
||||||
const { start, end } = selection.getCursorRange()
|
const { start, end } = selection.getCursorRange()
|
||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
return
|
return
|
||||||
@ -86,6 +86,27 @@ const inputCtrl = ContentState => {
|
|||||||
const key = start.key
|
const key = start.key
|
||||||
const block = this.getBlock(key)
|
const block = this.getBlock(key)
|
||||||
const paragraph = document.querySelector(`#${key}`)
|
const paragraph = document.querySelector(`#${key}`)
|
||||||
|
|
||||||
|
// Fix issue 1447
|
||||||
|
// Fixme: any better solution?
|
||||||
|
if (
|
||||||
|
oldStart.key === oldEnd.key &&
|
||||||
|
oldStart.offset === oldEnd.offset &&
|
||||||
|
block.text.endsWith('\n') &&
|
||||||
|
oldStart.offset === block.text.length &&
|
||||||
|
event.inputType === 'insertText'
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
block.text += event.data
|
||||||
|
const offset = block.text.length
|
||||||
|
this.cursor = {
|
||||||
|
start: { key, offset },
|
||||||
|
end: { key, offset }
|
||||||
|
}
|
||||||
|
this.singleRender(block)
|
||||||
|
return this.inputHandler(event, true)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -123,7 +144,7 @@ const inputCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// auto pair (not need to auto pair in math block)
|
// auto pair (not need to auto pair in math block)
|
||||||
if (block && block.text !== text) {
|
if (block && (block.text !== text || notEqual)) {
|
||||||
if (
|
if (
|
||||||
start.key === end.key &&
|
start.key === end.key &&
|
||||||
start.offset === end.offset &&
|
start.offset === end.offset &&
|
||||||
@ -173,7 +194,7 @@ const inputCtrl = ContentState => {
|
|||||||
((autoPairQuote && /[']{1}/.test(inputChar) && !(/[a-zA-Z\d]{1}/.test(preInputChar))) ||
|
((autoPairQuote && /[']{1}/.test(inputChar) && !(/[a-zA-Z\d]{1}/.test(preInputChar))) ||
|
||||||
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
||||||
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
|
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
|
||||||
(block.functionType !== 'codeLine' && !isInInlineMath && !isInInlineCode && autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)))
|
(block.functionType !== 'codeContent' && !isInInlineMath && !isInInlineCode && autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)))
|
||||||
) {
|
) {
|
||||||
needRender = true
|
needRender = true
|
||||||
text = BRACKET_HASH[event.data]
|
text = BRACKET_HASH[event.data]
|
||||||
@ -250,8 +271,7 @@ const inputCtrl = ContentState => {
|
|||||||
|
|
||||||
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, !!checkQuickInsert)
|
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, !!checkQuickInsert)
|
||||||
|
|
||||||
// Update preview content of math block
|
if (block && block.type === 'span' && block.functionType === 'codeContent') {
|
||||||
if (block && block.type === 'span' && block.functionType === 'codeLine') {
|
|
||||||
needRender = true
|
needRender = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ import selection from '../selection'
|
|||||||
import { PARAGRAPH_TYPES, DEFAULT_TURNDOWN_CONFIG } from '../config'
|
import { PARAGRAPH_TYPES, DEFAULT_TURNDOWN_CONFIG } from '../config'
|
||||||
import ExportMarkdown from '../utils/exportMarkdown'
|
import ExportMarkdown from '../utils/exportMarkdown'
|
||||||
|
|
||||||
const LINE_BREAKS_REG = /\n/
|
|
||||||
|
|
||||||
// get header level
|
// get header level
|
||||||
// eg: h1 => 1
|
// eg: h1 => 1
|
||||||
// h2 => 2
|
// h2 => 2
|
||||||
@ -99,15 +97,15 @@ const paragraphCtrl = ContentState => {
|
|||||||
const codeBlock = this.createBlock('code', {
|
const codeBlock = this.createBlock('code', {
|
||||||
lang
|
lang
|
||||||
})
|
})
|
||||||
const emptyLine = this.createBlock('span', {
|
const emptyCodeContent = this.createBlock('span', {
|
||||||
functionType: 'codeLine',
|
functionType: 'codeContent',
|
||||||
lang
|
lang
|
||||||
})
|
})
|
||||||
|
|
||||||
this.appendChild(codeBlock, emptyLine)
|
this.appendChild(codeBlock, emptyCodeContent)
|
||||||
this.appendChild(frontMatter, codeBlock)
|
this.appendChild(frontMatter, codeBlock)
|
||||||
this.insertBefore(frontMatter, firstBlock)
|
this.insertBefore(frontMatter, firstBlock)
|
||||||
const { key } = emptyLine
|
const { key } = emptyCodeContent
|
||||||
const offset = 0
|
const offset = 0
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset },
|
start: { key, offset },
|
||||||
@ -248,39 +246,21 @@ const paragraphCtrl = ContentState => {
|
|||||||
// change fenced code block to p paragraph
|
// change fenced code block to p paragraph
|
||||||
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 codeContent = preBlock.children[1].children[0]
|
||||||
preBlock.type = 'p'
|
preBlock.type = 'p'
|
||||||
preBlock.children = []
|
preBlock.children = []
|
||||||
|
|
||||||
const newParagraphBlock = this.createBlockP(codeLines.map(l => l.text).join('\n'))
|
const newParagraphBlock = this.createBlockP(codeContent.text)
|
||||||
this.insertBefore(newParagraphBlock, preBlock)
|
this.insertBefore(newParagraphBlock, preBlock)
|
||||||
|
|
||||||
this.removeBlock(preBlock)
|
this.removeBlock(preBlock)
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
|
|
||||||
const key = newParagraphBlock.children[0].key
|
const key = newParagraphBlock.children[0].key
|
||||||
let startOffset = 0
|
|
||||||
let endOffset = 0
|
|
||||||
let startStop = false
|
|
||||||
let endStop = false
|
|
||||||
for (const line of codeLines) {
|
|
||||||
if (line.key !== start.key && !startStop) {
|
|
||||||
startOffset += line.text.length + 1
|
|
||||||
} else {
|
|
||||||
startOffset += start.offset
|
|
||||||
startStop = true
|
|
||||||
}
|
|
||||||
if (line.key !== end.key && !endStop) {
|
|
||||||
endOffset += line.text.length + 1
|
|
||||||
} else {
|
|
||||||
endOffset += end.offset
|
|
||||||
endStop = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset: startOffset },
|
start: { key, offset: start.offset },
|
||||||
end: { key, offset: endOffset }
|
end: { key, offset: end.offset }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (start.key === end.key) {
|
if (start.key === end.key) {
|
||||||
@ -293,24 +273,20 @@ const paragraphCtrl = ContentState => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const codeBlock = this.createBlock('code', {
|
const codeBlock = this.createBlock('code', {
|
||||||
lang: ''
|
lang
|
||||||
})
|
})
|
||||||
|
|
||||||
const inputBlock = this.createBlock('span', {
|
const inputBlock = this.createBlock('span', {
|
||||||
functionType: 'languageInput'
|
functionType: 'languageInput'
|
||||||
})
|
})
|
||||||
|
|
||||||
const codes = startBlock.text.split('\n')
|
const codeContent = this.createBlock('span', {
|
||||||
|
text: startBlock.text,
|
||||||
for (const code of codes) {
|
lang,
|
||||||
const codeLine = this.createBlock('span', {
|
functionType: 'codeContent'
|
||||||
text: code,
|
})
|
||||||
functionType: 'codeLine',
|
|
||||||
lang
|
|
||||||
})
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
this.appendChild(preBlock, inputBlock)
|
this.appendChild(preBlock, inputBlock)
|
||||||
this.appendChild(preBlock, codeBlock)
|
this.appendChild(preBlock, codeBlock)
|
||||||
this.insertBefore(preBlock, anchorBlock)
|
this.insertBefore(preBlock, anchorBlock)
|
||||||
@ -345,20 +321,15 @@ const paragraphCtrl = ContentState => {
|
|||||||
|
|
||||||
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()
|
||||||
|
const codeContent = this.createBlock('span', {
|
||||||
markdown.split(LINE_BREAKS_REG).forEach(text => {
|
text: markdown,
|
||||||
const codeLine = this.createBlock('span', {
|
lang,
|
||||||
text,
|
functionType: 'codeContent'
|
||||||
lang,
|
|
||||||
functionType: 'codeLine'
|
|
||||||
})
|
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
})
|
})
|
||||||
const inputBlock = this.createBlock('span', {
|
const inputBlock = this.createBlock('span', {
|
||||||
functionType: 'languageInput'
|
functionType: 'languageInput'
|
||||||
})
|
})
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
this.appendChild(preBlock, inputBlock)
|
this.appendChild(preBlock, inputBlock)
|
||||||
this.appendChild(preBlock, codeBlock)
|
this.appendChild(preBlock, codeBlock)
|
||||||
this.insertAfter(preBlock, referBlock)
|
this.insertAfter(preBlock, referBlock)
|
||||||
@ -774,20 +745,19 @@ const paragraphCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
// Handler selectAll in code block. only select all the code block conent.
|
// Handler selectAll in code block. only select all the code block conent.
|
||||||
// `code block` here is Math, HTML, BLOCK CODE, Mermaid, vega-lite, flowchart, front-matter etc...
|
// `code block` here is Math, HTML, BLOCK CODE, Mermaid, vega-lite, flowchart, front-matter etc...
|
||||||
if (startBlock.type === 'span' && startBlock.functionType === 'codeLine') {
|
if (startBlock.type === 'span' && startBlock.functionType === 'codeContent') {
|
||||||
const codeBlock = this.getParent(startBlock)
|
const { key } = startBlock
|
||||||
const firstCodeLine = this.firstInDescendant(codeBlock)
|
|
||||||
const lastCodeLine = this.lastInDescendant(codeBlock)
|
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: {
|
start: {
|
||||||
key: firstCodeLine.key,
|
key,
|
||||||
offset: 0
|
offset: 0
|
||||||
},
|
},
|
||||||
end: {
|
end: {
|
||||||
key: lastCodeLine.key,
|
key,
|
||||||
offset: lastCodeLine.text.length
|
offset: startBlock.text.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
}
|
}
|
||||||
// Handler language input, only select language info only...
|
// Handler language input, only select language info only...
|
||||||
|
@ -314,42 +314,18 @@ const pasteCtrl = ContentState => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startBlock.type === 'span' && startBlock.functionType === 'codeLine') {
|
if (startBlock.type === 'span' && startBlock.functionType === 'codeContent') {
|
||||||
let referenceBlock = startBlock
|
|
||||||
const blockText = startBlock.text
|
const blockText = startBlock.text
|
||||||
const prePartText = blockText.substring(0, start.offset)
|
const prePartText = blockText.substring(0, start.offset)
|
||||||
const postPartText = blockText.substring(end.offset)
|
const postPartText = blockText.substring(end.offset)
|
||||||
const textList = text.split(LINE_BREAKS_REG)
|
startBlock.text = prePartText + text + postPartText
|
||||||
if (textList.length > 1) {
|
const { key } = startBlock
|
||||||
textList.forEach((line, i) => {
|
const offset = start.offset + text.length
|
||||||
if (i === 0) {
|
this.cursor = {
|
||||||
startBlock.text = prePartText + line
|
start: { key, offset },
|
||||||
} else {
|
end: { key, offset }
|
||||||
line = i === textList.length - 1 ? line + postPartText : line
|
|
||||||
const lineBlock = this.createBlock('span', { text: line })
|
|
||||||
lineBlock.functionType = startBlock.functionType
|
|
||||||
lineBlock.lang = startBlock.lang
|
|
||||||
this.insertAfter(lineBlock, referenceBlock)
|
|
||||||
referenceBlock = lineBlock
|
|
||||||
if (i === textList.length - 1) {
|
|
||||||
const { key } = lineBlock
|
|
||||||
const offset = line.length
|
|
||||||
this.cursor = {
|
|
||||||
start: { key, offset },
|
|
||||||
end: { key, offset }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
startBlock.text = prePartText + text + postPartText
|
|
||||||
const key = startBlock.key
|
|
||||||
const offset = start.offset + text.length
|
|
||||||
this.cursor = {
|
|
||||||
start: { key, offset },
|
|
||||||
end: { key, offset }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.partialRender()
|
return this.partialRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ const tabCtrl = ContentState => {
|
|||||||
start.key === end.key &&
|
start.key === end.key &&
|
||||||
start.offset === end.offset &&
|
start.offset === end.offset &&
|
||||||
HAS_TEXT_BLOCK_REG.test(startBlock.type) &&
|
HAS_TEXT_BLOCK_REG.test(startBlock.type) &&
|
||||||
startBlock.functionType !== 'codeLine' && // code line has no inline syntax
|
startBlock.functionType !== 'codeContent' && // code content has no inline syntax
|
||||||
startBlock.functionType !== 'languageInput' // language input textarea has no inline syntax
|
startBlock.functionType !== 'languageInput' // language input textarea has no inline syntax
|
||||||
) {
|
) {
|
||||||
const { text, key } = startBlock
|
const { text, key } = startBlock
|
||||||
@ -312,7 +312,7 @@ const tabCtrl = ContentState => {
|
|||||||
start.key === end.key &&
|
start.key === end.key &&
|
||||||
start.offset === end.offset &&
|
start.offset === end.offset &&
|
||||||
startBlock.type === 'span' &&
|
startBlock.type === 'span' &&
|
||||||
(!startBlock.functionType || startBlock.functionType === 'codeLine' && /markup|html|xml|svg|mathml/.test(startBlock.lang))
|
(!startBlock.functionType || startBlock.functionType === 'codeContent' && /markup|html|xml|svg|mathml/.test(startBlock.lang))
|
||||||
) {
|
) {
|
||||||
const { text } = startBlock
|
const { text } = startBlock
|
||||||
const lastWordBeforeCursor = text.substring(0, start.offset).split(/\s+/).pop()
|
const lastWordBeforeCursor = text.substring(0, start.offset).split(/\s+/).pop()
|
||||||
|
@ -49,7 +49,7 @@ const tableBlockCtrl = ContentState => {
|
|||||||
const { type, functionType } = block
|
const { type, functionType } = block
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'span':
|
case 'span':
|
||||||
if (functionType === 'codeLine') {
|
if (functionType === 'codeContent') {
|
||||||
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
return this.closest(block, 'figure') || this.closest(block, 'pre')
|
||||||
} else {
|
} else {
|
||||||
return this.getParent(block)
|
return this.getParent(block)
|
||||||
|
@ -68,7 +68,7 @@ const updateCtrl = ContentState => {
|
|||||||
ContentState.prototype.checkInlineUpdate = function (block) {
|
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||||
// table cell can not have blocks in it
|
// table cell can not have blocks in it
|
||||||
if (/th|td|figure/.test(block.type)) return false
|
if (/th|td|figure/.test(block.type)) return false
|
||||||
if (/codeLine|languageInput/.test(block.functionType)) return false
|
if (/codeContent|languageInput/.test(block.functionType)) return false
|
||||||
|
|
||||||
let line = null
|
let line = null
|
||||||
const { text } = block
|
const { text } = block
|
||||||
@ -84,7 +84,7 @@ const updateCtrl = ContentState => {
|
|||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case (!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
case (!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1):
|
||||||
return this.updateHr(block, hr, line)
|
return this.updateThematicBreak(block, hr, line)
|
||||||
|
|
||||||
case !!bullet:
|
case !!bullet:
|
||||||
return this.updateList(block, 'bullet', bullet, line)
|
return this.updateList(block, 'bullet', bullet, line)
|
||||||
@ -115,7 +115,7 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Thematic break
|
// Thematic break
|
||||||
ContentState.prototype.updateHr = function (block, marker, line) {
|
ContentState.prototype.updateThematicBreak = function (block, marker, line) {
|
||||||
// If the block is already thematic break, no need to update.
|
// If the block is already thematic break, no need to update.
|
||||||
if (block.type === 'hr') return null
|
if (block.type === 'hr') return null
|
||||||
const text = line.text
|
const text = line.text
|
||||||
@ -124,9 +124,10 @@ const updateCtrl = ContentState => {
|
|||||||
let thematicLine = ''
|
let thematicLine = ''
|
||||||
const postParagraphLines = []
|
const postParagraphLines = []
|
||||||
let thematicLineHasPushed = false
|
let thematicLineHasPushed = false
|
||||||
|
|
||||||
for (const l of lines) {
|
for (const l of lines) {
|
||||||
if (/ {0,3}(?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*$/.test(l) && !thematicLineHasPushed) {
|
/* eslint-disable no-useless-escape */
|
||||||
|
if (/ {0,3}(?:\* *\* *\*|- *- *-|_ *_ *_)[ \*\-\_]*$/.test(l) && !thematicLineHasPushed) {
|
||||||
|
/* eslint-enable no-useless-escape */
|
||||||
thematicLine = l
|
thematicLine = l
|
||||||
thematicLineHasPushed = true
|
thematicLineHasPushed = true
|
||||||
} else if (!thematicLineHasPushed) {
|
} else if (!thematicLineHasPushed) {
|
||||||
@ -155,9 +156,12 @@ const updateCtrl = ContentState => {
|
|||||||
this.removeBlock(block)
|
this.removeBlock(block)
|
||||||
const { start, end } = this.cursor
|
const { start, end } = this.cursor
|
||||||
const key = thematicBlock.children[0].key
|
const key = thematicBlock.children[0].key
|
||||||
|
const preParagraphLength = preParagraphLines.reduce((acc, i) => acc + i.length + 1, 0) // Add one, because the `\n`
|
||||||
|
const startOffset = start.offset - preParagraphLength
|
||||||
|
const endOffset = end.offset - preParagraphLength
|
||||||
this.cursor = {
|
this.cursor = {
|
||||||
start: { key, offset: start.offset },
|
start: { key, offset: startOffset },
|
||||||
end: { key, offset: end.offset }
|
end: { key, offset: endOffset }
|
||||||
}
|
}
|
||||||
return thematicBlock
|
return thematicBlock
|
||||||
}
|
}
|
||||||
@ -519,15 +523,16 @@ const updateCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentState.prototype.updateIndentCode = function (block, line) {
|
ContentState.prototype.updateIndentCode = function (block, line) {
|
||||||
|
const lang = ''
|
||||||
const codeBlock = this.createBlock('code', {
|
const codeBlock = this.createBlock('code', {
|
||||||
lang: ''
|
lang
|
||||||
})
|
})
|
||||||
const inputBlock = this.createBlock('span', {
|
const inputBlock = this.createBlock('span', {
|
||||||
functionType: 'languageInput'
|
functionType: 'languageInput'
|
||||||
})
|
})
|
||||||
const preBlock = this.createBlock('pre', {
|
const preBlock = this.createBlock('pre', {
|
||||||
functionType: 'indentcode',
|
functionType: 'indentcode',
|
||||||
lang: ''
|
lang
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = line ? line.text : block.text
|
const text = line ? line.text : block.text
|
||||||
@ -545,15 +550,13 @@ const updateCtrl = ContentState => {
|
|||||||
paragraphLines.push(l)
|
paragraphLines.push(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
codeLines.forEach(text => {
|
const codeContent = this.createBlock('span', {
|
||||||
const codeLine = this.createBlock('span', {
|
text: codeLines.join('\n'),
|
||||||
text,
|
functionType: 'codeContent',
|
||||||
functionType: 'codeLine',
|
lang
|
||||||
lang: ''
|
|
||||||
})
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
this.appendChild(preBlock, inputBlock)
|
this.appendChild(preBlock, inputBlock)
|
||||||
this.appendChild(preBlock, codeBlock)
|
this.appendChild(preBlock, codeBlock)
|
||||||
this.insertBefore(preBlock, block)
|
this.insertBefore(preBlock, block)
|
||||||
|
@ -275,7 +275,7 @@ class Keyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const block = contentState.getBlock(anchor.key)
|
const block = contentState.getBlock(anchor.key)
|
||||||
if (anchor.key === focus.key && anchor.offset !== focus.offset && block.functionType !== 'codeLine') {
|
if (anchor.key === focus.key && anchor.offset !== focus.offset && block.functionType !== 'codeContent') {
|
||||||
const reference = contentState.getPositionReference()
|
const reference = contentState.getPositionReference()
|
||||||
const { formats } = contentState.selectionFormats()
|
const { formats } = contentState.selectionFormats()
|
||||||
eventCenter.dispatch('muya-format-picker', { reference, formats })
|
eventCenter.dispatch('muya-format-picker', { reference, formats })
|
||||||
|
@ -56,7 +56,7 @@ Renderer.prototype.code = function (code, infostring, escaped, codeBlockStyle) {
|
|||||||
className +
|
className +
|
||||||
'">' +
|
'">' +
|
||||||
(escaped ? code : escape(code, true)) +
|
(escaped ? code : escape(code, true)) +
|
||||||
'\n</code></pre>\n'
|
'</code></pre>\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer.prototype.blockquote = function (quote) {
|
Renderer.prototype.blockquote = function (quote) {
|
||||||
|
@ -14,21 +14,40 @@ const MARKER_HASK = {
|
|||||||
"'": `%${getLongUniqueId()}%`
|
"'": `%${getLongUniqueId()}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHighlightHtml = (text, highlights, escape = false) => {
|
const getHighlightHtml = (text, highlights, escape = false, handleLineEnding = false) => {
|
||||||
let code = ''
|
let code = ''
|
||||||
let pos = 0
|
let pos = 0
|
||||||
|
const getEscapeHTML = (className, content) => {
|
||||||
|
return `${MARKER_HASK['<']}span class=${MARKER_HASK['"']}${className}${MARKER_HASK['"']}${MARKER_HASK['>']}${content}${MARKER_HASK['<']}/span${MARKER_HASK['>']}`
|
||||||
|
}
|
||||||
|
|
||||||
for (const highlight of highlights) {
|
for (const highlight of highlights) {
|
||||||
const { start, end, active } = highlight
|
const { start, end, active } = highlight
|
||||||
code += text.substring(pos, start)
|
code += text.substring(pos, start)
|
||||||
const className = active ? 'ag-highlight' : 'ag-selection'
|
const className = active ? 'ag-highlight' : 'ag-selection'
|
||||||
|
let highlightContent = text.substring(start, end)
|
||||||
|
if (handleLineEnding && text.endsWith('\n') && end === text.length) {
|
||||||
|
highlightContent = highlightContent.substring(start, end - 1) +
|
||||||
|
(escape
|
||||||
|
? getEscapeHTML('ag-line-end', '\n')
|
||||||
|
: '<span class="ag-line-end">\n</span>')
|
||||||
|
}
|
||||||
code += escape
|
code += escape
|
||||||
? `${MARKER_HASK['<']}span class=${MARKER_HASK['"']}${className}${MARKER_HASK['"']}${MARKER_HASK['>']}${text.substring(start, end)}${MARKER_HASK['<']}/span${MARKER_HASK['>']}`
|
? getEscapeHTML(className, highlightContent)
|
||||||
: `<span class="${className}">${text.substring(start, end)}</span>`
|
: `<span class="${className}">${highlightContent}</span>`
|
||||||
pos = end
|
pos = end
|
||||||
}
|
}
|
||||||
if (pos !== text.length) {
|
if (pos !== text.length) {
|
||||||
code += text.substring(pos)
|
if (handleLineEnding && text.endsWith('\n')) {
|
||||||
|
code += text.substring(pos, text.length - 1) +
|
||||||
|
(escape
|
||||||
|
? getEscapeHTML('ag-line-end', '\n')
|
||||||
|
: '<span class="ag-line-end">\n</span>')
|
||||||
|
} else {
|
||||||
|
code += text.substring(pos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +100,7 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
|||||||
tokens = this.tokenCache.get(text)
|
tokens = this.tokenCache.get(text)
|
||||||
} else if (
|
} else if (
|
||||||
HAS_TEXT_BLOCK_REG.test(type) &&
|
HAS_TEXT_BLOCK_REG.test(type) &&
|
||||||
functionType !== 'codeLine' &&
|
functionType !== 'codeContent' &&
|
||||||
functionType !== 'languageInput'
|
functionType !== 'languageInput'
|
||||||
) {
|
) {
|
||||||
const hasBeginRules = type === 'span'
|
const hasBeginRules = type === 'span'
|
||||||
@ -197,8 +216,8 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
|
|||||||
selector += `.${CLASS_OR_ID.AG_CHECKBOX_CHECKED}`
|
selector += `.${CLASS_OR_ID.AG_CHECKBOX_CHECKED}`
|
||||||
}
|
}
|
||||||
children = ''
|
children = ''
|
||||||
} else if (type === 'span' && functionType === 'codeLine') {
|
} else if (type === 'span' && functionType === 'codeContent') {
|
||||||
const code = escapeHtml(getHighlightHtml(text, highlights, true))
|
const code = escapeHtml(getHighlightHtml(text, highlights, true, true))
|
||||||
.replace(new RegExp(MARKER_HASK['<'], 'g'), '<')
|
.replace(new RegExp(MARKER_HASK['<'], 'g'), '<')
|
||||||
.replace(new RegExp(MARKER_HASK['>'], 'g'), '>')
|
.replace(new RegExp(MARKER_HASK['>'], 'g'), '>')
|
||||||
.replace(new RegExp(MARKER_HASK['"'], 'g'), '"')
|
.replace(new RegExp(MARKER_HASK['"'], 'g'), '"')
|
||||||
|
@ -419,6 +419,7 @@ class Selection {
|
|||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const childNodes = node.childNodes
|
const childNodes = node.childNodes
|
||||||
const len = childNodes.length
|
const len = childNodes.length
|
||||||
let i
|
let i
|
||||||
@ -429,6 +430,7 @@ class Selection {
|
|||||||
if (child.classList && child.classList.contains(CLASS_OR_ID.AG_FRONT_ICON)) {
|
if (child.classList && child.classList.contains(CLASS_OR_ID.AG_FRONT_ICON)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count + textLength >= offset) {
|
if (count + textLength >= offset) {
|
||||||
if (
|
if (
|
||||||
child.classList && child.classList.contains('ag-inline-image')
|
child.classList && child.classList.contains('ag-inline-image')
|
||||||
@ -536,8 +538,10 @@ class Selection {
|
|||||||
|
|
||||||
const anchorParagraph = findNearestParagraph(anchorNode)
|
const anchorParagraph = findNearestParagraph(anchorNode)
|
||||||
const focusParagraph = findNearestParagraph(focusNode)
|
const focusParagraph = findNearestParagraph(focusNode)
|
||||||
|
|
||||||
let aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset
|
let aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset
|
||||||
let fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset
|
let fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset
|
||||||
|
|
||||||
// fix input after image.
|
// fix input after image.
|
||||||
if (
|
if (
|
||||||
anchorNode === focusNode &&
|
anchorNode === focusNode &&
|
||||||
@ -576,6 +580,7 @@ class Selection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 })
|
||||||
|
|
||||||
|
@ -247,7 +247,8 @@ class ExportMarkdown {
|
|||||||
|
|
||||||
normalizeCodeBlock (block, indent) {
|
normalizeCodeBlock (block, indent) {
|
||||||
const result = []
|
const result = []
|
||||||
const textList = block.children[1].children.map(codeLine => codeLine.text)
|
const codeContent = block.children[1].children[0]
|
||||||
|
const textList = codeContent.text.split('\n')
|
||||||
const { functionType } = block
|
const { functionType } = block
|
||||||
if (functionType === 'fencecode') {
|
if (functionType === 'fencecode') {
|
||||||
result.push(`${indent}${block.lang ? '```' + block.lang + '\n' : '```\n'}`)
|
result.push(`${indent}${block.lang ? '```' + block.lang + '\n' : '```\n'}`)
|
||||||
@ -266,9 +267,10 @@ class ExportMarkdown {
|
|||||||
|
|
||||||
normalizeHTML (block, indent) { // figure
|
normalizeHTML (block, indent) { // figure
|
||||||
const result = []
|
const result = []
|
||||||
const codeLines = block.children[0].children[0].children
|
const codeContentText = block.children[0].children[0].children[0].text
|
||||||
for (const line of codeLines) {
|
const lines = codeContentText.split('\n')
|
||||||
result.push(`${indent}${line.text}\n`)
|
for (const line of lines) {
|
||||||
|
result.push(`${indent}${line}\n`)
|
||||||
}
|
}
|
||||||
return result.join('')
|
return result.join('')
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,6 @@ import { loadLanguage } from '../prism/index'
|
|||||||
// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
|
// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
|
||||||
import { CURSOR_ANCHOR_DNA, CURSOR_FOCUS_DNA } from '../config'
|
import { CURSOR_ANCHOR_DNA, CURSOR_FOCUS_DNA } from '../config'
|
||||||
|
|
||||||
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.
|
// Just because turndown change `\n`(soft line break) to space, So we add `span.ag-soft-line-break` to workaround.
|
||||||
const turnSoftBreakToSpan = html => {
|
const turnSoftBreakToSpan = html => {
|
||||||
const parser = new DOMParser()
|
const parser = new DOMParser()
|
||||||
@ -23,7 +21,7 @@ const turnSoftBreakToSpan = html => {
|
|||||||
const root = doc.querySelector('#turn-root')
|
const root = doc.querySelector('#turn-root')
|
||||||
const travel = childNodes => {
|
const travel = childNodes => {
|
||||||
for (const node of childNodes) {
|
for (const node of childNodes) {
|
||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3 && node.parentNode.tagName !== 'CODE') {
|
||||||
let startLen = 0
|
let startLen = 0
|
||||||
let endLen = 0
|
let endLen = 0
|
||||||
const text = node.nodeValue.replace(/^(\n+)/, (_, p) => {
|
const text = node.nodeValue.replace(/^(\n+)/, (_, p) => {
|
||||||
@ -89,27 +87,25 @@ const importRegister = ContentState => {
|
|||||||
case 'frontmatter': {
|
case 'frontmatter': {
|
||||||
const { lang, style } = token
|
const { lang, style } = token
|
||||||
value = token.text
|
value = token.text
|
||||||
|
.replace(/^\s+/, '')
|
||||||
|
.replace(/\s$/, '')
|
||||||
block = this.createBlock('pre', {
|
block = this.createBlock('pre', {
|
||||||
functionType: token.type,
|
functionType: token.type,
|
||||||
lang,
|
lang,
|
||||||
style
|
style
|
||||||
})
|
})
|
||||||
|
|
||||||
const codeBlock = this.createBlock('code', {
|
const codeBlock = this.createBlock('code', {
|
||||||
lang
|
lang
|
||||||
})
|
})
|
||||||
value
|
|
||||||
.replace(/^\s+/, '')
|
|
||||||
.replace(/\s$/, '')
|
|
||||||
.split(LINE_BREAKS_REG).forEach(line => {
|
|
||||||
const codeLine = this.createBlock('span', {
|
|
||||||
text: line,
|
|
||||||
lang,
|
|
||||||
functionType: 'codeLine'
|
|
||||||
})
|
|
||||||
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
const codeContent = this.createBlock('span', {
|
||||||
})
|
text: value,
|
||||||
|
lang,
|
||||||
|
functionType: 'codeContent'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
this.appendChild(block, codeBlock)
|
this.appendChild(block, codeBlock)
|
||||||
this.appendChild(parentList[0], block)
|
this.appendChild(parentList[0], block)
|
||||||
break
|
break
|
||||||
@ -175,13 +171,10 @@ const importRegister = ContentState => {
|
|||||||
const codeBlock = this.createBlock('code', {
|
const codeBlock = this.createBlock('code', {
|
||||||
lang
|
lang
|
||||||
})
|
})
|
||||||
value.split(LINE_BREAKS_REG).forEach(line => {
|
const codeContent = this.createBlock('span', {
|
||||||
const codeLine = this.createBlock('span', {
|
text: value,
|
||||||
text: line
|
lang,
|
||||||
})
|
functionType: 'codeContent'
|
||||||
codeLine.lang = lang
|
|
||||||
codeLine.functionType = 'codeLine'
|
|
||||||
this.appendChild(codeBlock, codeLine)
|
|
||||||
})
|
})
|
||||||
const inputBlock = this.createBlock('span', {
|
const inputBlock = this.createBlock('span', {
|
||||||
text: lang,
|
text: lang,
|
||||||
@ -205,6 +198,7 @@ const importRegister = ContentState => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.appendChild(codeBlock, codeContent)
|
||||||
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)
|
||||||
@ -364,6 +358,7 @@ const importRegister = ContentState => {
|
|||||||
html = html.replace(/<span> <\/span>/g, String.fromCharCode(160))
|
html = html.replace(/<span> <\/span>/g, String.fromCharCode(160))
|
||||||
|
|
||||||
html = turnSoftBreakToSpan(html)
|
html = turnSoftBreakToSpan(html)
|
||||||
|
|
||||||
const markdown = turndownService.turndown(html)
|
const markdown = turndownService.turndown(html)
|
||||||
return markdown
|
return markdown
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import TurndownService from 'turndown'
|
import TurndownService from 'turndown'
|
||||||
import { CLASS_OR_ID, LINE_BREAK } from '../config'
|
|
||||||
import { identity } from './index'
|
import { identity } from './index'
|
||||||
|
|
||||||
const turndownPluginGfm = require('joplin-turndown-plugin-gfm')
|
const turndownPluginGfm = require('joplin-turndown-plugin-gfm')
|
||||||
@ -27,18 +26,6 @@ export const usePluginAddRules = (turndownService, keeps) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add `LINE_BREAK` to the end of every code line but not the last line.
|
|
||||||
turndownService.addRule('codeLineBreak', {
|
|
||||||
filter (node, options) {
|
|
||||||
return (
|
|
||||||
node.nodeName === 'SPAN' && node.classList.contains(CLASS_OR_ID.AG_CODE_LINE) && node.nextElementSibling
|
|
||||||
)
|
|
||||||
},
|
|
||||||
replacement (content, node, options) {
|
|
||||||
return content + LINE_BREAK
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
turndownService.escape = identity
|
turndownService.escape = identity
|
||||||
turndownService.keep(keeps)
|
turndownService.keep(keeps)
|
||||||
}
|
}
|
||||||
|
26
yarn.lock
26
yarn.lock
@ -873,10 +873,10 @@
|
|||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
universal-user-agent "^4.0.0"
|
universal-user-agent "^4.0.0"
|
||||||
|
|
||||||
"@octokit/rest@^16.30.1":
|
"@octokit/rest@^16.31.0":
|
||||||
version "16.30.1"
|
version "16.31.0"
|
||||||
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.30.1.tgz#03e6dfb93e9a9cd2b3bacb95c49a8c7923f42ad0"
|
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.31.0.tgz#97ceba8e9b6bfdaffa321d7022917053fcb7dc39"
|
||||||
integrity sha512-1n2QzTbbaBXNLpx7WHlcsSMdJvxSdKmerXQm+bMYlKDbQM19uq446ZpGs7Ynq5SsdLj1usIfgJ9gJf4LtcWkDw==
|
integrity sha512-ZmMpc59N/Ju8FjN2qyOefB+vLppO/8uP/tPyvwEe7cQxdXvPx0OXL/OxDLib8tnT046AtgMNYaXH3FpZnrUdOA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@octokit/request" "^5.0.0"
|
"@octokit/request" "^5.0.0"
|
||||||
"@octokit/request-error" "^1.0.2"
|
"@octokit/request-error" "^1.0.2"
|
||||||
@ -10216,6 +10216,14 @@ schema-utils@^2.2.0:
|
|||||||
ajv "^6.10.2"
|
ajv "^6.10.2"
|
||||||
ajv-keywords "^3.4.1"
|
ajv-keywords "^3.4.1"
|
||||||
|
|
||||||
|
schema-utils@^2.4.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56"
|
||||||
|
integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==
|
||||||
|
dependencies:
|
||||||
|
ajv "^6.10.2"
|
||||||
|
ajv-keywords "^3.4.1"
|
||||||
|
|
||||||
scope-css@^1.2.1:
|
scope-css@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.npmjs.org/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e"
|
resolved "https://registry.npmjs.org/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e"
|
||||||
@ -11690,14 +11698,14 @@ urix@^0.1.0:
|
|||||||
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||||
|
|
||||||
url-loader@^2.1.0:
|
url-loader@^2.2.0:
|
||||||
version "2.1.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.npmjs.org/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
|
resolved "https://registry.npmjs.org/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e"
|
||||||
integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==
|
integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw==
|
||||||
dependencies:
|
dependencies:
|
||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
mime "^2.4.4"
|
mime "^2.4.4"
|
||||||
schema-utils "^2.0.0"
|
schema-utils "^2.4.1"
|
||||||
|
|
||||||
url-parse-lax@^3.0.0:
|
url-parse-lax@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user