| | |
| | function findMatchingClosingTag(src, openTag, closeTag) { |
| | let depth = 1; |
| | let index = openTag.length; |
| | while (depth > 0 && index < src.length) { |
| | if (src.startsWith(openTag, index)) { |
| | depth++; |
| | } else if (src.startsWith(closeTag, index)) { |
| | depth--; |
| | } |
| | if (depth > 0) { |
| | index++; |
| | } |
| | } |
| | return depth === 0 ? index + closeTag.length : -1; |
| | } |
| |
|
| | function detailsTokenizer(src) { |
| | const detailsRegex = /^<details>\n/; |
| | const summaryRegex = /^<summary>(.*?)<\/summary>\n/; |
| |
|
| | if (detailsRegex.test(src)) { |
| | const endIndex = findMatchingClosingTag(src, '<details>', '</details>'); |
| | if (endIndex === -1) return; |
| |
|
| | const fullMatch = src.slice(0, endIndex); |
| | let content = fullMatch.slice(10, -10).trim(); |
| |
|
| | let summary = ''; |
| | const summaryMatch = summaryRegex.exec(content); |
| | if (summaryMatch) { |
| | summary = summaryMatch[1].trim(); |
| | content = content.slice(summaryMatch[0].length).trim(); |
| | } |
| |
|
| | return { |
| | type: 'details', |
| | raw: fullMatch, |
| | summary: summary, |
| | text: content |
| | }; |
| | } |
| | } |
| |
|
| | function detailsStart(src) { |
| | return src.match(/^<details>/) ? 0 : -1; |
| | } |
| |
|
| | function detailsRenderer(token) { |
| | return `<details> |
| | ${token.summary ? `<summary>${token.summary}</summary>` : ''} |
| | ${token.text} |
| | </details>`; |
| | } |
| |
|
| | function detailsExtension() { |
| | return { |
| | name: 'details', |
| | level: 'block', |
| | start: detailsStart, |
| | tokenizer: detailsTokenizer, |
| | renderer: detailsRenderer |
| | }; |
| | } |
| |
|
| | export default function (options = {}) { |
| | return { |
| | extensions: [detailsExtension(options)] |
| | }; |
| | } |
| |
|