remark-mark-plus is a remark plugin that extends Markdown syntax to support highlighted text using ==text== notation. This syntax is then transformed into HTML <mark> elements, commonly used to draw attention to specific portions of text.
This plugin seamlessly integrates into the unified ecosystem, leveraging micromark for efficient tokenization and mdast for abstract syntax tree (AST) representation.
This plugin is designed for:
- Developers and content creators working with Markdown in Node.js environments.
- Users of the
unifiedcollective, includingremarkfor Markdown processing andrehypefor HTML transformation. - Projects involving static site generation, documentation systems, or any application where rich text emphasis beyond standard Markdown (bold, italic) is needed.
- Enhanced Semantics: Provides a clear, GFM-like syntax (
==highlight==) to semantically mark text for emphasis or highlighting. - Standard HTML Output: Converts the custom syntax to standard HTML
<mark>tags, ensuring broad browser compatibility and accessibility. - Ecosystem Compatible: Built for the modern
unifiedandremarkecosystem, ensuring compatibility and extensibility. - Customizable Processing: Fits into flexible
unifiedpipelines, allowing combination with many other text processing plugins.
Install remark-mark-plus using npm:
npm install remark-mark-plusThis package is an ESM-only module. In CommonJS projects, you may need to use dynamic import().
remark-mark-plus is used within a unified processing pipeline.
Here's a typical example of how to use remark-mark-plus to convert Markdown containing ==highlighted text== into HTML:
import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkMarkPlus from 'remark-mark-plus' // Ensure this path is correct if local
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
const markdownInput = `
This is some ==important highlighted text==.
You can also ==highlight text with ==nested markers== inside==.
And ==multiple highlights== in one paragraph.
`
// Note: While the syntax allows nested `==...==` like the example above,
// standard GFM-style markdowns usually don't support true nesting of the same marker.
// This plugin will parse it based on its rules, potentially leading to <mark>text with <mark>nested</mark> inside</mark>.
// Users should be mindful of this if aiming for strict GFM compatibility or specific rendering behaviors.
async function processMarkdown(markdown) {
const file = await unified()
.use(remarkParse) // Parse the Markdown into an mdast tree
.use(remarkMarkPlus) // Apply the highlight syntax
.use(remarkRehype) // Convert mdast to hast (HTML AST)
.use(rehypeStringify) // Convert hast to an HTML string
.process(markdown)
return String(file)
}
processMarkdown(markdownInput).then(htmlOutput => {
console.log(htmlOutput);
/*
Example Output:
<p>This is some <mark>important highlighted text</mark>.
You can also <mark>highlight text with <mark>nested markers</mark> inside</mark>.
And <mark>multiple highlights</mark> in one paragraph.</p>
*/
})While remark-mark-plus is primarily used programmatically, remark-cli can be configured to use plugins. You would typically do this by:
- Ensuring
remark-cliandremark-mark-plusare project dependencies. - Creating a
.remarkrc.js(or.remarkrc.mjsfor ESM) configuration file in your project.
Example .remarkrc.mjs:
import remarkMarkPlus from 'remark-mark-plus';
export default {
plugins: [
remarkMarkPlus,
// other remark plugins
]
};Then, you could use remark-cli to process files:
npx remark input.md --outputNote: remark-cli itself primarily processes and outputs Markdown. To get HTML output as shown in the programmatic example, your remark pipeline (configured via .remarkrc.mjs or programmatically) would need to include remark-rehype and a stringifier like rehype-stringify. The CLI command above would apply remark-mark-plus (and any other remark plugins) and then output the processed Markdown. For direct HTML output via CLI, you might use remark-cli in conjunction with other tools or a custom script that builds the full unified pipeline.
remark-mark-plus operates as a plugin within the unified processing ecosystem. It extends remark to understand and process ==highlighted text== syntax.
The process involves several key stages:
-
Micromark Extension (
src/lib/micromark-syntax.js):- At the core,
remark-mark-plusprovides a micromark syntax extension. Micromark is the low-level parser that converts Markdown text into a stream of tokens. - The extension defines how to recognize the
==markers.- It looks for an opening
==sequence that is not followed by a space or another=(to avoid===being misinterpreted). - It then tokenizes the content between the opening and a closing
==sequence.
- It looks for an opening
- The key token types generated by
micromark-syntax.jsare:markSequence: Represents the==opening and closing markers.markText: Represents the content within the==...==markers.mark: A wrapping token for the entire==highlighted text==construct.
- The tokenizer (
tokenizeMarkfunction) manages states to correctly parse opening sequences, content, and potential closing sequences, ensuring that markers are balanced and correctly scoped. It handles cases like unterminated marks or marks at line endings.
- At the core,
-
MDAST Utility Handlers (
src/lib/mdast-util-handlers.js):- Once micromark has tokenized the input, these handlers bridge the gap to the mdast (Markdown Abstract Syntax Tree) representation.
markFromMarkdown: This handler is used during the "fromMarkdown" phase (parsing Markdown to mdast).- It listens for the
marktoken from the micromark extension. - When an opening
markSequencetoken is encountered, it starts building a newmarknode in the mdast. - The content (
markText) is processed as phrasing content and becomes the children of thismarknode. - Upon encountering the closing
markSequence, themarknode is finalized.
- It listens for the
markToMarkdown: This handler is used during the "toMarkdown" phase (serializing mdast back to Markdown).- It defines how an mdast
marknode should be converted back into its text representation. - It prepends
==to the serialized content of themarknode's children and appends==. - It includes logic for safe serialization, considering characters that might be unsafe at the beginning or end of the highlighted content.
- It defines how an mdast
The plugin introduces a new Mark node type to the mdast.
Its definition in TypeScript (for illustrative purposes, as this is a JS project) would be:
import {Parent, PhrasingContent} from 'mdast'
export interface Mark extends Parent {
type: 'mark'
children: PhrasingContent[]
}
// To make it available in the mdast content model for TypeScript users:
declare module 'mdast' {
interface PhrasingContentMap {
mark: Mark
}
}For example, the Markdown This is ==highlighted==. would yield an mdast structure (simplified) like:
{
type: 'paragraph',
children: [
{ type: 'text', value: 'This is ' },
{
type: 'mark',
children: [
{ type: 'text', value: 'highlighted' }
]
},
{ type: 'text', value: '.' }
]
}When used with remark-rehype, the mark mdast nodes are transformed into hast (HTML Abstract Syntax Tree) elements. remark-rehype will convert mark nodes to HTML <mark> elements, which can then be stringified to HTML using rehype-stringify.
We welcome contributions to remark-mark-plus! Whether it's reporting a bug, suggesting an enhancement, or submitting a pull request, your help is valued. Please refer to the CONTRIBUTING.md file for full details. Here's a summary:
-
Getting Started:
- Fork the repository on GitHub.
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/remark-mark-plus.git - Navigate to the project directory:
cd remark-mark-plus - Install dependencies:
npm install(this also runsnpm run buildvia thepreparescript). - To build manually:
npm run build
-
Development Process:
- Create your feature branch from
main. - Write Tests: If you add new functionality or fix a bug, please add corresponding tests in the
__tests__directory. Tests are run using Jest:npm test. - Lint Your Code: This project uses ESLint for code style. Ensure your code adheres to the linting rules by running
npm run pretest. Many editors can be configured to show ESLint errors and auto-fix them. - Update Documentation: If you change APIs or add features, update this README.md and any other relevant documentation.
- Commit Messages: Consider adhering to conventional commit message standards.
- Create your feature branch from
-
Pull Requests:
- Ensure all tests pass (
npm test). - Ensure your code lints (
npm run pretest). - Push your branch to your fork and submit a pull request to the main
remark-mark-plusrepository. - Provide a clear description of your changes in the pull request.
- Ensure all tests pass (
-
Reporting Bugs & Suggesting Enhancements:
- Use the GitHub Issues page.
- For bugs, provide a clear title, description, relevant information, and a reproducible code sample or test case.
- For enhancements, clearly describe the proposed feature and its benefits.
This project is an ESM-only module. Please ensure your contributions are compatible with this module type.
MIT © Zeste de Savoir and contributors.