markdown-it-subtable.js 5.1 KB
Newer Older
D
DCloud_LXH 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
function normalizeMD (md) {
  if (typeof md === 'string') {
    return md
      .replace(/(?<!\\)</g, '\\<')
      .replace(/]([^(])|(?<!\\)]$/g, '\\]$1')
      .replace(/(?<!\\)\|/g, '\\|')
  }
  return md
}

function resolveSubtable(md, tokens = [], tokenIndexes = [], flags) {
	if (tokens.length && tokenIndexes.length) {
		tokenIndexes.forEach(tokenIndex => {
			if (Array.isArray(tokenIndex) && tokenIndex.length > 2) {
				const tableOpenIndex = tokenIndex.shift();
				const tableCloseIndex = tokenIndex.pop();
				let deleteOffset = 0;

				tokenIndex.forEach(subtableIndexes => {
					const subtableOpenIndex = subtableIndexes.shift() + deleteOffset;
					const subtableColumnCount = (subtableIndexes[0] + deleteOffset - subtableOpenIndex - 2) / 3 - 1;
					const subtableCloseIndex = subtableIndexes.pop() + deleteOffset + subtableColumnCount * 3;
					const subtableTokens = tokens.slice(subtableOpenIndex - 1, subtableCloseIndex + 1);

					// 2 table token 新行开始时固定的 tr_open td_open 数量
					// 3 每个 inline 都会伴随一个 td_close、td_open(行最后一列 inline 会伴随 td_close tr_close)
					// 1 为 flag ->(标识符) 的列,需要去掉
					let tableColumnCount = 0;
					for (let i = 0; i < tableCloseIndex; i++) {
						const token = tokens[i];
						if (i < subtableOpenIndex) {
							if (token.type === 'th_open') {
								tableColumnCount++;
							} else if (token.type === 'thead_close') {
								break;
							}
						}
					}

					let subtableMD = subtableTokens
						.map(token => {
							if (token.type === 'inline' && !flags.includes(token.content)) {
								return normalizeMD(token.content);
							} else if (token.type === 'tr_close') {
								return '\n';
							}
						})
						.filter(Boolean);
					const subtableLevel = subtableTokens[0].level;
					const newTokens = md.parse(`|${subtableMD.join('|')}|`);
					newTokens.forEach(token => {
						token.content = ''; // 和 children 内容重复
						token.level = token.level + subtableLevel;
					});
					newTokens[0].attrJoin('style', 'overflow-x: visible;margin: auto;');
					const childrenTableTokenIndex = subtableOpenIndex - 2;
					const subtablePrevTrOpenIndex = childrenTableTokenIndex - tableColumnCount * 3 - 2;
					tokens[tableOpenIndex].attrJoin('class', 'have-children-table'); // table
					tokens[subtablePrevTrOpenIndex].attrJoin('class', 'have-children-tr'); // table tr_open

					// table td_open
					const haveChildrenTrTdToken = tokens[subtablePrevTrOpenIndex + 1];
					haveChildrenTrTdToken.attrJoin('class', 'have-children-tr-td');
					haveChildrenTrTdToken.attrJoin('style', ';text-wrap: nowrap');

					tokens[childrenTableTokenIndex].attrJoin('class', 'children-table'); // subtable tr_open
					tokens[childrenTableTokenIndex].attrJoin('hidden', ''); // subtable tr_open

					tokens[subtableOpenIndex - 1].attrJoin('colspan', tableColumnCount); // subtable td_open
					const deleteCount = subtableCloseIndex - subtableOpenIndex + 1;
					tokens.splice(subtableOpenIndex, deleteCount, ...newTokens);
					deleteOffset = deleteOffset + newTokens.length - deleteCount;
				});
			}
		});
	}
}

function process(md, tokens, flags = ['->']) {
	const subtableMinTokenCount = 3;
	if (
		Array.isArray(tokens) &&
		tokens.length &&
		tokens.some(token => token.content.includes('->') && token.type === 'inline')
	) {
		const tableOpenTokenIndex = tokens.findIndex(token => token.type === 'table_open');
		if (tableOpenTokenIndex > -1) {
			/**
			 * [
			 * 	table_open index,
			 * 	[] // subtable indexes,
			 * 	table_close index
			 * ]
			 */
			let tableTokensIndexes = [[]];
			let tableIndex = 0;
			let subtableIndex = 1;
			tokens.forEach((token, index) => {
				if (token.type === 'table_open' && tableTokensIndexes[tableIndex].length === 0) {
					tableTokensIndexes[tableIndex].push(index);
				}
				if (tableTokensIndexes[tableIndex] && typeof tableTokensIndexes[tableIndex][0] !== 'undefined') {
					if (token.type === 'inline') {
						if (flags.find(flag => new RegExp(`^\\s*${flag}\\s*$`).test(token.content))) {
							(
								tableTokensIndexes[tableIndex][subtableIndex] || (tableTokensIndexes[tableIndex][subtableIndex] = [])
							).push(index);
						} else if (tokens[index - 2].type === 'tr_open') {
							tableTokensIndexes[tableIndex][++subtableIndex] = [];
						}
					}
				}
				if (token.type === 'table_close') {
					subtableIndex = 1;
					tableTokensIndexes[tableIndex].push(index);
					tableTokensIndexes[++tableIndex] = [];
				}
			});
			tableTokensIndexes.forEach((subtableTokensIndex, index) => {
				tableTokensIndexes[index] = subtableTokensIndex.filter(
					i => typeof i === 'number' || (Array.isArray(i) && i.length >= subtableMinTokenCount)
				);
			});
			tableTokensIndexes = tableTokensIndexes.filter(i => i.length);
			if (tableTokensIndexes.length) {
				resolveSubtable(md, tokens, tableTokensIndexes, flags);
			}
		}
	}
}

module.exports = {
	process,
	markdownIt: function subTablePlugin(md, { flags = ['->'] } = {}) {
		md.core.ruler.after(
			'inline',
			'subtable',
			(state, startLine, endLine, silent) => {
				process(md, state.tokens, flags);
			},
			{
				alt: ['paragraph', 'reference'],
			}
		);
	},
};