
代码块功能实现,借助prismjs 进行高亮,通过 /\n(?!$)/g 进行行截断。
// 正则demo
const NEW_LINE_EXP = /\n(?!$)/g
const string = `123
123`
string.split(NEW_LINE_EXP)
// [123,123]
> `?!`:正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
然后通过一个往编辑去塞入一个空元素,往这个空元素中填充之前通过正则分割出的字符,再获取该空元素
的行高,push进行号的数组lineNumber。

就算是这种自动换行的形式,也能兼容。
import * as React from 'react'
import { debounce } from 'lodash'
import { reaction } from 'mobx'
import { inject } from 'injection'
const useLineNumber = (id, code, language, isLineNumber, isLineBreak, isLigatures) => {
const editorUI = inject('editorUI')
const [lineNumber, lineNumberSet] = React.useState([])
const isLineBreakRef = React.useRef(isLineBreak)
const isLineNumberRef = React.useRef(isLineNumber)
const codeLinesLength = React.useRef(0)
const computeLineNumber = () => {
const res = []
const element = document.querySelector(`#id-${id}`)
if (isLineNumber && element) {
const codeElement = element.querySelector('[contenteditable="true"]')
const lineNumberElement: any = element.querySelector('.lineNumber')
if (!codeElement) {
return
}
const NEW_LINE_EXP = /\n(?!$)/g
let lineNumberSizer: any = element.querySelector('.line-numbers-sizer')
const codeLines = codeElement.textContent.split(NEW_LINE_EXP)
let cacheMinWidth = ''
if (lineNumberElement) {
cacheMinWidth = lineNumberElement.style.minWidth
lineNumberElement.style.minWidth = `${String(codeLines.length).length * 10}px`
}
if (!lineNumberSizer) {
lineNumberSizer = document.createElement('span')
lineNumberSizer.className = 'line-numbers-sizer'
codeElement.appendChild(lineNumberSizer)
}
lineNumberSizer.style.display = 'block'
lineNumberSizer.style.visibility = 'visible'
codeLines.forEach(line => {
if (lineNumberSizer) {
lineNumberSizer.textContent = line || '\n'
const lineSize = lineNumberSizer.getBoundingClientRect().height
res.push(lineSize)
}
})
if (lineNumberElement) {
lineNumberElement.style.minWidth = cacheMinWidth
}
lineNumberSizer.textContent = ''
lineNumberSizer.style.display = 'none'
}
lineNumberSet(res)
}
const debounceComputeLineNumber = React.useCallback(debounce(computeLineNumber, 300), [isLineNumber])
React.useEffect(() => {
window.addEventListener('resize', debounceComputeLineNumber)
const reactionMainEditorWidth = reaction(
() => editorUI.mainEditorWidth,
() => {
debounceComputeLineNumber()
},
)
return () => {
reactionMainEditorWidth()
window.removeEventListener('resize', debounceComputeLineNumber)
}
}, [isLineNumber])
React.useLayoutEffect(() => {
const element = document.querySelector(`#id-${id}`)
if (element && isLineNumber) {
const NEW_LINE_EXP = /\n(?!$)/g
const codeElement = element.querySelector('[contenteditable="true"]')
if (codeElement) {
const codeLines = codeElement.textContent.split(NEW_LINE_EXP)
if (isLineBreakRef.current !== isLineBreak || isLineNumberRef.current !== isLineNumber) {
computeLineNumber()
} else {
if (codeLinesLength.current !== codeLines.length) {
computeLineNumber()
} else {
debounceComputeLineNumber()
}
}
codeLinesLength.current = codeLines.length
isLineBreakRef.current = isLineBreak
isLineNumberRef.current = isLineNumber
}
}
}, [code, language, isLineNumber, isLineBreak, isLigatures])
return [lineNumber]
}
export default useLineNumber