|
@@ -70,32 +70,26 @@ const calculateStartingLine = (meta) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Split line to div node with className `code-line`
|
|
|
|
|
|
|
+ * Create container AST for node lines
|
|
|
*
|
|
*
|
|
|
- * @param {string} text
|
|
|
|
|
|
|
+ * @param {number} number
|
|
|
* @return {Element[]}
|
|
* @return {Element[]}
|
|
|
*/
|
|
*/
|
|
|
-const splitLine = (text) => {
|
|
|
|
|
- // Xdm Markdown parses every code line with \n
|
|
|
|
|
- const textArray = text.split(/\n/)
|
|
|
|
|
-
|
|
|
|
|
- // Remove last line \n which results in empty array
|
|
|
|
|
- if (textArray[textArray.length - 1].trim() === '') {
|
|
|
|
|
- textArray.pop()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Empty array are actually line segments so we convert them back to newlines
|
|
|
|
|
- return textArray.map((line) => {
|
|
|
|
|
- return {
|
|
|
|
|
|
|
+const createLineNodes = (number) => {
|
|
|
|
|
+ const a = new Array(number)
|
|
|
|
|
+ for (let i = 0; i < number; i++) {
|
|
|
|
|
+ a[i] = {
|
|
|
type: 'element',
|
|
type: 'element',
|
|
|
tagName: 'span',
|
|
tagName: 'span',
|
|
|
- properties: { className: ['code-line'] },
|
|
|
|
|
- children: [{ type: 'text', value: line }],
|
|
|
|
|
|
|
+ properties: { className: [] },
|
|
|
|
|
+ children: [],
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ return a
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * Split multiline text nodes into individual nodes with positioning
|
|
|
* Add a node start and end line position information for each text node
|
|
* Add a node start and end line position information for each text node
|
|
|
*
|
|
*
|
|
|
* @return { (ast:Element['children']) => Element['children'] }
|
|
* @return { (ast:Element['children']) => Element['children'] }
|
|
@@ -108,18 +102,32 @@ const addNodePositionClosure = () => {
|
|
|
* @return {Element['children']}
|
|
* @return {Element['children']}
|
|
|
*/
|
|
*/
|
|
|
const addNodePosition = (ast) => {
|
|
const addNodePosition = (ast) => {
|
|
|
- // @ts-ignore
|
|
|
|
|
return ast.reduce((result, node) => {
|
|
return ast.reduce((result, node) => {
|
|
|
if (node.type === 'text') {
|
|
if (node.type === 'text') {
|
|
|
const value = /** @type {string} */ (node.value)
|
|
const value = /** @type {string} */ (node.value)
|
|
|
const numLines = (value.match(/\n/g) || '').length
|
|
const numLines = (value.match(/\n/g) || '').length
|
|
|
- node.position = {
|
|
|
|
|
- // column: 0 is to make the ts compiler happy but we do not use this field
|
|
|
|
|
- start: { line: startLineNum, column: 0 },
|
|
|
|
|
- end: { line: startLineNum + numLines, column: 0 },
|
|
|
|
|
|
|
+ if (numLines === 0) {
|
|
|
|
|
+ node.position = {
|
|
|
|
|
+ // column: 0 is to make the ts compiler happy but we do not use this field
|
|
|
|
|
+ start: { line: startLineNum, column: 0 },
|
|
|
|
|
+ end: { line: startLineNum, column: 0 },
|
|
|
|
|
+ }
|
|
|
|
|
+ result.push(node)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const lines = value.split('\n')
|
|
|
|
|
+ for (const [i, line] of lines.entries()) {
|
|
|
|
|
+ result.push({
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ value: i === lines.length - 1 ? line : line + '\n',
|
|
|
|
|
+ position: {
|
|
|
|
|
+ start: { line: startLineNum + i },
|
|
|
|
|
+ end: { line: startLineNum + i },
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
startLineNum = startLineNum + numLines
|
|
startLineNum = startLineNum + numLines
|
|
|
- result.push(node)
|
|
|
|
|
|
|
+
|
|
|
return result
|
|
return result
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -142,48 +150,6 @@ const addNodePositionClosure = () => {
|
|
|
return addNodePosition
|
|
return addNodePosition
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * Split multiline text nodes into individual nodes with positioning
|
|
|
|
|
- *
|
|
|
|
|
- * @param {Element['children']} ast
|
|
|
|
|
- * @return {Element['children']}
|
|
|
|
|
- */
|
|
|
|
|
-const splitTextByLine = (ast) => {
|
|
|
|
|
- //@ts-ignore
|
|
|
|
|
- return ast.reduce((result, node) => {
|
|
|
|
|
- if (node.type === 'text') {
|
|
|
|
|
- if (node.value.indexOf('\n') === -1) {
|
|
|
|
|
- result.push(node)
|
|
|
|
|
- return result
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const lines = node.value.split('\n')
|
|
|
|
|
- for (const [i, line] of lines.entries()) {
|
|
|
|
|
- result.push({
|
|
|
|
|
- type: 'text',
|
|
|
|
|
- value: i === lines.length - 1 ? line : line + '\n',
|
|
|
|
|
- position: {
|
|
|
|
|
- start: { line: node.position.start.line + i },
|
|
|
|
|
- end: { line: node.position.start.line + i },
|
|
|
|
|
- },
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return result
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (Object.prototype.hasOwnProperty.call(node, 'children')) {
|
|
|
|
|
- // @ts-ignore
|
|
|
|
|
- node.children = splitTextByLine(node.children)
|
|
|
|
|
- result.push(node)
|
|
|
|
|
- return result
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- result.push(node)
|
|
|
|
|
- return result
|
|
|
|
|
- }, [])
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* Rehype prism plugin generator that highlights code blocks with refractor (prismjs)
|
|
* Rehype prism plugin generator that highlights code blocks with refractor (prismjs)
|
|
|
*
|
|
*
|
|
@@ -225,7 +191,6 @@ const rehypePrismGenerator = (refractor) => {
|
|
|
|
|
|
|
|
/** @type {Element} */
|
|
/** @type {Element} */
|
|
|
let refractorRoot
|
|
let refractorRoot
|
|
|
- let langError = false
|
|
|
|
|
|
|
|
|
|
// Syntax highlight
|
|
// Syntax highlight
|
|
|
if (lang) {
|
|
if (lang) {
|
|
@@ -238,7 +203,6 @@ const rehypePrismGenerator = (refractor) => {
|
|
|
)
|
|
)
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
|
|
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
|
|
|
- langError = true
|
|
|
|
|
refractorRoot = node
|
|
refractorRoot = node
|
|
|
} else {
|
|
} else {
|
|
|
throw err
|
|
throw err
|
|
@@ -248,9 +212,9 @@ const rehypePrismGenerator = (refractor) => {
|
|
|
refractorRoot = node
|
|
refractorRoot = node
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const nodeWithPosition = addNodePositionClosure()(refractorRoot.children)
|
|
|
|
|
- refractorRoot.children = splitTextByLine(nodeWithPosition)
|
|
|
|
|
|
|
+ refractorRoot.children = addNodePositionClosure()(refractorRoot.children)
|
|
|
|
|
|
|
|
|
|
+ // Add position info to root
|
|
|
if (refractorRoot.children.length > 0) {
|
|
if (refractorRoot.children.length > 0) {
|
|
|
refractorRoot.position = {
|
|
refractorRoot.position = {
|
|
|
start: { line: refractorRoot.children[0].position.start.line, column: 0 },
|
|
start: { line: refractorRoot.children[0].position.start.line, column: 0 },
|
|
@@ -259,47 +223,62 @@ const rehypePrismGenerator = (refractor) => {
|
|
|
column: 0,
|
|
column: 0,
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ refractorRoot.position = {
|
|
|
|
|
+ start: { line: 0, column: 0 },
|
|
|
|
|
+ end: { line: 0, column: 0 },
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
const shouldHighlightLine = calculateLinesToHighlight(meta)
|
|
const shouldHighlightLine = calculateLinesToHighlight(meta)
|
|
|
const startingLineNumber = calculateStartingLine(meta)
|
|
const startingLineNumber = calculateStartingLine(meta)
|
|
|
- const codeLineArray = splitLine(toString(node))
|
|
|
|
|
|
|
+ const codeLineArray = createLineNodes(refractorRoot.position.end.line)
|
|
|
|
|
+
|
|
|
const falseShowLineNumbersStr = [
|
|
const falseShowLineNumbersStr = [
|
|
|
'showlinenumbers=false',
|
|
'showlinenumbers=false',
|
|
|
'showlinenumbers="false"',
|
|
'showlinenumbers="false"',
|
|
|
'showlinenumbers={false}',
|
|
'showlinenumbers={false}',
|
|
|
]
|
|
]
|
|
|
for (const [i, line] of codeLineArray.entries()) {
|
|
for (const [i, line] of codeLineArray.entries()) {
|
|
|
- // Code lines
|
|
|
|
|
|
|
+ // Default class name for each line
|
|
|
|
|
+ line.properties.className = ['code-line']
|
|
|
|
|
+
|
|
|
|
|
+ // Syntax highlight
|
|
|
|
|
+ const treeExtract = filter(
|
|
|
|
|
+ refractorRoot,
|
|
|
|
|
+ (node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
|
|
|
|
|
+ )
|
|
|
|
|
+ line.children = treeExtract.children
|
|
|
|
|
+
|
|
|
|
|
+ // Line number
|
|
|
if (
|
|
if (
|
|
|
(meta.toLowerCase().includes('showLineNumbers'.toLowerCase()) ||
|
|
(meta.toLowerCase().includes('showLineNumbers'.toLowerCase()) ||
|
|
|
options.showLineNumbers) &&
|
|
options.showLineNumbers) &&
|
|
|
!falseShowLineNumbersStr.some((str) => meta.toLowerCase().includes(str))
|
|
!falseShowLineNumbersStr.some((str) => meta.toLowerCase().includes(str))
|
|
|
) {
|
|
) {
|
|
|
line.properties.line = [(i + startingLineNumber).toString()]
|
|
line.properties.line = [(i + startingLineNumber).toString()]
|
|
|
- // @ts-ignore
|
|
|
|
|
line.properties.className.push('line-number')
|
|
line.properties.className.push('line-number')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Line highlight
|
|
// Line highlight
|
|
|
if (shouldHighlightLine(i)) {
|
|
if (shouldHighlightLine(i)) {
|
|
|
- // @ts-ignore
|
|
|
|
|
line.properties.className.push('highlight-line')
|
|
line.properties.className.push('highlight-line')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Diff classes
|
|
|
if (lang === 'diff' && toString(line).substring(0, 1) === '-') {
|
|
if (lang === 'diff' && toString(line).substring(0, 1) === '-') {
|
|
|
- // @ts-ignore
|
|
|
|
|
line.properties.className.push('deleted')
|
|
line.properties.className.push('deleted')
|
|
|
} else if (lang === 'diff' && toString(line).substring(0, 1) === '+') {
|
|
} else if (lang === 'diff' && toString(line).substring(0, 1) === '+') {
|
|
|
- // @ts-ignore
|
|
|
|
|
line.properties.className.push('inserted')
|
|
line.properties.className.push('inserted')
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Syntax highlight
|
|
|
|
|
- const treeExtract = filter(
|
|
|
|
|
- refractorRoot,
|
|
|
|
|
- (node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
|
|
|
|
|
- )
|
|
|
|
|
- line.children = treeExtract.children
|
|
|
|
|
|
|
+ // Remove possible trailing line when splitting by \n which results in empty array
|
|
|
|
|
+ if (
|
|
|
|
|
+ codeLineArray.length > 0 &&
|
|
|
|
|
+ toString(codeLineArray[codeLineArray.length - 1]).trim() === ''
|
|
|
|
|
+ ) {
|
|
|
|
|
+ codeLineArray.pop()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
node.children = codeLineArray
|
|
node.children = codeLineArray
|