npm.io
0.10.0 • Published 1 month ago

@peaceroad/markdown-it-renderer-inline-text

Licence
MIT
Version
0.10.0
Deps
0
Size
150 kB
Vulns
0
Weekly
0

p7d-markdown-it-renderer-inline-text

Inline text transform plugin for markdown-it.

It adds:

  • Ruby conversion (漢字《かんじ》)
  • Automatic and explicit figure-reference decoration ((図1), **Figure A**, *Fig. 1*)
  • Star comments (★...★)
  • Percent comments (%%...%%)

Quick Start

import mdit from 'markdown-it'
import mditRendererInlineText from '@peaceroad/markdown-it-renderer-inline-text'

// Ruby/figure/star/percent transforms work with both html:true and html:false.
const md = mdit({ html: true }).use(mditRendererInlineText, {
  ruby: true,
  figureReference: true,
  starComment: true,
  percentComment: true,
})

console.log(md.render('今夜は★カツ★カレーです。'))
// <p>今夜は<span class="star-comment">★カツ★</span>カレーです。</p>

console.log(md.render('今日は甘味処が%%午後%%休みです。'))
// <p>今日は甘味処が<span class="percent-comment">%%午後%%</span>休みです。</p>

console.log(md.render('昼食は親子丼《おやこどん》です。'))
// <p>昼食は<ruby>親子丼<rp>《</rp><rt>おやこどん</rt><rp>》</rp></ruby>です。</p>

console.log(md.render('詳細は(図A.1)を参照してください。'))
// <p>詳細は(<span class="f-ref">図A.1</span>)を参照してください。</p>

console.log(md.render('詳細は **図A.1** を参照してください。'))
// <p>詳細は <span class="f-ref">図A.1</span> を参照してください。</p>

Ruby Syntax

Ruby conversion is based on:

  • bare shorthand: ([Han + 0-9A-Za-z._-]+)《reading》
  • explicit wrapper shorthand: <ruby>base《reading》</ruby> (open/close pair required, case-insensitive tag match)

Examples:

Input: 寿司は職人《しょくにん》の技です。
Output: <p>寿司は<ruby>職人<rp>《</rp><rt>しょくにん</rt><rp>》</rp></ruby>の技です。</p>

Input: 商品名はRAMEN2025《らーめんにーぜろにーごー》です。
Output: <p>商品名は<ruby>RAMEN2025<rp>《</rp><rt>らーめんにーぜろにーごー</rt><rp>》</rp></ruby>です。</p>

When the base text has long kanji runs, spaces, or hiragana/katakana, use an explicit <ruby>...</ruby> boundary for predictable wrapping. The explicit form accepts nonempty plain-text base and reading fields without nested angle markup, raw angle characters, or extra ruby brackets. Bare shorthand keeps the stricter Han/ASCII base grammar:

Input: お店の名物<ruby>鯛茶漬《たいちゃづけ》</ruby>を紹介します。
Output: <p>お店の名物<ruby>鯛茶漬<rp>《</rp><rt>たいちゃづけ</rt><rp>》</rp></ruby>を紹介します。</p>

Ruby shorthand conversion works in both html:true and html:false. With html:false, HTML-like input is generally escaped, while ruby shorthand and explicit <ruby>...</ruby> wrappers are still rendered as ruby HTML. Each complete explicit wrapper owns its entire range. If that range contains a nested <ruby> wrapper or other inline markup, its shorthand is left unconverted instead of converting a bare-looking suffix inside the wrapper. With html:true, an existing parsed <ruby> wrapper may carry attributes; direct plain-text shorthand is converted inside that wrapper without creating a nested <ruby> element. Attribute-bearing wrappers remain escaped under html:false, where only the exact attribute-free shorthand wrapper is the explicit HTML exception. If the wrapper is unclosed (for example <RUBY>寿司《すし》), the wrapper text is treated as normal text and only shorthand part is converted.

html:false example:

Input:案内文:<ruby>寿司《すし》</ruby>を掲載します。
Output:<p>案内文:<ruby>寿司<rp>《</rp><rt>すし</rt><rp>》</rp></ruby>を掲載します。</p>

Figure Reference Syntax

figureReference: true enables both authoring styles. Use the two mode-specific options when only one style is wanted or when overriding one side of the shorthand:

  • figureReferenceAuto: automatically decorate a recognized label and identifier inside paired ASCII or fullwidth parentheses. The parentheses and surrounding whitespace are preserved.
  • figureReferenceManual: treat an exact recognized reference wrapped in **...** or *...* as an explicit reference marker. Both use the configured figure-reference tag instead of emitting <strong> or <em> because the wrapper is a presentation hook rather than author emphasis.
