@readme/markdown 13.0.0 → 13.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -53102,6 +53102,59 @@ const isCalloutStructure = (node) => {
53102
53102
  const firstTextChild = firstChild.children?.[0];
53103
53103
  return firstTextChild?.type === 'text';
53104
53104
  };
53105
+ /**
53106
+ * Finds the first text node containing a newline in a paragraph's children.
53107
+ * Returns the index and the newline position within that text node.
53108
+ */
53109
+ const findNewlineInParagraph = (paragraph) => {
53110
+ for (let i = 0; i < paragraph.children.length; i += 1) {
53111
+ const child = paragraph.children[i];
53112
+ if (child.type === 'text' && typeof child.value === 'string') {
53113
+ const newlineIndex = child.value.indexOf('\n');
53114
+ if (newlineIndex !== -1) {
53115
+ return { index: i, newlineIndex };
53116
+ }
53117
+ }
53118
+ }
53119
+ return null;
53120
+ };
53121
+ /**
53122
+ * Splits a paragraph at the first newline, separating heading content (before \n)
53123
+ * from body content (after \n). Mutates the paragraph to contain only heading children.
53124
+ */
53125
+ const splitParagraphAtNewline = (paragraph) => {
53126
+ const splitPoint = findNewlineInParagraph(paragraph);
53127
+ if (!splitPoint)
53128
+ return null;
53129
+ const { index, newlineIndex } = splitPoint;
53130
+ const originalChildren = paragraph.children;
53131
+ const textNode = originalChildren[index];
53132
+ const beforeNewline = textNode.value.slice(0, newlineIndex);
53133
+ const afterNewline = textNode.value.slice(newlineIndex + 1);
53134
+ // Split paragraph: heading = children[0..index-1] + text before newline
53135
+ const headingChildren = originalChildren.slice(0, index);
53136
+ if (beforeNewline.length > 0 || headingChildren.length === 0) {
53137
+ headingChildren.push({ type: 'text', value: beforeNewline });
53138
+ }
53139
+ paragraph.children = headingChildren;
53140
+ // Body = text after newline + remaining children from original array
53141
+ const bodyChildren = [];
53142
+ if (afterNewline.length > 0) {
53143
+ bodyChildren.push({ type: 'text', value: afterNewline });
53144
+ }
53145
+ bodyChildren.push(...originalChildren.slice(index + 1));
53146
+ return bodyChildren.length > 0 ? bodyChildren : null;
53147
+ };
53148
+ /**
53149
+ * Removes the icon/match prefix from the first text node in a paragraph.
53150
+ * This is needed to clean up the raw AST after we've extracted the icon.
53151
+ */
53152
+ const removeIconPrefix = (paragraph, prefixLength) => {
53153
+ const firstTextNode = findFirst(paragraph);
53154
+ if (firstTextNode && 'value' in firstTextNode && typeof firstTextNode.value === 'string') {
53155
+ firstTextNode.value = firstTextNode.value.slice(prefixLength);
53156
+ }
53157
+ };
53105
53158
  const processBlockquote = (node, index, parent) => {
53106
53159
  if (!isCalloutStructure(node)) {
53107
53160
  // Only stringify empty blockquotes (no extractable text content)
@@ -53126,22 +53179,39 @@ const processBlockquote = (node, index, parent) => {
53126
53179
  const firstParagraph = node.children[0];
53127
53180
  const startText = lib_plain(firstParagraph).toString();
53128
53181
  const [match, icon] = startText.match(callouts_regex) || [];
53182
+ const firstParagraphOriginalEnd = firstParagraph.position.end;
53129
53183
  if (icon && match) {
53130
- const heading = startText.slice(match.length);
53131
- const empty = !heading.length && firstParagraph.children.length === 1;
53184
+ // Handle cases where heading and body are on the same line separated by a newline.
53185
+ // Example: "> ⚠️ **Bold heading**\nBody text here"
53186
+ const bodyChildren = splitParagraphAtNewline(firstParagraph);
53187
+ const didSplit = bodyChildren !== null;
53188
+ // Extract heading text after removing the icon prefix.
53189
+ // Use `plain()` to handle complex markdown structures (bold, inline code, etc.)
53190
+ const headingText = lib_plain(firstParagraph)
53191
+ .toString()
53192
+ .slice(match.length);
53193
+ // Clean up the raw AST by removing the icon prefix from the first text node
53194
+ removeIconPrefix(firstParagraph, match.length);
53195
+ const empty = !headingText.length && firstParagraph.children.length === 1;
53132
53196
  const theme = themes[icon] || 'default';
53133
- const firstChild = findFirst(node.children[0]);
53134
- if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
53135
- firstChild.value = firstChild.value.slice(match.length);
53136
- }
53137
- if (heading) {
53197
+ // Convert the first paragraph (first children of node) to a heading if it has content or was split
53198
+ if (headingText || didSplit) {
53138
53199
  node.children[0] = wrapHeading(node);
53139
- // @note: We add to the offset/column the length of the unicode
53140
- // character that was stripped off, so that the start position of the
53141
- // heading/text matches where it actually starts.
53200
+ // Adjust position to account for the stripped icon prefix
53142
53201
  node.children[0].position.start.offset += match.length;
53143
53202
  node.children[0].position.start.column += match.length;
53144
53203
  }
53204
+ // Insert body content as a separate paragraph after the heading
53205
+ if (bodyChildren) {
53206
+ node.children.splice(1, 0, {
53207
+ type: 'paragraph',
53208
+ children: bodyChildren,
53209
+ position: {
53210
+ start: node.children[0].position.end,
53211
+ end: firstParagraphOriginalEnd,
53212
+ },
53213
+ });
53214
+ }
53145
53215
  Object.assign(node, {
53146
53216
  type: NodeTypes.callout,
53147
53217
  data: {
@@ -86556,7 +86626,7 @@ const CUSTOM_PROP_BOUNDARIES = [
86556
86626
  /**
86557
86627
  * Tags that should be passed through and handled at runtime (not by the mdxish plugin)
86558
86628
  */
86559
- const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'rdme-pin']);
86629
+ const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'html-block', 'rdme-pin']);
86560
86630
  /**
86561
86631
  * Standard HTML tags that should never be treated as custom components.
86562
86632
  * Uses the html-tags package, converted to a Set<string> for efficient lookups.
@@ -93852,6 +93922,349 @@ const evaluateExpressions = ({ context = {} } = {}) => tree => {
93852
93922
  };
93853
93923
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
93854
93924
 
93925
+ ;// ./processor/transform/mdxish/normalize-malformed-md-syntax.ts
93926
+
93927
+ // Marker patterns for multi-node emphasis detection
93928
+ const MARKER_PATTERNS = [
93929
+ { isBold: true, marker: '**' },
93930
+ { isBold: true, marker: '__' },
93931
+ { isBold: false, marker: '*' },
93932
+ { isBold: false, marker: '_' },
93933
+ ];
93934
+ // Patterns to detect for bold (** and __) and italic (* and _) syntax:
93935
+ // Bold: ** text**, **text **, word** text**, ** text **
93936
+ // Italic: * text*, *text *, word* text*, * text *
93937
+ // Same patterns for underscore variants
93938
+ // We use separate patterns for each marker type to allow this flexibility.
93939
+ // Pattern for ** bold **
93940
+ // Groups: 1=wordBefore, 2=marker, 3=contentWithSpaceAfter, 4=trailingSpace1, 5=contentWithSpaceBefore, 6=trailingSpace2, 7=afterChar
93941
+ // trailingSpace1 is for "** text **" pattern, trailingSpace2 is for "**text **" pattern
93942
+ const asteriskBoldRegex = /([^*\s]+)?\s*(\*\*)(?:\s+((?:[^*\n]|\*(?!\*))+?)(\s*)\2|((?:[^*\n]|\*(?!\*))+?)(\s+)\2)(\S|$)?/g;
93943
+ // Pattern for __ bold __
93944
+ const underscoreBoldRegex = /([^_\s]+)?\s*(__)(?:\s+((?:[^_\n]|_(?!_))+?)(\s*)\2|((?:[^_\n]|_(?!_))+?)(\s+)\2)(\S|$)?/g;
93945
+ // Pattern for * italic *
93946
+ const asteriskItalicRegex = /([^*\s]+)?\s*(\*)(?!\*)(?:\s+([^*\n]+?)(\s*)\2|([^*\n]+?)(\s+)\2)(\S|$)?/g;
93947
+ // Pattern for _ italic _
93948
+ const underscoreItalicRegex = /([^_\s]+)?\s*(_)(?!_)(?:\s+([^_\n]+?)(\s*)\2|([^_\n]+?)(\s+)\2)(\S|$)?/g;
93949
+ // CommonMark ignores intraword underscores or asteriks, but we want to italicize/bold the inner part
93950
+ // Pattern for intraword _word_ in words like hello_world_
93951
+ const intrawordUnderscoreItalicRegex = /(\w)_(?!_)([a-zA-Z0-9]+)_(?![\w_])/g;
93952
+ // Pattern for intraword __word__ in words like hello__world__
93953
+ const intrawordUnderscoreBoldRegex = /(\w)__([a-zA-Z0-9]+)__(?![\w_])/g;
93954
+ // Pattern for intraword *word* in words like hello*world*
93955
+ const intrawordAsteriskItalicRegex = /(\w)\*(?!\*)([a-zA-Z0-9]+)\*(?![\w*])/g;
93956
+ // Pattern for intraword **word** in words like hello**world**
93957
+ const intrawordAsteriskBoldRegex = /(\w)\*\*([a-zA-Z0-9]+)\*\*(?![\w*])/g;
93958
+ /**
93959
+ * Finds opening emphasis marker in a text value.
93960
+ * Returns marker info if found, null otherwise.
93961
+ */
93962
+ function findOpeningMarker(text) {
93963
+ const results = MARKER_PATTERNS.map(({ isBold, marker }) => {
93964
+ if (marker === '*' && text.startsWith('**'))
93965
+ return null;
93966
+ if (marker === '_' && text.startsWith('__'))
93967
+ return null;
93968
+ if (text.startsWith(marker) && text.length > marker.length) {
93969
+ return { isBold, marker, textAfter: text.slice(marker.length), textBefore: '' };
93970
+ }
93971
+ const idx = text.indexOf(marker);
93972
+ if (idx > 0 && !/\s/.test(text[idx - 1])) {
93973
+ if (marker === '*' && text.slice(idx).startsWith('**'))
93974
+ return null;
93975
+ if (marker === '_' && text.slice(idx).startsWith('__'))
93976
+ return null;
93977
+ const after = text.slice(idx + marker.length);
93978
+ if (after.length > 0) {
93979
+ return { isBold, marker, textAfter: after, textBefore: text.slice(0, idx) };
93980
+ }
93981
+ }
93982
+ return null;
93983
+ });
93984
+ return results.find(r => r !== null) ?? null;
93985
+ }
93986
+ /**
93987
+ * Finds the end/closing marker in a text node for multi-node emphasis.
93988
+ */
93989
+ function findEndMarker(text, marker) {
93990
+ const spacePattern = ` ${marker}`;
93991
+ const spaceIdx = text.indexOf(spacePattern);
93992
+ if (spaceIdx >= 0) {
93993
+ if (marker === '*' && text.slice(spaceIdx + 1).startsWith('**'))
93994
+ return null;
93995
+ if (marker === '_' && text.slice(spaceIdx + 1).startsWith('__'))
93996
+ return null;
93997
+ return {
93998
+ textAfter: text.slice(spaceIdx + spacePattern.length),
93999
+ textBefore: text.slice(0, spaceIdx),
94000
+ };
94001
+ }
94002
+ if (text.startsWith(marker)) {
94003
+ if (marker === '*' && text.startsWith('**'))
94004
+ return null;
94005
+ if (marker === '_' && text.startsWith('__'))
94006
+ return null;
94007
+ return {
94008
+ textAfter: text.slice(marker.length),
94009
+ textBefore: '',
94010
+ };
94011
+ }
94012
+ return null;
94013
+ }
94014
+ /**
94015
+ * Scan children for an opening emphasis marker in a text node.
94016
+ */
94017
+ function findOpeningInChildren(children) {
94018
+ let result = null;
94019
+ children.some((child, idx) => {
94020
+ if (child.type !== 'text')
94021
+ return false;
94022
+ const found = findOpeningMarker(child.value);
94023
+ if (found) {
94024
+ result = { idx, opening: found };
94025
+ return true;
94026
+ }
94027
+ return false;
94028
+ });
94029
+ return result;
94030
+ }
94031
+ /**
94032
+ * Scan children (after openingIdx) for a closing emphasis marker.
94033
+ */
94034
+ function findClosingInChildren(children, openingIdx, marker) {
94035
+ let result = null;
94036
+ children.slice(openingIdx + 1).some((child, relativeIdx) => {
94037
+ if (child.type !== 'text')
94038
+ return false;
94039
+ const found = findEndMarker(child.value, marker);
94040
+ if (found) {
94041
+ result = { closingIdx: openingIdx + 1 + relativeIdx, closing: found };
94042
+ return true;
94043
+ }
94044
+ return false;
94045
+ });
94046
+ return result;
94047
+ }
94048
+ /**
94049
+ * Build the replacement nodes for a matched emphasis pair.
94050
+ */
94051
+ function buildReplacementNodes(container, { opening, openingIdx, closing, closingIdx }) {
94052
+ const newNodes = [];
94053
+ if (opening.textBefore) {
94054
+ newNodes.push({ type: 'text', value: `${opening.textBefore} ` });
94055
+ }
94056
+ const emphasisChildren = [];
94057
+ const openingText = opening.textAfter.replace(/^\s+/, '');
94058
+ if (openingText) {
94059
+ emphasisChildren.push({ type: 'text', value: openingText });
94060
+ }
94061
+ container.children.slice(openingIdx + 1, closingIdx).forEach(child => {
94062
+ emphasisChildren.push(child);
94063
+ });
94064
+ const closingText = closing.textBefore.replace(/\s+$/, '');
94065
+ if (closingText) {
94066
+ emphasisChildren.push({ type: 'text', value: closingText });
94067
+ }
94068
+ if (emphasisChildren.length > 0) {
94069
+ const emphasisNode = opening.isBold
94070
+ ? { type: 'strong', children: emphasisChildren }
94071
+ : { type: 'emphasis', children: emphasisChildren };
94072
+ newNodes.push(emphasisNode);
94073
+ }
94074
+ if (closing.textAfter) {
94075
+ newNodes.push({ type: 'text', value: closing.textAfter });
94076
+ }
94077
+ return newNodes;
94078
+ }
94079
+ /**
94080
+ * Find and transform one multi-node emphasis pair in the container.
94081
+ * Returns true if a pair was found and transformed, false otherwise.
94082
+ */
94083
+ function processOneEmphasisPair(container) {
94084
+ const openingResult = findOpeningInChildren(container.children);
94085
+ if (!openingResult)
94086
+ return false;
94087
+ const { idx: openingIdx, opening } = openingResult;
94088
+ const closingResult = findClosingInChildren(container.children, openingIdx, opening.marker);
94089
+ if (!closingResult)
94090
+ return false;
94091
+ const { closingIdx, closing } = closingResult;
94092
+ const newNodes = buildReplacementNodes(container, { opening, openingIdx, closing, closingIdx });
94093
+ const deleteCount = closingIdx - openingIdx + 1;
94094
+ container.children.splice(openingIdx, deleteCount, ...newNodes);
94095
+ return true;
94096
+ }
94097
+ /**
94098
+ * Handle malformed emphasis that spans multiple AST nodes.
94099
+ * E.g., "**bold [link](url)**" where markers are in different text nodes.
94100
+ */
94101
+ function visitMultiNodeEmphasis(tree) {
94102
+ const containerTypes = ['paragraph', 'heading', 'tableCell', 'listItem', 'blockquote'];
94103
+ visit(tree, node => {
94104
+ if (!containerTypes.includes(node.type))
94105
+ return;
94106
+ if (!('children' in node) || !Array.isArray(node.children))
94107
+ return;
94108
+ const container = node;
94109
+ let foundPair = true;
94110
+ while (foundPair) {
94111
+ foundPair = processOneEmphasisPair(container);
94112
+ }
94113
+ });
94114
+ }
94115
+ /**
94116
+ * A remark plugin that normalizes malformed bold and italic markers in text nodes.
94117
+ * Detects patterns like `** bold**`, `Hello** Wrong Bold**`, `__ bold__`, `Hello__ Wrong Bold__`,
94118
+ * `* italic*`, `Hello* Wrong Italic*`, `_ italic_`, or `Hello_ Wrong Italic_`
94119
+ * and converts them to proper strong/emphasis nodes, matching the behavior of the legacy rdmd engine.
94120
+ *
94121
+ * Supports both asterisk (`**bold**`, `*italic*`) and underscore (`__bold__`, `_italic_`) syntax.
94122
+ * Also supports snake_case content like `** some_snake_case**`.
94123
+ *
94124
+ * This runs after remark-parse, which (in v11+) is strict and doesn't parse
94125
+ * malformed emphasis syntax. This plugin post-processes the AST to handle these cases.
94126
+ */
94127
+ const normalizeEmphasisAST = () => (tree) => {
94128
+ visit(tree, 'text', function visitor(node, index, parent) {
94129
+ if (index === undefined || !parent)
94130
+ return undefined;
94131
+ // Skip if inside code blocks or inline code
94132
+ if (parent.type === 'inlineCode' || parent.type === 'code') {
94133
+ return undefined;
94134
+ }
94135
+ const text = node.value;
94136
+ const allMatches = [];
94137
+ [...text.matchAll(asteriskBoldRegex)].forEach(match => {
94138
+ allMatches.push({ isBold: true, marker: '**', match });
94139
+ });
94140
+ [...text.matchAll(underscoreBoldRegex)].forEach(match => {
94141
+ allMatches.push({ isBold: true, marker: '__', match });
94142
+ });
94143
+ [...text.matchAll(asteriskItalicRegex)].forEach(match => {
94144
+ allMatches.push({ isBold: false, marker: '*', match });
94145
+ });
94146
+ [...text.matchAll(underscoreItalicRegex)].forEach(match => {
94147
+ allMatches.push({ isBold: false, marker: '_', match });
94148
+ });
94149
+ [...text.matchAll(intrawordUnderscoreItalicRegex)].forEach(match => {
94150
+ allMatches.push({ isBold: false, isIntraword: true, marker: '_', match });
94151
+ });
94152
+ [...text.matchAll(intrawordUnderscoreBoldRegex)].forEach(match => {
94153
+ allMatches.push({ isBold: true, isIntraword: true, marker: '__', match });
94154
+ });
94155
+ [...text.matchAll(intrawordAsteriskItalicRegex)].forEach(match => {
94156
+ allMatches.push({ isBold: false, isIntraword: true, marker: '*', match });
94157
+ });
94158
+ [...text.matchAll(intrawordAsteriskBoldRegex)].forEach(match => {
94159
+ allMatches.push({ isBold: true, isIntraword: true, marker: '**', match });
94160
+ });
94161
+ if (allMatches.length === 0)
94162
+ return undefined;
94163
+ allMatches.sort((a, b) => (a.match.index ?? 0) - (b.match.index ?? 0));
94164
+ const filteredMatches = [];
94165
+ let lastEnd = 0;
94166
+ allMatches.forEach(info => {
94167
+ const start = info.match.index ?? 0;
94168
+ const end = start + info.match[0].length;
94169
+ if (start >= lastEnd) {
94170
+ filteredMatches.push(info);
94171
+ lastEnd = end;
94172
+ }
94173
+ });
94174
+ if (filteredMatches.length === 0)
94175
+ return undefined;
94176
+ const parts = [];
94177
+ let lastIndex = 0;
94178
+ filteredMatches.forEach(({ isBold, isIntraword, marker, match }) => {
94179
+ const matchIndex = match.index ?? 0;
94180
+ const fullMatch = match[0];
94181
+ if (isIntraword) {
94182
+ // handles cases like hello_world_ where we only want to italicize 'world'
94183
+ const charBefore = match[1] || ''; // e.g., "l" in "hello_world_"
94184
+ const content = match[2]; // e.g., "world"
94185
+ const combinedBefore = text.slice(lastIndex, matchIndex) + charBefore;
94186
+ if (combinedBefore) {
94187
+ parts.push({ type: 'text', value: combinedBefore });
94188
+ }
94189
+ if (isBold) {
94190
+ parts.push({
94191
+ type: 'strong',
94192
+ children: [{ type: 'text', value: content }],
94193
+ });
94194
+ }
94195
+ else {
94196
+ parts.push({
94197
+ type: 'emphasis',
94198
+ children: [{ type: 'text', value: content }],
94199
+ });
94200
+ }
94201
+ lastIndex = matchIndex + fullMatch.length;
94202
+ return;
94203
+ }
94204
+ if (matchIndex > lastIndex) {
94205
+ const beforeText = text.slice(lastIndex, matchIndex);
94206
+ if (beforeText) {
94207
+ parts.push({ type: 'text', value: beforeText });
94208
+ }
94209
+ }
94210
+ const wordBefore = match[1]; // e.g., "Hello" in "Hello** Wrong Bold**"
94211
+ const contentWithSpaceAfter = match[3]; // Content when there's a space after opening markers
94212
+ const trailingSpace1 = match[4] || ''; // Space before closing markers (for "** text **" pattern)
94213
+ const contentWithSpaceBefore = match[5]; // Content when there's only a space before closing markers
94214
+ const trailingSpace2 = match[6] || ''; // Space before closing markers (for "**text **" pattern)
94215
+ const trailingSpace = trailingSpace1 || trailingSpace2; // Combined trailing space
94216
+ const content = (contentWithSpaceAfter || contentWithSpaceBefore || '').trim();
94217
+ const afterChar = match[7]; // Character after closing markers (if any)
94218
+ const markerPos = fullMatch.indexOf(marker);
94219
+ const spacesBeforeMarkers = wordBefore
94220
+ ? fullMatch.slice(wordBefore.length, markerPos)
94221
+ : fullMatch.slice(0, markerPos);
94222
+ const shouldAddSpace = !!contentWithSpaceAfter && !!wordBefore && !spacesBeforeMarkers;
94223
+ if (wordBefore) {
94224
+ const spacing = spacesBeforeMarkers + (shouldAddSpace ? ' ' : '');
94225
+ parts.push({ type: 'text', value: wordBefore + spacing });
94226
+ }
94227
+ else if (spacesBeforeMarkers) {
94228
+ parts.push({ type: 'text', value: spacesBeforeMarkers });
94229
+ }
94230
+ if (content) {
94231
+ if (isBold) {
94232
+ parts.push({
94233
+ type: 'strong',
94234
+ children: [{ type: 'text', value: content }],
94235
+ });
94236
+ }
94237
+ else {
94238
+ parts.push({
94239
+ type: 'emphasis',
94240
+ children: [{ type: 'text', value: content }],
94241
+ });
94242
+ }
94243
+ }
94244
+ if (afterChar) {
94245
+ const prefix = trailingSpace ? ' ' : '';
94246
+ parts.push({ type: 'text', value: prefix + afterChar });
94247
+ }
94248
+ lastIndex = matchIndex + fullMatch.length;
94249
+ });
94250
+ if (lastIndex < text.length) {
94251
+ const remainingText = text.slice(lastIndex);
94252
+ if (remainingText) {
94253
+ parts.push({ type: 'text', value: remainingText });
94254
+ }
94255
+ }
94256
+ if (parts.length > 0) {
94257
+ parent.children.splice(index, 1, ...parts);
94258
+ return [SKIP, index + parts.length];
94259
+ }
94260
+ return undefined;
94261
+ });
94262
+ // Handle malformed emphasis spanning multiple nodes (e.g., **text [link](url) **)
94263
+ visitMultiNodeEmphasis(tree);
94264
+ return tree;
94265
+ };
94266
+ /* harmony default export */ const normalize_malformed_md_syntax = (normalizeEmphasisAST);
94267
+
93855
94268
  ;// ./processor/transform/mdxish/magic-blocks/placeholder.ts
93856
94269
  const EMPTY_IMAGE_PLACEHOLDER = {
93857
94270
  type: 'image',
@@ -93904,6 +94317,7 @@ const EMPTY_CODE_PLACEHOLDER = {
93904
94317
 
93905
94318
 
93906
94319
 
94320
+
93907
94321
  /**
93908
94322
  * Wraps a node in a "pinned" container if sidebar: true is set.
93909
94323
  */
@@ -93931,7 +94345,8 @@ const imgWidthBySize = new Proxy(imgSizeValues, {
93931
94345
  });
93932
94346
  const textToInline = (text) => [{ type: 'text', value: text }];
93933
94347
  const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
93934
- const contentParser = unified().use(remarkParse).use(remarkGfm);
94348
+ /** Parses markdown and html to markdown nodes */
94349
+ const contentParser = unified().use(remarkParse).use(remarkGfm).use(normalize_malformed_md_syntax);
93935
94350
  const parseTableCell = (text) => {
93936
94351
  if (!text.trim())
93937
94352
  return [{ type: 'text', value: '' }];
@@ -94969,139 +95384,6 @@ function restoreSnakeCase(placeholderName, mapping) {
94969
95384
  return matchingKey ? mapping[matchingKey] : placeholderName;
94970
95385
  }
94971
95386
 
94972
- ;// ./processor/transform/mdxish/normalize-malformed-md-syntax.ts
94973
-
94974
- // Patterns to detect for bold (** and __) and italic (* and _) syntax:
94975
- // Bold: ** text**, **text **, word** text**, ** text **
94976
- // Italic: * text*, *text *, word* text*, * text *
94977
- // Same patterns for underscore variants
94978
- // We use separate patterns for each marker type to allow this flexibility.
94979
- // Pattern for ** bold **
94980
- // Groups: 1=wordBefore, 2=marker, 3=contentWithSpaceAfter, 4=trailingSpace1, 5=contentWithSpaceBefore, 6=trailingSpace2, 7=afterChar
94981
- // trailingSpace1 is for "** text **" pattern, trailingSpace2 is for "**text **" pattern
94982
- const asteriskBoldRegex = /([^*\s]+)?\s*(\*\*)(?:\s+((?:[^*\n]|\*(?!\*))+?)(\s*)\2|((?:[^*\n]|\*(?!\*))+?)(\s+)\2)(\S|$)?/g;
94983
- // Pattern for __ bold __
94984
- const underscoreBoldRegex = /([^_\s]+)?\s*(__)(?:\s+((?:[^_\n]|_(?!_))+?)(\s*)\2|((?:[^_\n]|_(?!_))+?)(\s+)\2)(\S|$)?/g;
94985
- // Pattern for * italic *
94986
- const asteriskItalicRegex = /([^*\s]+)?\s*(\*)(?!\*)(?:\s+([^*\n]+?)(\s*)\2|([^*\n]+?)(\s+)\2)(\S|$)?/g;
94987
- // Pattern for _ italic _
94988
- const underscoreItalicRegex = /([^_\s]+)?\s*(_)(?!_)(?:\s+([^_\n]+?)(\s*)\2|([^_\n]+?)(\s+)\2)(\S|$)?/g;
94989
- /**
94990
- * A remark plugin that normalizes malformed bold and italic markers in text nodes.
94991
- * Detects patterns like `** bold**`, `Hello** Wrong Bold**`, `__ bold__`, `Hello__ Wrong Bold__`,
94992
- * `* italic*`, `Hello* Wrong Italic*`, `_ italic_`, or `Hello_ Wrong Italic_`
94993
- * and converts them to proper strong/emphasis nodes, matching the behavior of the legacy rdmd engine.
94994
- *
94995
- * Supports both asterisk (`**bold**`, `*italic*`) and underscore (`__bold__`, `_italic_`) syntax.
94996
- * Also supports snake_case content like `** some_snake_case**`.
94997
- *
94998
- * This runs after remark-parse, which (in v11+) is strict and doesn't parse
94999
- * malformed emphasis syntax. This plugin post-processes the AST to handle these cases.
95000
- */
95001
- const normalizeEmphasisAST = () => (tree) => {
95002
- visit(tree, 'text', function visitor(node, index, parent) {
95003
- if (index === undefined || !parent)
95004
- return undefined;
95005
- // Skip if inside code blocks or inline code
95006
- if (parent.type === 'inlineCode' || parent.type === 'code') {
95007
- return undefined;
95008
- }
95009
- const text = node.value;
95010
- const allMatches = [];
95011
- [...text.matchAll(asteriskBoldRegex)].forEach(match => {
95012
- allMatches.push({ isBold: true, marker: '**', match });
95013
- });
95014
- [...text.matchAll(underscoreBoldRegex)].forEach(match => {
95015
- allMatches.push({ isBold: true, marker: '__', match });
95016
- });
95017
- [...text.matchAll(asteriskItalicRegex)].forEach(match => {
95018
- allMatches.push({ isBold: false, marker: '*', match });
95019
- });
95020
- [...text.matchAll(underscoreItalicRegex)].forEach(match => {
95021
- allMatches.push({ isBold: false, marker: '_', match });
95022
- });
95023
- if (allMatches.length === 0)
95024
- return undefined;
95025
- allMatches.sort((a, b) => (a.match.index ?? 0) - (b.match.index ?? 0));
95026
- const filteredMatches = [];
95027
- let lastEnd = 0;
95028
- allMatches.forEach(info => {
95029
- const start = info.match.index ?? 0;
95030
- const end = start + info.match[0].length;
95031
- if (start >= lastEnd) {
95032
- filteredMatches.push(info);
95033
- lastEnd = end;
95034
- }
95035
- });
95036
- if (filteredMatches.length === 0)
95037
- return undefined;
95038
- const parts = [];
95039
- let lastIndex = 0;
95040
- filteredMatches.forEach(({ match, marker, isBold }) => {
95041
- const matchIndex = match.index ?? 0;
95042
- const fullMatch = match[0];
95043
- if (matchIndex > lastIndex) {
95044
- const beforeText = text.slice(lastIndex, matchIndex);
95045
- if (beforeText) {
95046
- parts.push({ type: 'text', value: beforeText });
95047
- }
95048
- }
95049
- const wordBefore = match[1]; // e.g., "Hello" in "Hello** Wrong Bold**"
95050
- const contentWithSpaceAfter = match[3]; // Content when there's a space after opening markers
95051
- const trailingSpace1 = match[4] || ''; // Space before closing markers (for "** text **" pattern)
95052
- const contentWithSpaceBefore = match[5]; // Content when there's only a space before closing markers
95053
- const trailingSpace2 = match[6] || ''; // Space before closing markers (for "**text **" pattern)
95054
- const trailingSpace = trailingSpace1 || trailingSpace2; // Combined trailing space
95055
- const content = (contentWithSpaceAfter || contentWithSpaceBefore || '').trim();
95056
- const afterChar = match[7]; // Character after closing markers (if any)
95057
- const markerPos = fullMatch.indexOf(marker);
95058
- const spacesBeforeMarkers = wordBefore
95059
- ? fullMatch.slice(wordBefore.length, markerPos)
95060
- : fullMatch.slice(0, markerPos);
95061
- const shouldAddSpace = !!contentWithSpaceAfter && !!wordBefore && !spacesBeforeMarkers;
95062
- if (wordBefore) {
95063
- const spacing = spacesBeforeMarkers + (shouldAddSpace ? ' ' : '');
95064
- parts.push({ type: 'text', value: wordBefore + spacing });
95065
- }
95066
- else if (spacesBeforeMarkers) {
95067
- parts.push({ type: 'text', value: spacesBeforeMarkers });
95068
- }
95069
- if (content) {
95070
- if (isBold) {
95071
- parts.push({
95072
- type: 'strong',
95073
- children: [{ type: 'text', value: content }],
95074
- });
95075
- }
95076
- else {
95077
- parts.push({
95078
- type: 'emphasis',
95079
- children: [{ type: 'text', value: content }],
95080
- });
95081
- }
95082
- }
95083
- if (afterChar) {
95084
- const prefix = trailingSpace ? ' ' : '';
95085
- parts.push({ type: 'text', value: prefix + afterChar });
95086
- }
95087
- lastIndex = matchIndex + fullMatch.length;
95088
- });
95089
- if (lastIndex < text.length) {
95090
- const remainingText = text.slice(lastIndex);
95091
- if (remainingText) {
95092
- parts.push({ type: 'text', value: remainingText });
95093
- }
95094
- }
95095
- if (parts.length > 0) {
95096
- parent.children.splice(index, 1, ...parts);
95097
- return [SKIP, index + parts.length];
95098
- }
95099
- return undefined;
95100
- });
95101
- return tree;
95102
- };
95103
- /* harmony default export */ const normalize_malformed_md_syntax = (normalizeEmphasisAST);
95104
-
95105
95387
  ;// ./processor/transform/mdxish/normalize-table-separator.ts
95106
95388
  /**
95107
95389
  * Preprocessor to normalize malformed GFM table separator syntax.
@@ -96573,7 +96855,7 @@ function loadComponents() {
96573
96855
 
96574
96856
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
96575
96857
  function mdxishAstProcessor(mdContent, opts = {}) {
96576
- const { components: userComponents = {}, jsxContext = {}, newEditorTypes = false, useTailwind } = opts;
96858
+ const { components: userComponents = {}, jsxContext = {}, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
96577
96859
  const components = {
96578
96860
  ...loadComponents(),
96579
96861
  ...userComponents,
@@ -96584,7 +96866,9 @@ function mdxishAstProcessor(mdContent, opts = {}) {
96584
96866
  // Step 1: Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
96585
96867
  const contentAfterTableNormalization = normalizeTableSeparator(mdContent);
96586
96868
  // Step 2: Evaluate JSX expressions in attributes
96587
- const contentAfterJSXEvaluation = preprocessJSXExpressions(contentAfterTableNormalization, jsxContext);
96869
+ const contentAfterJSXEvaluation = safeMode
96870
+ ? contentAfterTableNormalization
96871
+ : preprocessJSXExpressions(contentAfterTableNormalization, jsxContext);
96588
96872
  // Step 3: Replace snake_case component names with parser-safe placeholders
96589
96873
  const { content: parserReadyContent, mapping: snakeCaseMapping } = processSnakeCaseComponent(contentAfterJSXEvaluation, { knownComponents });
96590
96874
  // Create string map for tailwind transformer
@@ -96599,8 +96883,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
96599
96883
  text: mdxExprExt.text,
96600
96884
  };
96601
96885
  const processor = unified()
96602
- .data('micromarkExtensions', [magicBlock(), mdxExprTextOnly])
96603
- .data('fromMarkdownExtensions', [magicBlockFromMarkdown(), mdxExpressionFromMarkdown()])
96886
+ .data('micromarkExtensions', safeMode ? [magicBlock()] : [magicBlock(), mdxExprTextOnly])
96887
+ .data('fromMarkdownExtensions', safeMode ? [magicBlockFromMarkdown()] : [magicBlockFromMarkdown(), mdxExpressionFromMarkdown()])
96604
96888
  .use(remarkParse)
96605
96889
  .use(remarkFrontmatter)
96606
96890
  .use(normalize_malformed_md_syntax)
@@ -96612,8 +96896,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
96612
96896
  .use(mdxish_tables)
96613
96897
  .use(mdxish_html_blocks)
96614
96898
  .use(newEditorTypes ? mdxish_jsx_to_mdast : undefined) // Convert JSX elements to MDAST types
96615
- .use(evaluate_expressions, { context: jsxContext }) // Evaluate MDX expressions using jsxContext
96616
- .use(variables_text) // Parse {user.*} patterns from text (can't rely on remarkMdx)
96899
+ .use(safeMode ? undefined : evaluate_expressions, { context: jsxContext }) // Evaluate MDX expressions using jsxContext
96900
+ .use(variables_text) // Parse {user.*} patterns from text
96617
96901
  .use(useTailwind ? transform_tailwind : undefined, { components: tempComponentsMap })
96618
96902
  .use(remarkGfm);
96619
96903
  return {
@@ -97234,6 +97518,7 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
97234
97518
 
97235
97519
 
97236
97520
 
97521
+
97237
97522
  ;// ./index.tsx
97238
97523
 
97239
97524