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:
Ran Luo 2019-10-08 14:12:51 +08:00 committed by GitHub
parent 4a24ff0954
commit 5b8da2cdf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 242 additions and 242 deletions

View File

@ -116,6 +116,7 @@ function greeting () {
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n lets-build'))
console.log()
} else {
console.log(chalk.yellow.bold('\n lets-build'))
}
}

View File

@ -6,7 +6,7 @@
- languageInput
- codeLine
- codeContent (used in code block)
- 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)
- lang - only when it's code line
- lang - only when it's functionType is `codeContent`
- All prismjs support language or empty string

View File

@ -34,7 +34,7 @@
},
"dependencies": {
"@hfelix/electron-localshortcut": "^3.1.1",
"@octokit/rest": "^16.30.1",
"@octokit/rest": "^16.31.0",
"arg": "^4.1.1",
"axios": "^0.19.0",
"ced": "^1.0.0",
@ -155,7 +155,7 @@
"svgo": "^1.3.0",
"svgo-loader": "^2.2.1",
"to-string-loader": "^1.1.5",
"url-loader": "^2.1.0",
"url-loader": "^2.2.0",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",

View File

@ -103,12 +103,12 @@ export const updateSelectionMenus = (applicationMenu, { start, end, affiliation
if (
(/th|td/.test(start.type) && /th|td/.test(end.type)) ||
(start.type === 'span' && start.block.functionType === 'codeLine') ||
(end.type === 'span' && end.block.functionType === 'codeLine')
(start.type === 'span' && start.block.functionType === 'codeContent') ||
(end.type === 'span' && end.block.functionType === 'codeContent')
) {
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)
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
}

View File

@ -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-thematic-break-line:empty::after,
.ag-code-line:empty::after,
.ag-code-content:empty::after,
.ag-paragraph-content:empty::after {
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-thematic-break-line,
.ag-paragraph-content,
.ag-code-line {
.ag-code-content {
display: block;
white-space: pre-wrap;
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 {
content: '↩';
opacity: .5;
font-family: monospace;
}
.ag-line-end {
@ -402,7 +403,6 @@ li.ag-task-list-item > input[type=checkbox]::before {
content: '';
width: 18px;
height: 18px;
box-sizing: border-box;
display: inline-block;
border: 2px solid var(--editorColor50);
border-radius: 50%;
@ -477,7 +477,7 @@ pre.ag-front-matter {
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...';
color: var(--editorColor10);
}
@ -487,7 +487,7 @@ pre[data-role$='code'] span.ag-language-input:empty::after {
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...';
color: var(--editorColor10);
}
@ -673,7 +673,7 @@ span.ag-emoji-marked-text {
.ag-emoji-marked-text::before {
position: absolute;
content: attr(data-emoji);
color: var(--editorColor);
color: #000000;
top: 0;
left: -1.3em;
font-size: 1em;

View File

@ -70,9 +70,6 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'AG_BULLET_LIST',
'AG_BULLET_LIST_ITEM',
'AG_CHECKBOX_CHECKED',
'AG_CODE_LINE',
'AG_CODE_LINE_ADD',
'AG_CODE_LINE_MINUS',
'AG_CONTAINER_BLOCK',
'AG_CONTAINER_PREVIEW',
'AG_CONTAINER_ICON',

View File

@ -308,7 +308,7 @@ const backspaceCtrl = ContentState => {
if (
block.type === 'span' &&
block.functionType === 'codeLine' &&
block.functionType === 'codeContent' &&
left === 0 &&
!block.preSibling
) {

View File

@ -136,7 +136,7 @@ const clickCtrl = ContentState => {
start.key === end.key &&
start.offset !== end.offset &&
HAS_TEXT_BLOCK_REG.test(block.type) &&
block.functionType !== 'codeLine' &&
block.functionType !== 'codeContent' &&
block.functionType !== 'languageInput'
) {
const reference = this.getPositionReference()

View File

@ -79,32 +79,41 @@ const codeBlockCtrl = ContentState => {
if (block.type === 'span') {
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 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
const { text } = block.children[0]
const match = CODE_UPDATE_REP.exec(text)
if (match || lang) {
const codeBlock = this.createBlock('code')
const firstLine = this.createBlock('span', { text: code })
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)
inputBlock.functionType = 'languageInput'
block.type = 'pre'
block.functionType = 'fencecode'
block.lang = language
block.text = ''
block.history = null
block.children = []
codeBlock.lang = language
firstLine.lang = language
firstLine.functionType = 'codeLine'
this.appendChild(codeBlock, firstLine)
this.appendChild(codeBlock, codeContent)
this.appendChild(block, inputBlock)
this.appendChild(block, codeBlock)
const { key } = firstLine
const { key } = codeContent
const offset = code.length
this.cursor = {
start: { key, offset },

View File

@ -1,4 +1,3 @@
const LINE_BREAKS_REG = /\n/
const FUNCTION_TYPE_LANG = {
multiplemath: 'latex',
flowchart: 'yaml',
@ -33,22 +32,20 @@ const containerCtrl = ContentState => {
this.appendChild(preBlock, codeBlock)
if (typeof value === 'string' && value) {
value.replace(/^\s+/, '').split(LINE_BREAKS_REG).forEach(line => {
const codeLine = this.createBlock('span', {
text: line,
functionType: 'codeLine',
lang
})
this.appendChild(codeBlock, codeLine)
value = value.replace(/^\s+/, '')
const codeContent = this.createBlock('span', {
text: value,
lang,
functionType: 'codeContent'
})
this.appendChild(codeBlock, codeContent)
} else {
const emptyLine = this.createBlock('span', {
functionType: 'codeLine',
const emptyCodeContent = this.createBlock('span', {
functionType: 'codeContent',
lang
})
this.appendChild(codeBlock, emptyLine)
this.appendChild(codeBlock, emptyCodeContent)
}
const preview = this.createBlock('div', {

View File

@ -1,5 +1,6 @@
import selection from '../selection'
import { CLASS_OR_ID } from '../config'
import { escapeHtml } from '../utils'
import { getSanitizeHtml } from '../utils/exportHtml'
import ExportMarkdown from '../utils/exportMarkdown'
import marked from '../parser/marked'
@ -34,6 +35,19 @@ const copyCutCtrl = ContentState => {
}
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 wrapper = document.createElement('div')
wrapper.innerHTML = html
@ -109,8 +123,8 @@ const copyCutCtrl = ContentState => {
const id = cf.id
const block = this.getBlock(id)
const language = block.lang || ''
const selectedCodeLines = cf.querySelectorAll('.ag-code-line')
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
const codeContent = cf.querySelector('.ag-code-content')
const value = escapeHtml(codeContent.textContent)
cf.innerHTML = `<code class="language-${language}">${value}</code>`
}
@ -125,10 +139,9 @@ const copyCutCtrl = ContentState => {
const htmlBlock = wrapper.querySelectorAll('figure[data-role=\'HTML\']')
for (const hb of htmlBlock) {
const selectedCodeLines = hb.querySelectorAll('span.ag-code-line')
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
const codeContent = hb.querySelector('.ag-code-content')
const pre = document.createElement('pre')
pre.textContent = value
pre.textContent = codeContent.textContent
hb.replaceWith(pre)
}
@ -142,8 +155,8 @@ const copyCutCtrl = ContentState => {
for (const mb of mathBlock) {
const preElement = mb.querySelector('pre[data-role]')
const functionType = preElement.getAttribute('data-role')
const selectedCodeLines = mb.querySelectorAll('span.ag-code-line')
const value = Array.from(selectedCodeLines).map(codeLine => codeLine.textContent).join('\n')
const codeContent = mb.querySelector('.ag-code-content')
const value = codeContent.textContent
let pre
switch (functionType) {
case 'multiplemath':
@ -165,7 +178,6 @@ const copyCutCtrl = ContentState => {
let htmlData = wrapper.innerHTML
const textData = this.htmlToMarkdown(htmlData)
htmlData = marked(textData)
return { html: htmlData, text: textData }
}

View File

@ -33,10 +33,11 @@ const deleteCtrl = ContentState => {
) {
event.preventDefault()
if (nextBlock && /h\d|span/.test(nextBlock.type)) {
if (nextBlock.functionType === 'codeLine' && nextBlock.nextSibling) {
// if code block more than one line, do nothing!
// if cursor at the end of code block-language input, do nothing!
if (nextBlock.functionType === 'codeContent' && startBlock.functionType === 'languageInput') {
return
}
startBlock.text += nextBlock.text
const toBeRemoved = [nextBlock]

View File

@ -237,31 +237,21 @@ const enterCtrl = ContentState => {
}
return this.partialRender()
} else if (
block.type === 'span' && block.functionType === 'codeLine'
block.type === 'span' && block.functionType === 'codeContent'
) {
const { text } = block
const newLineText = text.substring(start.offset)
const { text, key } = block
const autoIndent = checkAutoIndent(text, start.offset)
const indent = getIndentSpace(text)
block.text = text.substring(0, start.offset)
const newLine = this.createBlock('span', {
text: `${indent}${newLineText}`,
functionType: block.functionType,
lang: block.lang
})
block.text = text.substring(0, start.offset) +
'\n' +
(autoIndent ? indent + ' '.repeat(this.tabSize) + '\n' : '') +
indent +
text.substring(start.offset)
let offset = start.offset + 1 + indent.length
this.insertAfter(newLine, block)
let { key } = newLine
let offset = indent.length
if (autoIndent) {
const emptyLine = this.createBlock('span', {
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
offset += this.tabSize
}
this.cursor = {

View File

@ -16,7 +16,7 @@ const imageCtrl = ContentState => {
if (
block.type === 'span' &&
(
block.functionType === 'codeLine' ||
block.functionType === 'codeContent' ||
block.functionType === 'languageInput' ||
block.functionType === 'thematicBreakLine'
)

View File

@ -28,6 +28,7 @@ import linkCtrl from './linkCtrl'
import dragDropCtrl from './dragDropCtrl'
import importMarkdown from '../utils/importMarkdown'
import Cursor from '../selection/cursor'
import escapeCharactersMap, { escapeCharacters } from '../parser/escapeCharacter'
const prototypes = [
tabCtrl,
@ -253,6 +254,13 @@ class ContentState {
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)
return blockData
}

View File

@ -77,7 +77,7 @@ const inputCtrl = ContentState => {
return false
}
ContentState.prototype.inputHandler = function (event) {
ContentState.prototype.inputHandler = function (event, notEqual = false) {
const { start, end } = selection.getCursorRange()
if (!start || !end) {
return
@ -86,6 +86,27 @@ const inputCtrl = ContentState => {
const key = start.key
const block = this.getBlock(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 needRender = false
@ -123,7 +144,7 @@ const inputCtrl = ContentState => {
}
// auto pair (not need to auto pair in math block)
if (block && block.text !== text) {
if (block && (block.text !== text || notEqual)) {
if (
start.key === end.key &&
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)) ||
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
(block.functionType !== 'codeLine' && !isInInlineMath && !isInInlineCode && autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)))
(block.functionType !== 'codeContent' && !isInInlineMath && !isInInlineCode && autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)))
) {
needRender = true
text = BRACKET_HASH[event.data]
@ -250,8 +271,7 @@ const inputCtrl = ContentState => {
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, !!checkQuickInsert)
// Update preview content of math block
if (block && block.type === 'span' && block.functionType === 'codeLine') {
if (block && block.type === 'span' && block.functionType === 'codeContent') {
needRender = true
}

View File

@ -2,8 +2,6 @@ import selection from '../selection'
import { PARAGRAPH_TYPES, DEFAULT_TURNDOWN_CONFIG } from '../config'
import ExportMarkdown from '../utils/exportMarkdown'
const LINE_BREAKS_REG = /\n/
// get header level
// eg: h1 => 1
// h2 => 2
@ -99,15 +97,15 @@ const paragraphCtrl = ContentState => {
const codeBlock = this.createBlock('code', {
lang
})
const emptyLine = this.createBlock('span', {
functionType: 'codeLine',
const emptyCodeContent = this.createBlock('span', {
functionType: 'codeContent',
lang
})
this.appendChild(codeBlock, emptyLine)
this.appendChild(codeBlock, emptyCodeContent)
this.appendChild(frontMatter, codeBlock)
this.insertBefore(frontMatter, firstBlock)
const { key } = emptyLine
const { key } = emptyCodeContent
const offset = 0
this.cursor = {
start: { key, offset },
@ -248,39 +246,21 @@ const paragraphCtrl = ContentState => {
// change fenced code block to p paragraph
if (affiliation.length && affiliation[0].type === 'pre' && /code/.test(affiliation[0].functionType)) {
const preBlock = affiliation[0]
const codeLines = preBlock.children[1].children
const codeContent = preBlock.children[1].children[0]
preBlock.type = 'p'
preBlock.children = []
const newParagraphBlock = this.createBlockP(codeLines.map(l => l.text).join('\n'))
const newParagraphBlock = this.createBlockP(codeContent.text)
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) {
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 = {
start: { key, offset: startOffset },
end: { key, offset: endOffset }
start: { key, offset: start.offset },
end: { key, offset: end.offset }
}
} else {
if (start.key === end.key) {
@ -293,24 +273,20 @@ const paragraphCtrl = ContentState => {
})
const codeBlock = this.createBlock('code', {
lang: ''
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)
}
const codeContent = this.createBlock('span', {
text: startBlock.text,
lang,
functionType: 'codeContent'
})
this.appendChild(codeBlock, codeContent)
this.appendChild(preBlock, inputBlock)
this.appendChild(preBlock, codeBlock)
this.insertBefore(preBlock, anchorBlock)
@ -345,20 +321,15 @@ const paragraphCtrl = ContentState => {
const listIndentation = this.listIndentation
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate()
markdown.split(LINE_BREAKS_REG).forEach(text => {
const codeLine = this.createBlock('span', {
text,
lang,
functionType: 'codeLine'
})
this.appendChild(codeBlock, codeLine)
const codeContent = this.createBlock('span', {
text: markdown,
lang,
functionType: 'codeContent'
})
const inputBlock = this.createBlock('span', {
functionType: 'languageInput'
})
this.appendChild(codeBlock, codeContent)
this.appendChild(preBlock, inputBlock)
this.appendChild(preBlock, codeBlock)
this.insertAfter(preBlock, referBlock)
@ -774,20 +745,19 @@ const paragraphCtrl = ContentState => {
}
// 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...
if (startBlock.type === 'span' && startBlock.functionType === 'codeLine') {
const codeBlock = this.getParent(startBlock)
const firstCodeLine = this.firstInDescendant(codeBlock)
const lastCodeLine = this.lastInDescendant(codeBlock)
if (startBlock.type === 'span' && startBlock.functionType === 'codeContent') {
const { key } = startBlock
this.cursor = {
start: {
key: firstCodeLine.key,
key,
offset: 0
},
end: {
key: lastCodeLine.key,
offset: lastCodeLine.text.length
key,
offset: startBlock.text.length
}
}
return this.partialRender()
}
// Handler language input, only select language info only...

View File

@ -314,42 +314,18 @@ const pasteCtrl = ContentState => {
return
}
if (startBlock.type === 'span' && startBlock.functionType === 'codeLine') {
let referenceBlock = startBlock
if (startBlock.type === 'span' && startBlock.functionType === 'codeContent') {
const blockText = startBlock.text
const prePartText = blockText.substring(0, start.offset)
const postPartText = blockText.substring(end.offset)
const textList = text.split(LINE_BREAKS_REG)
if (textList.length > 1) {
textList.forEach((line, i) => {
if (i === 0) {
startBlock.text = prePartText + line
} else {
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 }
}
startBlock.text = prePartText + text + postPartText
const { key } = startBlock
const offset = start.offset + text.length
this.cursor = {
start: { key, offset },
end: { key, offset }
}
return this.partialRender()
}

View File

@ -292,7 +292,7 @@ const tabCtrl = ContentState => {
start.key === end.key &&
start.offset === end.offset &&
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
) {
const { text, key } = startBlock
@ -312,7 +312,7 @@ const tabCtrl = ContentState => {
start.key === end.key &&
start.offset === end.offset &&
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 lastWordBeforeCursor = text.substring(0, start.offset).split(/\s+/).pop()

View File

@ -49,7 +49,7 @@ const tableBlockCtrl = ContentState => {
const { type, functionType } = block
switch (type) {
case 'span':
if (functionType === 'codeLine') {
if (functionType === 'codeContent') {
return this.closest(block, 'figure') || this.closest(block, 'pre')
} else {
return this.getParent(block)

View File

@ -68,7 +68,7 @@ const updateCtrl = ContentState => {
ContentState.prototype.checkInlineUpdate = function (block) {
// table cell can not have blocks in it
if (/th|td|figure/.test(block.type)) return false
if (/codeLine|languageInput/.test(block.functionType)) return false
if (/codeContent|languageInput/.test(block.functionType)) return false
let line = null
const { text } = block
@ -84,7 +84,7 @@ const updateCtrl = ContentState => {
switch (true) {
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:
return this.updateList(block, 'bullet', bullet, line)
@ -115,7 +115,7 @@ const updateCtrl = ContentState => {
}
// 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 (block.type === 'hr') return null
const text = line.text
@ -124,9 +124,10 @@ const updateCtrl = ContentState => {
let thematicLine = ''
const postParagraphLines = []
let thematicLineHasPushed = false
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
thematicLineHasPushed = true
} else if (!thematicLineHasPushed) {
@ -155,9 +156,12 @@ const updateCtrl = ContentState => {
this.removeBlock(block)
const { start, end } = this.cursor
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 = {
start: { key, offset: start.offset },
end: { key, offset: end.offset }
start: { key, offset: startOffset },
end: { key, offset: endOffset }
}
return thematicBlock
}
@ -519,15 +523,16 @@ const updateCtrl = ContentState => {
}
ContentState.prototype.updateIndentCode = function (block, line) {
const lang = ''
const codeBlock = this.createBlock('code', {
lang: ''
lang
})
const inputBlock = this.createBlock('span', {
functionType: 'languageInput'
})
const preBlock = this.createBlock('pre', {
functionType: 'indentcode',
lang: ''
lang
})
const text = line ? line.text : block.text
@ -545,15 +550,13 @@ const updateCtrl = ContentState => {
paragraphLines.push(l)
}
}
codeLines.forEach(text => {
const codeLine = this.createBlock('span', {
text,
functionType: 'codeLine',
lang: ''
})
this.appendChild(codeBlock, codeLine)
const codeContent = this.createBlock('span', {
text: codeLines.join('\n'),
functionType: 'codeContent',
lang
})
this.appendChild(codeBlock, codeContent)
this.appendChild(preBlock, inputBlock)
this.appendChild(preBlock, codeBlock)
this.insertBefore(preBlock, block)

View File

@ -275,7 +275,7 @@ class Keyboard {
}
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 { formats } = contentState.selectionFormats()
eventCenter.dispatch('muya-format-picker', { reference, formats })

View File

@ -56,7 +56,7 @@ Renderer.prototype.code = function (code, infostring, escaped, codeBlockStyle) {
className +
'">' +
(escaped ? code : escape(code, true)) +
'\n</code></pre>\n'
'</code></pre>\n'
}
Renderer.prototype.blockquote = function (quote) {

View File

@ -14,21 +14,40 @@ const MARKER_HASK = {
"'": `%${getLongUniqueId()}%`
}
const getHighlightHtml = (text, highlights, escape = false) => {
const getHighlightHtml = (text, highlights, escape = false, handleLineEnding = false) => {
let code = ''
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) {
const { start, end, active } = highlight
code += text.substring(pos, start)
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
? `${MARKER_HASK['<']}span class=${MARKER_HASK['"']}${className}${MARKER_HASK['"']}${MARKER_HASK['>']}${text.substring(start, end)}${MARKER_HASK['<']}/span${MARKER_HASK['>']}`
: `<span class="${className}">${text.substring(start, end)}</span>`
? getEscapeHTML(className, highlightContent)
: `<span class="${className}">${highlightContent}</span>`
pos = end
}
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
}
@ -81,7 +100,7 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
tokens = this.tokenCache.get(text)
} else if (
HAS_TEXT_BLOCK_REG.test(type) &&
functionType !== 'codeLine' &&
functionType !== 'codeContent' &&
functionType !== 'languageInput'
) {
const hasBeginRules = type === 'span'
@ -197,8 +216,8 @@ export default function renderLeafBlock (block, activeBlocks, matches, useCache
selector += `.${CLASS_OR_ID.AG_CHECKBOX_CHECKED}`
}
children = ''
} else if (type === 'span' && functionType === 'codeLine') {
const code = escapeHtml(getHighlightHtml(text, highlights, true))
} else if (type === 'span' && functionType === 'codeContent') {
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'), '"')

View File

@ -419,6 +419,7 @@ class Selection {
offset
}
}
const childNodes = node.childNodes
const len = childNodes.length
let i
@ -429,6 +430,7 @@ class Selection {
if (child.classList && child.classList.contains(CLASS_OR_ID.AG_FRONT_ICON)) {
continue
}
if (count + textLength >= offset) {
if (
child.classList && child.classList.contains('ag-inline-image')
@ -536,8 +538,10 @@ class Selection {
const anchorParagraph = findNearestParagraph(anchorNode)
const focusParagraph = findNearestParagraph(focusNode)
let aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset
let fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset
// fix input after image.
if (
anchorNode === focusNode &&
@ -576,6 +580,7 @@ class Selection {
}
const anchor = { key: anchorParagraph.id, offset: aOffset }
const focus = { key: focusParagraph.id, offset: fOffset }
const result = new Cursor({ anchor, focus })

View File

@ -247,7 +247,8 @@ class ExportMarkdown {
normalizeCodeBlock (block, indent) {
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
if (functionType === 'fencecode') {
result.push(`${indent}${block.lang ? '```' + block.lang + '\n' : '```\n'}`)
@ -266,9 +267,10 @@ class ExportMarkdown {
normalizeHTML (block, indent) { // figure
const result = []
const codeLines = block.children[0].children[0].children
for (const line of codeLines) {
result.push(`${indent}${line.text}\n`)
const codeContentText = block.children[0].children[0].children[0].text
const lines = codeContentText.split('\n')
for (const line of lines) {
result.push(`${indent}${line}\n`)
}
return result.join('')
}

View File

@ -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
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.
const turnSoftBreakToSpan = html => {
const parser = new DOMParser()
@ -23,7 +21,7 @@ const turnSoftBreakToSpan = html => {
const root = doc.querySelector('#turn-root')
const travel = childNodes => {
for (const node of childNodes) {
if (node.nodeType === 3) {
if (node.nodeType === 3 && node.parentNode.tagName !== 'CODE') {
let startLen = 0
let endLen = 0
const text = node.nodeValue.replace(/^(\n+)/, (_, p) => {
@ -89,27 +87,25 @@ const importRegister = ContentState => {
case 'frontmatter': {
const { lang, style } = token
value = token.text
.replace(/^\s+/, '')
.replace(/\s$/, '')
block = this.createBlock('pre', {
functionType: token.type,
lang,
style
})
const codeBlock = this.createBlock('code', {
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(parentList[0], block)
break
@ -175,13 +171,10 @@ const importRegister = ContentState => {
const codeBlock = this.createBlock('code', {
lang
})
value.split(LINE_BREAKS_REG).forEach(line => {
const codeLine = this.createBlock('span', {
text: line
})
codeLine.lang = lang
codeLine.functionType = 'codeLine'
this.appendChild(codeBlock, codeLine)
const codeContent = this.createBlock('span', {
text: value,
lang,
functionType: 'codeContent'
})
const inputBlock = this.createBlock('span', {
text: lang,
@ -205,6 +198,7 @@ const importRegister = ContentState => {
})
}
this.appendChild(codeBlock, codeContent)
this.appendChild(block, inputBlock)
this.appendChild(block, codeBlock)
this.appendChild(parentList[0], block)
@ -364,6 +358,7 @@ const importRegister = ContentState => {
html = html.replace(/<span>&nbsp;<\/span>/g, String.fromCharCode(160))
html = turnSoftBreakToSpan(html)
const markdown = turndownService.turndown(html)
return markdown
}

View File

@ -1,5 +1,4 @@
import TurndownService from 'turndown'
import { CLASS_OR_ID, LINE_BREAK } from '../config'
import { identity } from './index'
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.keep(keeps)
}

View File

@ -873,10 +873,10 @@
once "^1.4.0"
universal-user-agent "^4.0.0"
"@octokit/rest@^16.30.1":
version "16.30.1"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.30.1.tgz#03e6dfb93e9a9cd2b3bacb95c49a8c7923f42ad0"
integrity sha512-1n2QzTbbaBXNLpx7WHlcsSMdJvxSdKmerXQm+bMYlKDbQM19uq446ZpGs7Ynq5SsdLj1usIfgJ9gJf4LtcWkDw==
"@octokit/rest@^16.31.0":
version "16.31.0"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.31.0.tgz#97ceba8e9b6bfdaffa321d7022917053fcb7dc39"
integrity sha512-ZmMpc59N/Ju8FjN2qyOefB+vLppO/8uP/tPyvwEe7cQxdXvPx0OXL/OxDLib8tnT046AtgMNYaXH3FpZnrUdOA==
dependencies:
"@octokit/request" "^5.0.0"
"@octokit/request-error" "^1.0.2"
@ -10216,6 +10216,14 @@ schema-utils@^2.2.0:
ajv "^6.10.2"
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:
version "1.2.1"
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"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
url-loader@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==
url-loader@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e"
integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw==
dependencies:
loader-utils "^1.2.3"
mime "^2.4.4"
schema-utils "^2.0.0"
schema-utils "^2.4.1"
url-parse-lax@^3.0.0:
version "3.0.0"