Input: 詳細は(図1)と(図A.1)を参照してください。
Output: <p>詳細は(<span class="f-ref">図1</span>)と(<span class="f-ref">図A.1</span>)を参照してください。</p>

Input: See (Figure 1-1) and (Figure A).
Output: <p>See (<span class="f-ref">Figure 1-1</span>) and (<span class="f-ref">Figure A</span>).</p>

Input: See (Fig. 1), (Fig.A-1), and (Figure.A.1).
Output: <p>See (<span class="f-ref">Fig. 1</span>), (<span class="f-ref">Fig.A-1</span>), and (<span class="f-ref">Figure.A.1</span>).</p>

Input: 詳細は(図3、4)と(図A-1、図B-1)を参照してください。
Output: <p>詳細は(<span class="f-ref">図3、4</span>)と(<span class="f-ref">図A-1、図B-1</span>)を参照してください。</p>

Input: See (Figure 3, Figure 4) and (Figs. 5 and 6).
Output: <p>See (<span class="f-ref">Figure 3, Figure 4</span>) and (<span class="f-ref">Figs. 5 and 6</span>).</p>

Input: 詳細は(**図1**)と **図A.1** を参照してください。
Output: <p>詳細は(<span class="f-ref">図1</span>)と <span class="f-ref">図A.1</span> を参照してください。</p>

Input: See *Figure A* and **Figures 3 and 4**.
Output: <p>See <span class="f-ref">Figure A</span> and <span class="f-ref">Figures 3 and 4</span>.</p>

Recognized forms:

  • Parentheses must be a matching (...) or (...) pair.
  • Japanese references start with 図 and have no intervening space.
  • A Japanese reference can list identifiers with 、; 図 may be omitted or repeated after the separator. This covers 図3、4, 図3、図4, and mixed identifiers such as 図A-1、B-1、図C.1.
  • English references start with case-sensitive Figure, Figures, Fig, or Figs.
  • Figure / Figures are followed by either an ASCII dot or one or more spaces, tabs, or fullwidth spaces.
  • Fig / Figs are followed by one or more such spaces, or by an ASCII dot with optional spaces after it. This covers Fig 1, Fig.1, and Fig. 1.
  • Singular English labels can be repeated in a comma/and list, such as Figure 3, Figure 4 or Fig. 3 and Fig. 4. The same label form must be used for every item.
  • Plural Figures / Figs require at least two identifiers and a final and; intermediate identifiers are optional, and lists of three or more may use an Oxford comma. This covers Figures 3 and 4, Figures 3, 4 and 5, and Figs. 3, 4, and 5.
  • Each identifier component is either one ASCII/fullwidth uppercase letter or one or more ASCII/fullwidth digits.
  • Components can be joined with ASCII . or -.

Examples include 図1, 図1, 図A, 図A, 図1.1, 図A.1, 図1-1, 図A-1, Figure 1, Figure A.1, Figure.1, Figure.A-1, and Figure.A.1, Fig 1, Fig.1, Fig. 1, and Fig.A-1. A recognized list is wrapped as one reference phrase rather than one element per identifier. Lowercase aliases such as figure / fig and all-uppercase FIG are not recognized. Fullwidth separators such as ., -, :, and , are intentionally not recognized. English shorthand without a repeated or plural label, such as Figure 3, 4, is also left unchanged.

Both modes default to <span class="f-ref">. Use figureReferenceTag to select span, b, or i for both. Set figureReferenceManualTagFromMarker: true when manual **...** should use b and manual *...* should use i instead. Use figureReferenceClass to change the class in both modes. The plugin emits the class hook but does not bundle a stylesheet for it.

md.use(mditRendererInlineText, {
  figureReferenceManual: true,
  figureReferenceManualTagFromMarker: true,
})
// **図1** -> <b class="f-ref">図1</b>
// *図1*   -> <i class="f-ref">図1</i>

Manual mode accepts only an exact recognized reference inside one asterisk delimiter pair. It intentionally leaves _図1_, ***図1***, ** 図1 **, and ordinary emphasis such as **重要** to markdown-it. It reuses markdown-it's parsed emphasis tokens; if a preset disables the emphasis inline rule (for example the unmodified zero preset), manual mode has no delimiter tokens to retag and leaves the source unchanged. Inline/fenced code and link destinations are not rewritten. With html:true, raw HTML and its attributes are also left unchanged. With html:false, HTML-looking source is ordinary text, so a reference inside it can be decorated while markdown-it escapes the tag-like text. An odd backslash run before either opening parenthesis prevents conversion. Markdown-it removes the escaping backslash from \(Figure 1); a backslash before fullwidth ( remains literal because it is not standard Markdown ASCII punctuation.

