import type { Markdown } from 'vuepress/markdown'
import type { TableContainerOptions } from '../../shared/table.js'
import { encodeData } from '@vuepress/helper'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerSyntaxPlugin } from './createContainer.js'
/**
* Table container attributes
*
* 表格容器属性
*/
export interface TableContainerAttrs extends TableContainerOptions {
/**
* Table title
*
* 表格标题
*/
title?: string
/**
* Highlighted rows
*
* 表格高亮的行
*
* @example hl-rows="warning:1,2,3;error:4,5,6"
*/
hlRows?: string
/**
* Highlighted columns
*
* 表格高亮的列
*
* @example hl-cols="warning:1;error:2,3"
*/
hlCols?: string
/**
* Highlighted cells
*
* 表格高亮的单元格
*
* @example hl-cells="warning:(1,2)(2,3);error:(3,4)(4,5)"
*/
hlCells?: string
}
/**
* Table plugin - Wrap table with container for enhanced features
*
* 表格插件 - 通过容器语法将表格包裹起来,为表格提供增强功能
*
* @example
* ```md
* ::: table title="表格标题" max-content copy align="center" hl-rows="warning:1,2,3;error:4,5,6" hl-cols="warning:1;error:2,3" hl-cells="warning:(1,2)(2,3);"
*
* | xx | xx | xx |
* | -- | -- | -- |
* | xx | xx | xx |
* :::
* ```
*
* @param md - Markdown instance / Markdown 实例
* @param options - Table container options / 表格容器选项
*/
export function tablePlugin(md: Markdown, options: TableContainerOptions = {}): void {
createContainerSyntaxPlugin(md, 'table', (tokens, index, opt, env) => {
const { hlCols = '', hlRows = '', hlCells = '', ...meta } = tokens[index].meta as TableContainerAttrs
const props = { copy: true, maxContent: false, fullWidth: false, ...options, ...meta } as TableContainerAttrs & { markdown?: string }
const content = tokens[index].content
if (props.copy) {
props.copy = props.copy === true ? 'all' : props.copy
if (props.copy === 'all' || props.copy === 'md') {
props.markdown = encodeData(content.trim())
}
}
if (!hlCols && !hlRows && !hlCells) {
return `${md.render(content, env)}`
}
const rows = parseHl(hlRows)
const cols = parseHl(hlCols)
const cells = parseHlCells(hlCells)
const tableTokens = md.parse(content, env)
let isTable = false
let colIndex = 0
let rowIndex = 0
for (const token of tableTokens) {
if (token.type === 'table_open')
isTable = true
if (token.type === 'table_close')
isTable = false
if (!isTable)
continue
// row
if (token.type === 'tr_open') {
rowIndex++
colIndex = 0
}
// cell (rowIndex, colIndex)
if (token.type === 'th_open' || token.type === 'td_open') {
colIndex++
const classes = cells[rowIndex]?.[colIndex] || rows[rowIndex] || cols[colIndex]
if (classes)
token.attrJoin('class', classes)
}
}
return `${md.renderer.render(tableTokens, opt, env)}`
})
}
/**
* Parse highlight string
*
* 解析高亮字符串
*
* @param hl - Highlight string / 高亮字符串
* @returns Parsed highlight map / 解析后的高亮映射
*/
function parseHl(hl: string) {
const res: Record = {}
if (!hl)
return res
hl
.split(';')
.forEach((item) => {
const [key, value = '1'] = item.split(':')
String(value).split(',').forEach(v => res[v.trim()] = key.trim())
})
return res
}
/**
* Parse highlight cells string
*
* 解析高亮单元格字符串
*
* @param hl - Highlight cells string / 高亮单元格字符串
* @returns Parsed highlight cells map / 解析后的高亮单元格映射
*/
function parseHlCells(hl: string) {
const res: Record> = {}
if (!hl)
return res
hl
.split(';')
.forEach((item) => {
const [key, value = ''] = item.split(':')
value.trim().replace(/\s*\((\d+)\s*,\s*(\d+)\)\s*/g, (_, row, col) => {
res[row] ??= {}
res[row][col] = key.trim()
return ''
})
})
return res
}