@mintlify/common 1.0.1331 → 1.0.1332

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.
@@ -1,7 +1,7 @@
1
1
  import { getAST, stringifyTree } from '../../remark-core.js';
2
2
  import { BLOCK_LINK_LABEL, BLOCK_MARKERS } from './firstPartyMarkdownComponents.js';
3
3
  import { evaluateStatic } from './staticEvaluator.js';
4
- import { chargeUnfoldBudget, chargeUnfoldCall, collectMarkdownComponentContext, collectUsageProps, containsJsx, createUnfoldBudget, definitionGatesContent, DROP_ELEMENTS, FORM_CONTROL_ELEMENTS, dropPairedDarkOnly, escapeInterpolatedText, HEADING_ELEMENTS, isPresentationalAttribute, UNWRAP_BLOCK_ELEMENTS, UNWRAP_INLINE_ELEMENTS, escapeLinkLabel, extractProgramExpression, FAILED_SPREAD, markdownLinkDestination, MAX_UNFOLD_DEPTH, GFM_ALERT_PATTERN, HEADLINE_ATTRIBUTES, KEEP_TAG_COMPONENTS, materializeComponent, serializeExpressionJsx, } from './unfoldComponentsForMarkdown.js';
4
+ import { chargeUnfoldBudget, chargeUnfoldCall, collectMarkdownComponentContext, collectUsageProps, containsJsx, createUnfoldBudget, definitionGatesContent, DROP_ELEMENTS, FORM_CONTROL_ELEMENTS, dropPairedDarkOnly, escapeInterpolatedText, HEADING_ELEMENTS, isPresentationalAttribute, UNWRAP_BLOCK_ELEMENTS, UNWRAP_INLINE_ELEMENTS, escapeLinkLabel, extractProgramExpression, FAILED_SPREAD, fillJsxPropSlots, markdownLinkDestination, MAX_UNFOLD_DEPTH, GFM_ALERT_PATTERN, HEADLINE_ATTRIBUTES, KEEP_TAG_COMPONENTS, materializeComponent, serializeExpressionJsx, slotJsxUsageProps, } from './unfoldComponentsForMarkdown.js';
5
5
  import { toVisibilityGroups, toVisibilityGroupsRule, } from './visibility-auth-rule.js';