Compatibility with @peaceroad/markdown-it-figure-with-p-caption

Caption detection runs before this plugin's manual core transform, so plain caption labels such as 図1 Caption and Figure 1. Caption remain owned by the figure plugin. This is stable in either .use(...) order. Caption labels generated as <span>, <b>, or <strong> by that plugin are not retagged; explicit references in caption body text can still use the manual syntax.

Do not use **図1** as the leading caption label. The figure plugin does not recognize an emphasis token as a caption label even without this plugin. Use a plain source label and its bLabel: true option when the rendered caption label should use <b>.

The classes intentionally remain role-specific: f-img-label identifies a label in a caption, while f-ref identifies a reference in body text. When they need the same appearance, group the selectors in CSS (for example, :where(.f-img-label, .f-ref)) rather than making the DOM roles share one class name.

★ / %% Comment Syntax

Both syntaxes are pair-based markers:

  • Star: ★...★ -> <span class="star-comment">...</span>
  • Percent: %%...%% -> <span class="percent-comment">...</span> (or custom class)

Behavior summary (without examples):

  • Pair matching is per marker type, so ★...★ and %%...%% are handled independently.
  • Escaped markers (odd backslash parity) stay as plain text.
  • In a backslash run before a marker, each pair produces one literal backslash; an unpaired final backslash escapes the marker. This is consistent for html: false and html: true.
  • In default span mode, matched ranges are wrapped and preserved in output.
  • In delete mode, only the enabled marker type is removed from output.
  • In inline mode, marker ranges are fixed by preparse before markdown inline formatting.
Escape Markers
Input:料理名は\★限定★ではありません。
Output:<p>料理名は★限定★ではありません。</p>

Input:今日の注記は\%%内輪%%ではありません。
Output:<p>今日の注記は%%内輪%%ではありません。</p>
Span Element (Default)

Basic:

Input: 蕎麦は★売り切れ次第終了です。★美味しいです。
Output: <p>蕎麦は<span class="star-comment">★売り切れ次第終了です。★</span>美味しいです。</p>

Input: 喫茶店に%%季節限定の%%パフェがあります。
Output: <p>喫茶店に<span class="percent-comment">%%季節限定の%%</span>パフェがあります。</p>

Input: 今日は★**本日のおすすめ**の★ハンバーグを注文します。
Output: <p>今日は<span class="star-comment">★**本日のおすすめ**の★</span>ハンバーグを注文します。</p>

In html:true, inline HTML inside a marker range is kept as HTML:

Input: メニューから★<span>だし</span>香る★うどんを選びます。
Output: <p>メニューから<span class="star-comment">★<span>だし</span>香る★</span>うどんを選びます。</p>

Marker priority is high in inline mode (html:true / html:false): markdown syntax inside marker ranges stays literal.

Input: **春★御膳**定★食を案内します。
Output: <p>**春<span class="star-comment">★御膳**定★</span>食を案内します。</p>
Delete option

starCommentDelete and percentCommentDelete are independent:

  • starCommentDelete: true removes only ★...★ ranges.
  • percentCommentDelete: true removes only %%...%% ranges.

In paragraph mode, delete removes only the marked paragraph. It does not delete the containing list item or neighboring paragraphs, and adjacent visible paragraphs are emitted without a placeholder blank line. If the marked paragraph is the only content of a list item, the structural empty <li></li> remains. If source inline HTML opens in that paragraph and closes outside it, paragraph deletion fails closed instead of orphaning the external close tag.

Non-delete line and paragraph modes wrap the complete parsed inline range, so nested links, emphasis, and code spans remain inside balanced comment markup. If a link, emphasis run, or raw HTML element crosses a line boundary, line mode uses separate wrappers around the visible leaf tokens on the marked line rather than emitting overlapping tags. Line deletion likewise retains only the structural open/close tokens needed by surviving text on adjacent lines. Paragraph decoration uses the same leaf fallback when a raw element is not balanced inside the paragraph. A multiline raw HTML tag or comment held in one html_inline token, or an inline construct such as a multiline code span that normalizes away a source newline, is left unchanged in line mode because its individual editor lines cannot be isolated safely.

