mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 23:12:45 +08:00

* 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
295 lines
9.5 KiB
JavaScript
295 lines
9.5 KiB
JavaScript
import selection from '../selection'
|
|
import { getTextContent } from '../selection/dom'
|
|
import { beginRules } from '../parser/rules'
|
|
import { tokenizer } from '../parser/'
|
|
import { CLASS_OR_ID } from '../config'
|
|
|
|
const BRACKET_HASH = {
|
|
'{': '}',
|
|
'[': ']',
|
|
'(': ')',
|
|
'*': '*',
|
|
_: '_',
|
|
'"': '"',
|
|
'\'': '\'',
|
|
$: '$',
|
|
'~': '~'
|
|
}
|
|
|
|
const BACK_HASH = {
|
|
'}': '{',
|
|
']': '[',
|
|
')': '(',
|
|
'*': '*',
|
|
_: '_',
|
|
'"': '"',
|
|
'\'': '\'',
|
|
$: '$',
|
|
'~': '~'
|
|
}
|
|
|
|
const inputCtrl = ContentState => {
|
|
// Input @ to quick insert paragraph
|
|
ContentState.prototype.checkQuickInsert = function (block) {
|
|
const { type, text, functionType } = block
|
|
if (type !== 'span' || functionType !== 'paragraphContent') return false
|
|
return /^@\S*$/.test(text)
|
|
}
|
|
|
|
ContentState.prototype.checkCursorInTokenType = function (text, offset, type) {
|
|
const tokens = tokenizer(text, [], false)
|
|
return tokens.filter(t => t.type === type).some(t => offset >= t.range.start && offset <= t.range.end)
|
|
}
|
|
|
|
ContentState.prototype.checkNotSameToken = function (oldText, text) {
|
|
const oldTokens = tokenizer(oldText)
|
|
const tokens = tokenizer(text)
|
|
|
|
const oldCache = {}
|
|
const cache = {}
|
|
|
|
for (const { type } of oldTokens) {
|
|
if (oldCache[type]) {
|
|
oldCache[type]++
|
|
} else {
|
|
oldCache[type] = 1
|
|
}
|
|
}
|
|
|
|
for (const { type } of tokens) {
|
|
if (cache[type]) {
|
|
cache[type]++
|
|
} else {
|
|
cache[type] = 1
|
|
}
|
|
}
|
|
|
|
if (Object.keys(oldCache).length !== Object.keys(cache).length) {
|
|
return true
|
|
}
|
|
|
|
for (const key of Object.keys(oldCache)) {
|
|
if (!cache[key] || oldCache[key] !== cache[key]) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
ContentState.prototype.inputHandler = function (event, notEqual = false) {
|
|
const { start, end } = selection.getCursorRange()
|
|
if (!start || !end) {
|
|
return
|
|
}
|
|
const { start: oldStart, end: oldEnd } = this.cursor
|
|
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
|
|
let needRenderAll = false
|
|
if (oldStart.key !== oldEnd.key) {
|
|
const startBlock = this.getBlock(oldStart.key)
|
|
const startOutmostBlock = this.findOutMostBlock(startBlock)
|
|
const endBlock = this.getBlock(oldEnd.key)
|
|
const endOutmostBlock = this.findOutMostBlock(endBlock)
|
|
if (
|
|
// fix #918.
|
|
startBlock.functionType === 'languageInput'
|
|
) {
|
|
if (startOutmostBlock === endOutmostBlock && !endBlock.nextSibling) {
|
|
this.removeBlocks(startBlock, endBlock, false)
|
|
endBlock.text = ''
|
|
} else if (startOutmostBlock !== endOutmostBlock) {
|
|
const preBlock = this.getParent(startBlock)
|
|
const pBlock = this.createBlock('p')
|
|
this.removeBlocks(startBlock, endBlock)
|
|
startBlock.functionType = 'paragraphContent'
|
|
this.appendChild(pBlock, startBlock)
|
|
this.insertBefore(pBlock, preBlock)
|
|
this.removeBlock(preBlock)
|
|
} else {
|
|
this.removeBlocks(startBlock, endBlock)
|
|
}
|
|
} else {
|
|
this.removeBlocks(startBlock, endBlock)
|
|
}
|
|
if (this.blocks.length === 1) {
|
|
needRenderAll = true
|
|
}
|
|
needRender = true
|
|
}
|
|
|
|
// auto pair (not need to auto pair in math block)
|
|
if (block && (block.text !== text || notEqual)) {
|
|
if (
|
|
start.key === end.key &&
|
|
start.offset === end.offset &&
|
|
event.type === 'input'
|
|
) {
|
|
const { offset } = start
|
|
const { autoPairBracket, autoPairMarkdownSyntax, autoPairQuote } = this.muya.options
|
|
const inputChar = text.charAt(+offset - 1)
|
|
const preInputChar = text.charAt(+offset - 2)
|
|
const prePreInputChar = text.charAt(+offset - 3)
|
|
const postInputChar = text.charAt(+offset)
|
|
|
|
if (/^delete/.test(event.inputType)) {
|
|
// handle `deleteContentBackward` or `deleteContentForward`
|
|
const deletedChar = block.text[offset]
|
|
if (event.inputType === 'deleteContentBackward' && postInputChar === BRACKET_HASH[deletedChar]) {
|
|
needRender = true
|
|
text = text.substring(0, offset) + text.substring(offset + 1)
|
|
}
|
|
if (event.inputType === 'deleteContentForward' && inputChar === BACK_HASH[deletedChar]) {
|
|
needRender = true
|
|
start.offset -= 1
|
|
end.offset -= 1
|
|
text = text.substring(0, offset - 1) + text.substring(offset)
|
|
}
|
|
/* eslint-disable no-useless-escape */
|
|
} else if (
|
|
(event.inputType.indexOf('delete') === -1) &&
|
|
(inputChar === postInputChar) &&
|
|
(
|
|
(autoPairQuote && /[']{1}/.test(inputChar)) ||
|
|
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
|
(autoPairBracket && /[\}\]\)]{1}/.test(inputChar)) ||
|
|
(autoPairMarkdownSyntax && /[$]{1}/.test(inputChar)) ||
|
|
(autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)) && /[_*~]{1}/.test(prePreInputChar)
|
|
)
|
|
) {
|
|
needRender = true
|
|
text = text.substring(0, offset) + text.substring(offset + 1)
|
|
} else {
|
|
/* eslint-disable no-useless-escape */
|
|
// Not Unicode aware, since things like \p{Alphabetic} or \p{L} are not supported yet
|
|
const isInInlineMath = this.checkCursorInTokenType(text, offset, 'inline_math')
|
|
const isInInlineCode = this.checkCursorInTokenType(text, offset, 'inline_code')
|
|
if (
|
|
!/\\/.test(preInputChar) &&
|
|
((autoPairQuote && /[']{1}/.test(inputChar) && !(/[a-zA-Z\d]{1}/.test(preInputChar))) ||
|
|
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
|
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
|
|
(block.functionType !== 'codeContent' && !isInInlineMath && !isInInlineCode && autoPairMarkdownSyntax && /[*$`~_]{1}/.test(inputChar)))
|
|
) {
|
|
needRender = true
|
|
text = BRACKET_HASH[event.data]
|
|
? text.substring(0, offset) + BRACKET_HASH[inputChar] + text.substring(offset)
|
|
: text
|
|
}
|
|
/* eslint-enable no-useless-escape */
|
|
// Delete the last `*` of `**` when you insert one space between `**` to create a bullet list.
|
|
if (
|
|
/\s/.test(event.data) &&
|
|
/^\* /.test(text) &&
|
|
preInputChar === '*' &&
|
|
postInputChar === '*'
|
|
) {
|
|
text = text.substring(0, offset) + text.substring(offset + 1)
|
|
needRender = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.checkNotSameToken(block.text, text)) {
|
|
needRender = true
|
|
}
|
|
// Just work for `Shift + Enter` to create a soft and hard line break.
|
|
if (
|
|
block.text.endsWith('\n') &&
|
|
start.offset === text.length &&
|
|
(event.inputType === 'insertText' || event.type === 'compositionend')
|
|
) {
|
|
block.text += event.data
|
|
start.offset++
|
|
end.offset++
|
|
} else if (
|
|
block.text.length === oldStart.offset &&
|
|
block.text[oldStart.offset - 2] === '\n' &&
|
|
event.inputType === 'deleteContentBackward'
|
|
) {
|
|
block.text = block.text.substring(0, oldStart.offset - 1)
|
|
start.offset = block.text.length
|
|
end.offset = block.text.length
|
|
} else {
|
|
block.text = text
|
|
}
|
|
|
|
// Update code block language when modify code block identifer
|
|
if (block.functionType === 'languageInput') {
|
|
const parent = this.getParent(block)
|
|
parent.lang = block.text
|
|
}
|
|
|
|
if (beginRules.reference_definition.test(text)) {
|
|
needRenderAll = true
|
|
}
|
|
}
|
|
|
|
// show quick insert
|
|
const rect = paragraph.getBoundingClientRect()
|
|
const checkQuickInsert = this.checkQuickInsert(block)
|
|
const reference = this.getPositionReference()
|
|
reference.getBoundingClientRect = function () {
|
|
const { x, y, left, top, height, bottom } = rect
|
|
|
|
return Object.assign({}, {
|
|
left,
|
|
x,
|
|
top,
|
|
y,
|
|
bottom,
|
|
height,
|
|
width: 0,
|
|
right: left
|
|
})
|
|
}
|
|
|
|
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, !!checkQuickInsert)
|
|
|
|
if (block && block.type === 'span' && block.functionType === 'codeContent') {
|
|
needRender = true
|
|
}
|
|
|
|
this.cursor = { start, end }
|
|
const checkMarkedUpdate = this.checkNeedRender()
|
|
const inlineUpdatedBlock = this.isCollapse() && this.checkInlineUpdate(block)
|
|
// just for fix #707,need render All if in combines pre list and next list into one list.
|
|
if (inlineUpdatedBlock) {
|
|
const liBlock = this.getParent(inlineUpdatedBlock)
|
|
if (liBlock && liBlock.type === 'li' && liBlock.preSibling && liBlock.nextSibling) {
|
|
needRenderAll = true
|
|
}
|
|
}
|
|
if (checkMarkedUpdate || inlineUpdatedBlock || needRender) {
|
|
return needRenderAll ? this.render() : this.partialRender()
|
|
}
|
|
}
|
|
}
|
|
|
|
export default inputCtrl
|