6
6
  export function isVisibilityElement(node) {
7
7
  return ((node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') &&
@@ -401,17 +401,22 @@ function unfoldUsage(node, name, ctx) {
401
401
  props.delete('children');
402
402
  failedProps.delete('children');
403
403
  }
404
+ const slots = slotJsxUsageProps(node, ctx, props, failedProps);
404
405
  const trustedFailed = new Set();
405
406
  const trustedProps = collectUsageProps(node, Object.assign(Object.assign({}, ctx), { moduleScope: ctx.literalScope }), trustedFailed);
406
407
  if (bodyChildren.length > 0)
407
408
  trustedProps.delete('children');
408
- const markdown = materializeComponent(def, name, props, childrenMarkdown, ctx, failedProps, bodyExpressionValue(bodyChildren, ctx), { props: trustedProps, failedProps: trustedFailed });
409
- if (markdown === undefined) {
409
+ const materialized = materializeComponent(def, name, props, childrenMarkdown, ctx, failedProps, bodyExpressionValue(bodyChildren, ctx), { props: trustedProps, failedProps: trustedFailed });
410
+ if (materialized === undefined) {
410
411
  if (definitionGatesContent(def, ctx.defs))
411
412
  return [];
412
413
  // Same fallback as inside definitions: headline and description props still render
413
414
  return ((_a = unfoldForeignUsage(node, name, ctx, transformedChildren)) !== null && _a !== void 0 ? _a : wrapPhrasingRuns(transformedChildren));
414
415
  }
416
+ const markdown = fillJsxPropSlots(materialized, slots, ctx.budget);
417
+ if (markdown === undefined) {
418
+ return definitionGatesContent(def, ctx.defs) ? [] : wrapPhrasingRuns(transformedChildren);
419
+ }
415
420
  if (markdown.trim() === '')
416
421
  return [];
417
422
  if (!chargeUnfoldBudget(ctx, markdown.length)) {
@@ -59,6 +59,9 @@ export declare function escapeInterpolatedText(value: string): string;
59
59
  export declare function markdownLinkDestination(url: string): string;
60
60
  /** Escapes brackets outside code spans and images, so a label cannot end its link early. */
61
61
  export declare function escapeLinkLabel(text: string): string;
62
+ export type JsxPropSlots = Map<string, string>;
63
+ export declare function slotJsxUsageProps(node: MdxUsageElement, ctx: UnfoldContext, props: StaticScope, failed: Set<string>): JsxPropSlots;
64
+ export declare function fillJsxPropSlots(markdown: string, slots: JsxPropSlots, budget: UnfoldBudget): string | undefined;
62
65
  export declare function materializeComponent(def: ComponentDef, name: string, props: StaticScope, childrenMarkdown: string, ctx: UnfoldContext, failedProps?: ReadonlySet<string>, bodyValue?: {
63
66
  value: StaticValue;
64
67
  }, trusted?: TrustedProps): string | undefined;
@@ -1029,6 +1029,7 @@ function serializeComponentElement(node, name, ctx) {
1029
1029
  if (withinLimits) {
1030
1030
  const failedProps = new Set();
1031
1031
  const props = collectStaticProps(node, ctx, failedProps);
1032
+ const slots = slotJsxStaticProps(node, ctx, props, failedProps);
1032
1033
  let childrenMarkdown = serializeJsxChildren(node.children, ctx).trim();
1033
1034
  let bodyValue;
1034
1035
  // Body children replace any children attribute or spread, even when they render nothing
@@ -1043,9 +1044,10 @@ function serializeComponentElement(node, name, ctx) {
1043
1044
  }
1044
1045
  const trustedFailed = new Set();
1045
1046
  const trustedProps = collectStaticProps(node, Object.assign(Object.assign({}, ctx), trustedEnv(ctx)), trustedFailed);
1046
- const markdown = materializeComponent(def, name, props, childrenMarkdown, ctx, failedProps, bodyValue, { props: trustedProps, failedProps: trustedFailed });
1047
- if (markdown !== undefined) {
1048
- if (chargeUnfoldBudget(ctx, markdown.length))
1047
+ const materialized = materializeComponent(def, name, props, childrenMarkdown, ctx, failedProps, bodyValue, { props: trustedProps, failedProps: trustedFailed });
1048
+ if (materialized !== undefined) {
1049
+ const markdown = fillJsxPropSlots(materialized, slots, ctx.budget);
1050
+ if (markdown !== undefined && chargeUnfoldBudget(ctx, markdown.length))
1049
1051
  return markdown;
1050
1052
  return definitionGatesContent(def, ctx.defs) ? '' : childrenMarkdown;
1051
1053
  }
@@ -1060,6 +1062,72 @@ function setKnownProp(props, failed, name, value) {
1060
1062
  if (name !== FAILED_SPREAD)
1061
1063
  failed === null || failed === void 0 ? void 0 : failed.delete(name);
1062
1064
  }
1065
+ let nextJsxPropSlot = 0;
1066
+ function jsxValueExpression(expression) {
1067
+ let value = expression;
1068
+ while (isObject(value) &&
1069
+ typeof value.type === 'string' &&
1070
+ TRANSPARENT_WRAPPERS.has(value.type)) {
1071
+ value = value.expression;
1072
+ }
1073
+ return isObject(value) && (value.type === 'JSXElement' || value.type === 'JSXFragment')
1074
+ ? value
1075
+ : undefined;
1076
+ }
1077
+ function slotJsxProp(props, failed, name, markdown, slots) {
1078
+ const placeholder = `\uE000${nextJsxPropSlot++}\uE000`;
1079
+ slots.set(placeholder, markdown);
1080
+ setKnownProp(props, failed, name, placeholder);
1081
+ }
1082
+ export function slotJsxUsageProps(node, ctx, props, failed) {
1083
+ var _a;
1084
+ const slots = new Map();
1085
+ for (const attribute of node.attributes) {
1086
+ if (attribute.type !== 'mdxJsxAttribute' || !failed.has(attribute.name))
1087
+ continue;
1088
+ if (attribute.value === null || typeof attribute.value !== 'object')
1089
+ continue;
1090
+ const expression = jsxValueExpression(extractProgramExpression((_a = attribute.value.data) === null || _a === void 0 ? void 0 : _a.estree));
1091
+ if (expression === undefined)
1092
+ continue;
1093
+ const markdown = serializeExpressionJsx(expression, ctx);
1094
+ if (markdown !== undefined)
1095
+ slotJsxProp(props, failed, attribute.name, markdown.trim(), slots);
1096
+ }
1097
+ return slots;
1098
+ }
1099
+ function slotJsxStaticProps(node, ctx, props, failed) {
1100
+ const slots = new Map();
1101
+ for (const attribute of jsxAttributes(node)) {
1102
+ if (attribute.type !== 'JSXAttribute')
1103
+ continue;
1104
+ const attrName = attribute.name;
1105
+ if (!isObject(attrName) || typeof attrName.name !== 'string')
1106
+ continue;
1107
+ if (!failed.has(attrName.name))
1108
+ continue;
1109
+ const value = attribute.value;
1110
+ const expression = jsxValueExpression(nodeType(value) === 'JSXExpressionContainer' && isObject(value) ? value.expression : value);
1111
+ if (expression === undefined)
1112
+ continue;
1113
+ const markdown = serializeJsxExpression(expression, ctx).trim();
1114
+ slotJsxProp(props, failed, attrName.name, markdown, slots);
1115
+ }
1116
+ return slots;
1117
+ }
1118
+ export function fillJsxPropSlots(markdown, slots, budget) {
1119
+ let filledLength = markdown.length;
1120
+ for (const [placeholder, value] of slots) {
1121
+ const occurrences = markdown.split(placeholder).length - 1;
1122
+ filledLength += occurrences * (value.length - placeholder.length);
1123
+ }
1124
+ if (filledLength > budget.remainingBytes)
1125
+ return undefined;
1126
+ let filled = markdown;
1127
+ for (const [placeholder, value] of slots)
1128
+ filled = filled.replaceAll(placeholder, () => value);
1129
+ return filled;
1130
+ }
1063
1131
  function setFailedProp(props, failed, name) {
1064
1132
  props.delete(name);
1065
1133
  failed === null || failed === void 0 ? void 0 : failed.add(name);
@@ -18,6 +18,7 @@ export function getVersionOrLanguageFromPath(type, entry, path, defaultValue, in
18
18
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
19
19
  return (result || backupResult);
20
20
  }
21
+ const CHILD_KEYS = [...divisions, 'groups', 'pages'];
21
22
  function getDivisionFromPathHelper(type, entry, parentEntry, path, defaultValue, nearestDivisionValue) {
22
23
  var _a, _b;
23
24
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
@@ -26,10 +27,9 @@ function getDivisionFromPathHelper(type, entry, parentEntry, path, defaultValue,
26
27
  return undefinedResult;
27
28
  if (isPage(entry)) {
28
29
  if (isEqualIgnoringLeadingSlash(entry.href, path)) {
29
- const isCommon = isCommonPage(parentEntry, path);
30
30
  // logic: if this pages was shared in multiple divisions, we should use the default one,
31
31
  // if the page was not shared and only used in one division, we should use the nearest division
32
- if (isCommon && defaultValue && defaultValue !== nearestDivisionValue)
32
+ if (defaultValue && defaultValue !== nearestDivisionValue && isCommonPage(parentEntry, path))
33
33
  return undefinedResult;
34
34
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
35
35
  return (type === 'version' ? ((_a = entry.version) !== null && _a !== void 0 ? _a : nearestDivisionValue) : nearestDivisionValue);
@@ -38,7 +38,7 @@ function getDivisionFromPathHelper(type, entry, parentEntry, path, defaultValue,
38
38
  return undefinedResult;
39
39
  }
40
40
  }
41
- for (const key of [...divisions, 'groups', 'pages']) {
41
+ for (const key of CHILD_KEYS) {
42
42
  let subDivisions = (_b = readObjectArrayProperty(entry, key)) !== null && _b !== void 0 ? _b : [];
43
43
  if (key === 'pages' && 'root' in entry && typeof entry.root === 'object') {
44
44
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
@@ -157,3 +157,16 @@ describe('pathsDifferByOneSegment configured languages', () => {
157
157
  expect(pathsDifferByOneSegment('/tlh/guide', '/guide', 'language', languages)).toBe(true);
158
158
  });
159
159
  });
160
+ describe('pathsDifferByOneSegment when the target ends in a locale or index segment', () => {
161
+ it('matches language variants whose last segment differs', () => {
162
+ expect(pathsDifferByOneSegment('/guide/fr', '/guide/ja', 'language')).toBe(true);
163
+ expect(pathsDifferByOneSegment('/fr', '/ja', 'language')).toBe(true);
164
+ expect(pathsDifferByOneSegment('/guide', '/guide/ja', 'language')).toBe(true);
165
+ expect(pathsDifferByOneSegment('/guide/ja', '/guide', 'language')).toBe(true);
166
+ expect(pathsDifferByOneSegment('/fr/guide/index', '/ja/guide', 'language')).toBe(true);
167
+ });
168
+ it('matches version variants ending in index', () => {
169
+ expect(pathsDifferByOneSegment('/v1/guide/index', '/v2/guide/index', 'version')).toBe(true);
170
+ expect(pathsDifferByOneSegment('/v1/guide', '/v2/other', 'version')).toBe(false);
171
+ });
172
+ });
@@ -13,7 +13,7 @@ import { isLocale } from '@mintlify/models';
13
13
  import { divisions, } from '@mintlify/validation';
14
14
  import { generatePathToLanguageDict, generatePathToVersionDict } from '../divisions/index.js';
15
15
  import { optionallyRemoveLeadingSlash } from '../fs/optionallySlash.js';
16
- import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
16
+ import { isEqualIgnoringLeadingSlash, normalizeComparablePath, } from '../paths/isEqualIgnoringLeadingSlash.js';
17
17
  import { filterDivisions } from './filterDivisions.js';
18
18
  import { getFirstPageFromNavigation } from './getFirstPageFromNavigation.js';
19
19
  import { getVersionOrLanguageFromPath } from './getVersionOrLanguageFromPath.js';
@@ -125,10 +125,29 @@ function collectPageCandidates(node, candidates, inHiddenSubtree = false) {
125
125
  }
126
126
  }
127
127
  }
128
+ function containsComparablePath(node, path) {
129
+ if (Array.isArray(node))
130
+ return node.some((entry) => containsComparablePath(entry, path));
131
+ if (!isNavNode(node))
132
+ return false;
133
+ if (isDecoratedPageNode(node))
134
+ return normalizeComparablePath(node.href) === path;
135
+ for (const [key, value] of Object.entries(node)) {
136
+ if (key === 'pages' && Array.isArray(value)) {
137
+ const found = value.some((entry) => typeof entry === 'string'
138
+ ? normalizeComparablePath(entry) === path
139
+ : containsComparablePath(entry, path));
140
+ if (found)
141
+ return true;
142
+ }
143
+ else if ((Array.isArray(value) || isNavNode(value)) && containsComparablePath(value, path)) {
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
128
149
  function divisionContainsPath(division, currentPath) {
129
- const candidates = [];
130
- collectPageCandidates(division, candidates);
131
- return candidates.some((candidate) => isEqualIgnoringLeadingSlash(candidate.href, currentPath));
150
+ return containsComparablePath(division, normalizeComparablePath(currentPath));
132
151
  }
133
152
  function collectContainingProductNames(node, currentPath, names) {
134
153
  if (Array.isArray(node)) {
@@ -261,6 +280,15 @@ function containsDivision(node, keys) {
261
280
  const containsStatefulDivision = (node) => containsDivision(node, STATEFUL_DIVISION_KEYS);
262
281
  const PRODUCT_KEYS = new Set(['products']);
263
282
  const containsProducts = (node) => containsDivision(node, PRODUCT_KEYS);
283
+ const navHasProductsCache = new WeakMap();
284
+ function navHasProducts(nav) {
285
+ const cached = navHasProductsCache.get(nav);
286
+ if (cached !== undefined)
287
+ return cached;
288
+ const result = containsProducts(nav);
289
+ navHasProductsCache.set(nav, result);
290
+ return result;
291
+ }
264
292
  // mirrors buildDivisionItem's anonymous href resolution (first page in
265
293
  // getFirstPageFromNavigation order that is neither hidden nor group-gated);
266
294
  // falls back to the first raw candidate so a division with no anonymously
@@ -575,13 +603,39 @@ function cachedPathToLanguageDict(decoratedNav) {
575
603
  pathToLanguageDictCache.set(decoratedNav, dict);
576
604
  return dict;
577
605
  }
606
+ const comparableHrefsCache = new WeakMap();
607
+ function addComparableHrefs(node, hrefs) {
608
+ if (Array.isArray(node)) {
609
+ for (const entry of node)
610
+ addComparableHrefs(entry, hrefs);
611
+ return;
612
+ }
613
+ if (!isNavNode(node))
614
+ return;
615
+ if (typeof node.href === 'string')
616
+ hrefs.add(normalizeComparablePath(node.href));
617
+ for (const value of Object.values(node))
618
+ addComparableHrefs(value, hrefs);
619
+ }
620
+ function comparableHrefs(nav) {
621
+ const cached = comparableHrefsCache.get(nav);
622
+ if (cached)
623
+ return cached;
624
+ const hrefs = new Set();
625
+ addComparableHrefs(nav, hrefs);
626
+ comparableHrefsCache.set(nav, hrefs);
627
+ return hrefs;
628
+ }
629
+ const EMPTY_NAV = { pages: [] };
578
630
  export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = new Set(), isPreview = false, includeProductVersionMetadata = false, }) {
579
631
  var _a, _b, _c, _d;
580
632
  var _e, _f, _g;
581
633
  const containingProductNames = new Set();
582
- collectContainingProductNames(
583
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
584
- decoratedNav, currentPath, containingProductNames);
634
+ if (navHasProducts(decoratedNav)) {
635
+ collectContainingProductNames(
636
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
637
+ decoratedNav, currentPath, containingProductNames);
638
+ }
585
639
  const soleProductName = containingProductNames.size === 1 ? [...containingProductNames][0] : undefined;
586
640
  const currentProduct = typeof soleProductName === 'string' ? soleProductName : undefined;
587
641
  const productAmbiguous = containingProductNames.size > 1;
@@ -606,11 +660,14 @@ export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = ne
606
660
  const dictVersion = cachedPathToVersionDict(decoratedNav).get(strippedPath);
607
661
  const dictLanguage = cachedPathToLanguageDict(decoratedNav).get(strippedPath);
608
662
  const localeFromDict = dictLanguage && isLocale(dictLanguage) ? dictLanguage : undefined;
663
+ const lookupNav = comparableHrefs(decoratedNav).has(normalizeComparablePath(currentPath))
664
+ ? decoratedNav
665
+ : EMPTY_NAV;
609
666
  currentVersion =
610
667
  dictVersion ||
611
- getVersionOrLanguageFromPath('version', decoratedNav, currentPath, defaultVersion, (_e = (_c = initial.versions[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _e !== void 0 ? _e : '') ||
668
+ getVersionOrLanguageFromPath('version', lookupNav, currentPath, defaultVersion, (_e = (_c = initial.versions[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _e !== void 0 ? _e : '') ||
612
669
  undefined;
613
- currentLanguage = (_g = localeFromDict !== null && localeFromDict !== void 0 ? localeFromDict : getVersionOrLanguageFromPath('language', decoratedNav, currentPath, defaultLanguage, (_f = (_d = initial.languages[0]) === null || _d === void 0 ? void 0 : _d.language) !== null && _f !== void 0 ? _f : undefined)) !== null && _g !== void 0 ? _g : undefined;
670
+ currentLanguage = (_g = localeFromDict !== null && localeFromDict !== void 0 ? localeFromDict : getVersionOrLanguageFromPath('language', lookupNav, currentPath, defaultLanguage, (_f = (_d = initial.languages[0]) === null || _d === void 0 ? void 0 : _d.language) !== null && _f !== void 0 ? _f : undefined)) !== null && _g !== void 0 ? _g : undefined;
614
671
  }
615
672
  const filtered = filterDivisions({
616
673
  currentPath,
@@ -0,0 +1 @@
1
+ export {};