const md = MarkdownIt().use(rendererInlineText, {
  starComment: true,
  starCommentDelete: true,
  percentComment: true,
  percentCommentDelete: true,
})

console.log(md.render('カレーのメインは%%海老%%鶏肉です。'))
// <p>カレーのメインは鶏肉です。</p>
console.log(md.render('カレーのメインは★あさり★マトンです。'))
// <p>カレーのメインはマトンです。</p>
Line and Paragraph Mode

Line mode example:

Options:{ starComment: true, starCommentLine: true }

Input:
通常案内
★売り切れ注意
通常案内
Output:<p>通常案内
<span class="star-comment">★売り切れ注意</span>
通常案内</p>

Percent comments follow the same line/paragraph behaviors with percentCommentLine, percentCommentParagraph, and percentCommentParagraphClass.

Paragraph mode example:

Options:{ starComment: true, starCommentParagraph: true }

Input: ★本日は売り切れ次第終了です。
Output: <p><span class="star-comment">★本日は売り切れ次第終了です。</span></p>

Paragraph-only class example:

Options:{ starComment: true, starCommentParagraph: true, starCommentParagraphClass: true }

Input: ★本日は売り切れ次第終了です。
Output: <p class="star-comment">★本日は売り切れ次第終了です。</p>

Options

  • ruby (default: false) Enable ruby conversion.

  • figureReference (default: false) Enable both automatic and manual figure-reference conversion. It supplies the default for each mode-specific option.

  • figureReferenceAuto (default: value of figureReference) Automatically decorate recognized single or listed 図..., Figure... / Figures..., and Fig... / Figs... references inside paired parentheses. An explicit value overrides figureReference for this mode.

  • figureReferenceManual (default: value of figureReference) Convert exact single or listed **reference** / *reference* author markers with the configured reference tag and class. An explicit value overrides figureReference for this mode.

  • figureReferenceTag (default: "span") Output tag for automatic and manual references. Allowed values are "span", "b", and "i".

  • figureReferenceManualTagFromMarker (default: false) In manual mode, derive the tag from the author marker instead of figureReferenceTag: **reference** uses <b> and *reference* uses <i>.

  • figureReferenceClass (default: "f-ref") CSS class for automatic and manual references.

  • starComment (default: false) Enable ★...★ comments.

  • starCommentDelete (default: false) Delete star-comment spans instead of rendering them.

  • starCommentParagraph (default: false) Paragraph mode for stars (paragraph starts with ★).

  • starCommentLine (default: false) Line mode for stars (editor line starts with ★).

  • starCommentParagraphClass (default: false) In star paragraph mode, add class to <p> and skip inner span wrapping. true uses "star-comment", and a string uses that class name.

  • percentComment (default: false) Enable %%...%% comments.

  • percentCommentDelete (default: false) Delete percent-comment spans instead of rendering them.

  • percentCommentParagraph (default: false) Paragraph mode for percents (paragraph starts with %%).

  • percentCommentLine (default: false) Line mode for percents (editor line starts with %%).

  • percentCommentParagraphClass (default: false) In percent paragraph mode, add class to <p> and skip inner span wrapping. true uses percentClass, and a string uses that class name.

  • percentClass (default: "percent-comment") CSS class for percent-comment spans.

Notes:

  • If starCommentLine is true, starCommentParagraph is disabled.
  • If percentCommentLine is true, percentCommentParagraph is disabled.
  • In paragraph mode, delete takes precedence over starCommentParagraphClass / percentCommentParagraphClass; a paragraph that cannot be deleted safely is left unchanged rather than falling back to a class.
  • Line and paragraph modes replace inline pair conversion for the same marker. A matching line or paragraph is wrapped only once, while marker pairs outside that mode's target remain literal.
  • Star and percent line modes can be enabled together; mixed ★/%% lines are classified independently, including delete mode.
  • percentClass is escaped via md.utils.escapeHtml.
  • figureReferenceTag is validated at setup time; token attribute rendering escapes figureReferenceClass.
  • figureReferenceManualTagFromMarker affects only the manual output tag; range recognition is unchanged.
  • figureReference: true enables both modes; explicit figureReferenceAuto / figureReferenceManual values override the shorthand independently.

Analyzer API (Experimental)

You can also import a parserless analyzer API from:

import {
  normalizeOptions,
  createRuntimePlan,
  lineStartsWithStar,
  lineStartsWithPercent,
  isEscapedStar,
  isEscapedPercent,
  normalizeLineWindow,
  expandToParagraphBoundaries,
  shouldFullAnalyze,
  scanInlineRanges,
  analyzeLines,
  analyzeLineWindow,
} from '@peaceroad/markdown-it-renderer-inline-text/analyzer'

