@digitalocean/do-markdownit
Version:
Markdown-It plugin for the DigitalOcean Community.
123 lines (102 loc) • 4.21 kB
JavaScript
/*
Copyright 2022 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
;
/**
* @module rules/embeds/details
*/
const blockLines = require('../../util/block_lines');
const findBlockEmbed = require('../../util/find_block_embed');
/**
* Add support for expandable details in Markdown, as block syntax.
*
* To create an expandable details section, use `[details` followed by a summary.
* Content for the expanded section should be provided on lines after, closed with `]` on a new line.
*
* @example
* [details This is hidden content
* Content for inside the expanded section
* ]
*
* [details open This section is *open* by default
* Content for inside the expanded section
* ]
*
* <details>
* <summary>This is hidden content</summary>
* <p>Content for inside the expanded section</p>
* </details>
*
* <details open>
* <summary>This section is <em>open</em> by default</summary>
* <p>Content for inside the expanded section</p>
* </details>
*
* @type {import('markdown-it').PluginSimple}
*/
module.exports = md => {
/**
* Parsing rule for details markup.
*
* @type {import('markdown-it/lib/parser_block').RuleBlock}
* @private
*/
const detailsRule = (state, startLine, endLine, silent) => {
// If silent, don't replace
if (silent) return false;
// Get current string to consider (current line to end)
const currentLines = blockLines(state, startLine, endLine);
// Attempt to find the block
const block = findBlockEmbed(currentLines, 'details');
if (block === false) return false;
// Check that we have content
const parts = currentLines[0].split(' ');
if (parts.length === 1) return false;
const stateSelection = [ 'open', 'closed' ].includes(parts[1].toLowerCase()) ? parts[1].toLowerCase() : null;
if (parts.length === 2 && stateSelection) return false;
// Create the outer details element
const tokenDetailsOpen = state.push('details', 'details', 1);
tokenDetailsOpen.block = true;
tokenDetailsOpen.map = [ startLine, startLine + block ];
if (stateSelection === 'open') tokenDetailsOpen.attrSet('open', '');
// Create the summary element
const tokenSummaryOpen = state.push('summary', 'summary', 1);
tokenSummaryOpen.block = true;
tokenSummaryOpen.map = [ startLine, startLine + 1 ];
tokenSummaryOpen.markup = currentLines[0];
// Create an inline token for the summary
const tokenSummary = state.push('inline', '', 0);
tokenSummary.content = parts.slice(stateSelection ? 2 : 1).join(' ');
tokenSummary.map = [ startLine, startLine + 1 ];
tokenSummary.children = [];
// Close the summary element
const tokenSummaryClose = state.push('summary', 'summary', -1);
tokenSummaryClose.block = true;
// Ensure we only tokenize the content of the column
const oldParentType = state.parentType;
const oldLineMax = state.lineMax;
state.parentType = 'details';
state.lineMax = startLine + block;
// Process the content of the details as block content
state.md.block.tokenize(state, startLine + 1, startLine + block);
// Close the outer details element
const tokenContainerClose = state.push('details', 'details', -1);
tokenContainerClose.block = true;
// Move the parser past this content
state.parentType = oldParentType;
state.lineMax = oldLineMax;
state.line = startLine + block + 1;
// Done
return true;
};
md.block.ruler.before('paragraph', 'details', detailsRule);
};