This API is intended for editor-side highlighting (for example VSCode) without running markdown-it.

Recommended editor workflow:

  • Use analyzeLineWindow(...) for viewport/diff updates.
  • Expand a changed range with expandToParagraphBoundaries(...) (or use analyzeLineWindow default expansion).
  • Paragraph types are resolved from the actual paragraph start even when a requested window or its context starts mid-paragraph.
  • Use shouldFullAnalyze(changeCount, totalLines) to decide when to fallback to full-document analyzeLines(...).
  • Keep final HTML output on markdown-it + plugin (not analyzer output).
  • Results from normalizeOptions(...) and createRuntimePlan(...) are frozen. Pass a fresh option object when behavior changes so precedence and inlineProfileMask cannot drift after normalization.
  • Treat inlineRanges and lineModeRange returned by analyzeLines(...) and analyzeLineWindow(...) as read-only. Cached range arrays and range objects are frozen so duplicate lines and later incremental analyses cannot be corrupted through shared references.

Guaranteed parity scope:

  • marker escape parity (★, %%)
  • inline marker pairing in inline mode (starInlineEnabled / percentInlineEnabled)
  • line-start checks (starCommentLine / percentCommentLine)
  • ruby shorthand range detection outside inline marker-wrapped ranges
  • automatic/manual figure-reference range detection outside inline marker-wrapped ranges

With figureReferenceAuto, matching ranges use type: "figure-reference" and cover the full parenthesized source range. With figureReferenceManual, they cover the full asterisk-marked range. Rendering wraps only the recognized label and identifier in either mode.

Out of scope for strict parity:

  • full markdown-it token behavior (lists, links, emphasis nesting, fenced blocks)
  • HTML block/inline token boundary decisions made by markdown-it and other plugins
  • analyzer parity for source entities that markdown-it decodes before a manual figure-reference or ruby decision
  • renderer-time wrapper suppression / list-item hiding behavior

Notes

Rule Details
  • Escape parity rule:
    • Odd number of backslashes before marker: marker is escaped.
    • Even number: marker can participate in pairing.
  • In inline mode (html:true / html:false), one preparse pass selects the earliest valid enabled star, percent, or figure-reference candidate.
  • Markdown inline syntax inside a marker range is kept literal (for example, ★**bold**★, ★`code`★, ★[link](...)★).
  • Ruby conversion runs on text tokens; when marker preparse has already wrapped a range, ruby conversion does not rewrite inside that wrapped marker content.
  • Figure references emit normal paired inline tokens; figure-only configuration does not install the core conversion rule.
HTML Boundary Behavior
  • With html:true, conversion targets markdown-it inline text tokens.
  • Raw html_block token bodies are not rewritten by this plugin.
  • HTML attributes are not rewritten.
  • Raw-text elements are skipped: script, style, textarea, title.
  • Inside literal-priority ★/%% ranges, enabled markdown-it html_inline and entity rules remain authoritative: valid inline HTML is preserved, character references are decoded as usual, and other <, >, and & characters are escaped. Other Markdown delimiters in the range remain literal. Preserved inline HTML must be structurally self-contained inside the marker pair. If an opening or closing source tag would cross the generated comment boundary, conversion and deletion both fail closed for that pair.
  • With html:false, HTML-like input is treated as text and escaped, but marker/ruby transforms still apply. Explicit <ruby>...</ruby> wrappers used with ruby shorthand are preserved.

Block/inline example:

Input: <div>店内に★売り切れ注意の★張り紙が貼ってあります。</div>
Output: <div>店内に★売り切れ注意の★張り紙が貼ってあります。</div>

Input:
<div>

店内に★売り切れ注意の★張り紙が貼ってあります。

</div>

Output:
<div>
<p>店内に<span class="star-comment">★売り切れ注意の★</span>張り紙が貼ってあります。</p>
</div>
markdown-it Compatibility
  • Tested with markdown-it 14.3.0 and 15.0.0.
  • Ruby and comment conversion run as core transforms and may rewrite text tokens to html_inline; figure references are emitted by inline preparse.
  • If another plugin expects raw text only, run that plugin earlier or support html_inline.
  • Designed to coexist with text_join / cjk_breaks by forcing conversion at the tail of core processing.
  • Install this plugin once per markdown-it instance. Use a fresh instance for a different option set.

Install

npm i @peaceroad/markdown-it-renderer-inline-text

This package is ESM ("type": "module").