diff --git a/app/(site)/(components-ui)/components-ui/examples/content.tsx b/app/(site)/(components-ui)/components-ui/examples/content.tsx
index afd3cd5da..57e06b569 100644
--- a/app/(site)/(components-ui)/components-ui/examples/content.tsx
+++ b/app/(site)/(components-ui)/components-ui/examples/content.tsx
@@ -1,6 +1,5 @@
import { Note, Tip, Warning } from "@/app/ui/callout";
import { ContentBlock } from "@/app/ui/content-block";
-import { Term, Terms } from "@/app/ui/content-elements";
import { Faq } from "@/app/ui/faq";
import { CodeBlock } from "@/app/ui/site/code-block";
import { proseClassName, proseFlowClassName } from "@/app/ui/site/prose";
@@ -68,24 +67,6 @@ Each month returns $80, so the cost comes back in 15 months.
-
-How long a resolver may keep a record before it asks again.
-The DNS server that looks up names for your device.
-` }}
- >
-
-
- How long a resolver may keep a record before it asks again.
- The DNS server that looks up names for your device.
-
-
-
-
Questions stay open, one under another. Course pages use this. In MDX, write each question as a ### heading under a ## heading instead.>}
diff --git a/app/(site)/(components-ui)/components-ui/sections.ts b/app/(site)/(components-ui)/components-ui/sections.ts
index a6b945ee1..8fc94e074 100644
--- a/app/(site)/(components-ui)/components-ui/sections.ts
+++ b/app/(site)/(components-ui)/components-ui/sections.ts
@@ -106,9 +106,9 @@ export const gallerySections: GallerySection[] = [
{
slug: "content",
label: "Content",
- description: "Notes, warnings, tips, formulas, examples, terms, questions, and code.",
+ description: "Notes, warnings, tips, formulas, examples, questions, and code.",
icon: FileText,
- components: ["Callout", "Note", "Warning", "Tip", "ContentBlock", "Terms", "Term", "Faq", "CodeBlock"],
+ components: ["Callout", "Note", "Warning", "Tip", "ContentBlock", "Faq", "CodeBlock"],
},
{
slug: "prose",
diff --git a/app/(site)/(directories)/ad-networks/content/article.mdx b/app/(site)/(directories)/ad-networks/content/article.mdx
index 9136556a2..b5112586e 100644
--- a/app/(site)/(directories)/ad-networks/content/article.mdx
+++ b/app/(site)/(directories)/ad-networks/content/article.mdx
@@ -17,13 +17,13 @@ Each card shows the joining rule under "To join" when the network publishes one.
Networks count traffic in different ways, and the numbers aren't interchangeable. Check the exact word and period the rule uses:
-
-Every page loaded. One visit that reads three articles is three pageviews.
-One visit, which often includes several pageviews, so 50,000 sessions is harder to reach than 50,000 pageviews. Journey by Mediavine counts only Tier 1 sessions over 30 days.
-Sovrn's word. It isn't defined the same way as Google Analytics sessions, so don't swap one for the other.
-People. Someone who comes back five times in a month usually counts once. Ezoic counts monthly active users, MGID daily or monthly visitors, and The Moneytizer organic unique visitors from three countries.
-What your ads already earn. Mediavine counts the previous calendar year, while Publift and PubGuru Premium look at a month.
-
+| Word | What it counts |
+| --- | --- |
+| Pageviews | Every page loaded. One visit that reads three articles is three pageviews. |
+| Sessions | One visit, which often includes several pageviews, so 50,000 sessions is harder to reach than 50,000 pageviews. Journey by Mediavine counts only Tier 1 sessions over 30 days. |
+| Visits | Sovrn's word. It isn't defined the same way as Google Analytics sessions, so don't swap one for the other. |
+| Visitors and active users | People. Someone who comes back five times in a month usually counts once. Ezoic counts monthly active users, MGID daily or monthly visitors, and The Moneytizer organic unique visitors from three countries. |
+| Ad revenue | What your ads already earn. Mediavine counts the previous calendar year, while Publift and PubGuru Premium look at a month. |
Traffic isn't the only rule. Many networks also check where your visitors come from, whether your content is original, and whether your site follows Google's ad policies. Each network's page lists its full rules.
diff --git a/app/(site)/(directories)/launch-directories/content/how-we-check.mdx b/app/(site)/(directories)/launch-directories/content/how-we-check.mdx
index c52842843..ed21a61f5 100644
--- a/app/(site)/(directories)/launch-directories/content/how-we-check.mdx
+++ b/app/(site)/(directories)/launch-directories/content/how-we-check.mdx
@@ -8,10 +8,10 @@ Card prices show the lowest price we found, and "From" means higher plans exist.
We don't have a checked answer for that detail, and we don't guess one.
-
-The site's own pages don't publish it.
-A page blocked us, needed a login, or didn't load. It doesn't mean the site has closed.
-Two of the site's own pages disagree. The place's page links both.
-We found a submission page, but it didn't load when we checked.
-We couldn't find where submissions start. Look on the site itself before you give up on it.
-
+| Label | What it means |
+| --- | --- |
+| Not stated on their site | The site's own pages don't publish it. |
+| Couldn't verify | A page blocked us, needed a login, or didn't load. It doesn't mean the site has closed. |
+| Conflicting info | Two of the site's own pages disagree. The place's page links both. |
+| Submit page down | We found a submission page, but it didn't load when we checked. |
+| No submit page found | We couldn't find where submissions start. Look on the site itself before you give up on it. |
diff --git a/app/(site)/(directories)/launch-directories/content/link-types.mdx b/app/(site)/(directories)/launch-directories/content/link-types.mdx
index 48651f549..96b823be4 100644
--- a/app/(site)/(directories)/launch-directories/content/link-types.mdx
+++ b/app/(site)/(directories)/launch-directories/content/link-types.mdx
@@ -4,19 +4,19 @@ Choose Dofollow under Link type to see them. Each result comes from one publishe
[Google says](https://developers.google.com/search/docs/crawling-indexing/qualify-outbound-links) links marked nofollow, ugc, or sponsored will generally not be followed. A dofollow link still doesn't promise indexing, rankings, or traffic.
-
-The sampled link had no nofollow, ugc, or sponsored value, or the site's terms promise that.
-The link asks search engines not to follow it.
-The link is marked as user-generated content.
-The link is marked as paid.
-The link carried more than one value, such as ugc and nofollow.
-Free, paid, or top-ranked listings get different links. The place's page says which.
-The sampled listing doesn't link to the maker's own site.
-
+| Link type | What it means |
+| --- | --- |
+| Dofollow | The sampled link had no nofollow, ugc, or sponsored value, or the site's terms promise that. |
+| Nofollow | The link asks search engines not to follow it. |
+| UGC | The link is marked as user-generated content. |
+| Sponsored | The link is marked as paid. |
+| Mixed | The link carried more than one value, such as ugc and nofollow. |
+| Varies | Free, paid, or top-ranked listings get different links. The place's page says which. |
+| No link | The sampled listing doesn't link to the maker's own site. |
These two labels mean we have no checked result:
-
-We couldn't read a sampled link, but other evidence points to nofollow.
-We couldn't open a published listing or read its link.
-
+| Label | What it means |
+| --- | --- |
+| Likely nofollow | We couldn't read a sampled link, but other evidence points to nofollow. |
+| Couldn't verify | We couldn't open a published listing or read its link. |
diff --git a/app/(site)/(directories)/platform-fees/content/article.mdx b/app/(site)/(directories)/platform-fees/content/article.mdx
index 84888e907..b1f250ae0 100644
--- a/app/(site)/(directories)/platform-fees/content/article.mdx
+++ b/app/(site)/(directories)/platform-fees/content/article.mdx
@@ -8,14 +8,14 @@ Open the link at the bottom of a card, such as "+ 3 more rates" or "All rates an
Because the platform doesn't take a simple cut of your price. Subscription sites that split a shared pool by downloads, ad programs, and fixed pay per item show their own terms instead. These labels stand in for a rate:
-
-The platform pays creators a share but doesn't publish it.
-The platform charges a fee but doesn't publish the rate.
-The rate is in a contract that isn't public, as with console stores.
-The platform shows the rate only inside your seller account.
-You set the price, and the cost of making the product comes out of it.
-The platform doesn't sell for you, so you need your own payment provider.
-
+| Label | What it means |
+| --- | --- |
+| Share not disclosed | The platform pays creators a share but doesn't publish it. |
+| Fee not disclosed | The platform charges a fee but doesn't publish the rate. |
+| Set by agreement | The rate is in a contract that isn't public, as with console stores. |
+| Shown in dashboard | The platform shows the rate only inside your seller account. |
+| Base cost | You set the price, and the cost of making the product comes out of it. |
+| Your own checkout | The platform doesn't sell for you, so you need your own payment provider. |
## Is the lowest fee the cheapest?
diff --git a/app/(site)/(tools)/caa-checker/content/article.mdx b/app/(site)/(tools)/caa-checker/content/article.mdx
index fc03aedf6..9a27e5149 100644
--- a/app/(site)/(tools)/caa-checker/content/article.mdx
+++ b/app/(site)/(tools)/caa-checker/content/article.mdx
@@ -12,13 +12,13 @@ If a name is an alias (a CNAME), its target's CAA records count. The search does
## What do issue, issuewild, and iodef mean?
-
-Allows one certificate authority to issue normal certificates. With no `issuewild`, it covers wildcards too.
-Allows one certificate authority to issue wildcard certificates, such as `*.example.com`. When any `issuewild` exists, wildcards ignore `issue`.
-An email or web address for reports about blocked requests. It doesn't allow or block anything.
-An empty value. On its own, it allows no certificate authority.
-Marks a record as critical. A certificate authority that doesn't understand it must refuse to issue.
-
+| Setting | What it does |
+| --- | --- |
+| issue | Allows one certificate authority to issue normal certificates. With no `issuewild`, it covers wildcards too. |
+| issuewild | Allows one certificate authority to issue wildcard certificates, such as `*.example.com`. When any `issuewild` exists, wildcards ignore `issue`. |
+| iodef | An email or web address for reports about blocked requests. It doesn't allow or block anything. |
+| issue ";" | An empty value. On its own, it allows no certificate authority. |
+| Flag 128 | Marks a record as critical. A certificate authority that doesn't understand it must refuse to issue. |
Several `issue` records add up, and each one allows one more certificate authority. A policy with only `issuewild` records leaves normal certificates unrestricted.
diff --git a/app/(site)/(tools)/churn-rate-converter/content/usage.mdx b/app/(site)/(tools)/churn-rate-converter/content/usage.mdx
index 0ce501162..3d9b99836 100644
--- a/app/(site)/(tools)/churn-rate-converter/content/usage.mdx
+++ b/app/(site)/(tools)/churn-rate-converter/content/usage.mdx
@@ -12,8 +12,8 @@ Annualized churn is 45.96%, so 54.04% of customers are still with you after a ye
-
-What you'd get by just multiplying or dividing, such as 5% × 12 = 60%. It's there to compare, not to use.
-The model's expected count. A real group of 100 can lose a few more or fewer.
-How long a customer stays on average, in the period you entered. It's a rough model estimate, not your customers' real tenure.
-
+| Result | What it means |
+| --- | --- |
+| Wrong shortcut | What you'd get by just multiplying or dividing, such as 5% × 12 = 60%. It's there to compare, not to use. |
+| About 46 | The model's expected count. A real group of 100 can lose a few more or fewer. |
+| Average lifetime | How long a customer stays on average, in the period you entered. It's a rough model estimate, not your customers' real tenure. |
diff --git a/app/(site)/(tools)/domain-age/content/article.mdx b/app/(site)/(tools)/domain-age/content/article.mdx
index f8bb3b76c..1d139fb63 100644
--- a/app/(site)/(tools)/domain-age/content/article.mdx
+++ b/app/(site)/(tools)/domain-age/content/article.mdx
@@ -35,9 +35,9 @@ The lookup goes from stack127 to the registry. The website itself isn't contacte
## What do the dates in the result mean?
-
-The original registration date. It shows when the registry also publishes a later re-registration date.
-When the registration record last changed. It doesn't say what changed, and it isn't when the website was edited.
-When the current paid term ends, as the registry reports it. It isn't a deletion date.
-When the registry last updated the database it answered from.
-
+| Date | What it means |
+| --- | --- |
+| First registered | The original registration date. It shows when the registry also publishes a later re-registration date. |
+| Last changed | When the registration record last changed. It doesn't say what changed, and it isn't when the website was edited. |
+| Registry expiry | When the current paid term ends, as the registry reports it. It isn't a deletion date. |
+| Registry data | When the registry last updated the database it answered from. |
diff --git a/app/(site)/(tools)/startup-runway-calculator/content/usage.mdx b/app/(site)/(tools)/startup-runway-calculator/content/usage.mdx
index c5c1c4b62..ab676d6c3 100644
--- a/app/(site)/(tools)/startup-runway-calculator/content/usage.mdx
+++ b/app/(site)/(tools)/startup-runway-calculator/content/usage.mdx
@@ -1,10 +1,10 @@
## What to enter
-
-Business cash you can spend today. Not personal savings, unused credit, or money you haven't received.
-What customers pay into your account in a normal month. Use cash, not MRR. Leave out yearly payments, and add the next one as a planned change, so it's counted once. Leave out loans and investment.
-Everything that left the account in a typical month, including your own pay, payment fees, and taxes. The calculator adds no fees of its own.
-
+| Field | What to enter |
+| --- | --- |
+| Cash now | Business cash you can spend today. Not personal savings, unused credit, or money you haven't received. |
+| Cash collected | What customers pay into your account in a normal month. Use cash, not MRR. Leave out yearly payments, and add the next one as a planned change, so it's counted once. Leave out loans and investment. |
+| Cash paid out | Everything that left the account in a typical month, including your own pay, payment fees, and taxes. The calculator adds no fees of its own. |
If your months vary, use an average. [Pilot suggests](https://pilot.com/blog/burn-rate-calculation-startup-runway-calculator) at least three months.
diff --git a/app/ui/content-elements.tsx b/app/ui/content-elements.tsx
index 9b1a95069..d6efc6061 100644
--- a/app/ui/content-elements.tsx
+++ b/app/ui/content-elements.tsx
@@ -76,15 +76,3 @@ export function ContentImage({ alt, src, ...props }: ComponentPropsWithoutRef<"i
if (typeof src === "string" && src.startsWith("marker-art://")) return ;
return
;
}
-
-export function Terms({ children }: { children: ReactNode }) {
- return
{children}
;
-}
-
-/** A meaning written as one paragraph sits directly in the dd, as Markdown definition lists render it. */
-export function Term({ children, name }: { children: ReactNode; name: string }) {
- const items = Children.toArray(children);
- const only = items.length === 1 ? items[0] : null;
- const meaning = isValidElement<{ children?: ReactNode }>(only) && (only.type === "p" || only.type === ContentParagraph) ? only.props.children : children;
- return <>{name}{meaning}>;
-}
diff --git a/app/ui/content-markdown.tsx b/app/ui/content-markdown.tsx
index 9b21b75bc..aee9daa35 100644
--- a/app/ui/content-markdown.tsx
+++ b/app/ui/content-markdown.tsx
@@ -8,6 +8,7 @@ import type { UiChartSpec } from "./charts/chart-option";
type MarkdownNode = {
type: string;
value?: string;
+ align?: null[];
data?: { hName?: string; hProperties?: Record };
children?: MarkdownNode[];
};
@@ -32,13 +33,15 @@ function remarkDefinitionLists() {
index += 1;
}
if (entries.length) {
+ // Terms show as a two-column table, the same as terms in MDX pages.
+ const row = (cells: string[]): MarkdownNode => ({
+ type: "tableRow",
+ children: cells.map((value) => ({ type: "tableCell", children: [{ type: "text", value }] })),
+ });
children.push({
- type: "paragraph",
- data: { hName: "dl" },
- children: entries.flatMap((entry) => [
- { type: "paragraph", data: { hName: "dt" }, children: [{ type: "text", value: entry.term }] },
- { type: "paragraph", data: { hName: "dd" }, children: [{ type: "text", value: entry.description }] },
- ]),
+ type: "table",
+ align: [null, null],
+ children: [row(["Term", "Meaning"]), ...entries.map((entry) => row([entry.term, entry.description]))],
});
continue;
}
diff --git a/app/ui/site/prose.module.css b/app/ui/site/prose.module.css
index a71a69818..43a051492 100644
--- a/app/ui/site/prose.module.css
+++ b/app/ui/site/prose.module.css
@@ -97,32 +97,6 @@
width: 100%;
}
-/* Terms sit beside their meanings, the same layout tool articles use, and stack on phones. */
-.prose dl {
- display: grid;
- gap: var(--space-2) var(--space-5);
- grid-template-columns: minmax(8rem, max-content) minmax(0, 1fr);
- margin: 0;
-}
-
-.prose dt {
- font-weight: var(--weight-semibold);
-}
-
-.prose dd {
- margin: 0;
-}
-
-@media (max-width: 640px) {
- .prose dl {
- grid-template-columns: 1fr;
- }
-
- .prose dd + dt {
- margin-top: var(--space-3);
- }
-}
-
.flow,
.sectionFlow > section {
display: grid;
diff --git a/mdx-components.tsx b/mdx-components.tsx
index dd0ac3ced..61d0aa9de 100644
--- a/mdx-components.tsx
+++ b/mdx-components.tsx
@@ -10,8 +10,6 @@ import {
ContentParagraph,
ContentPre,
ContentTable,
- Term,
- Terms,
} from "@/app/ui/content-elements";
import { UiDataTable } from "@/app/ui/display";
@@ -31,8 +29,6 @@ const contentComponents: MDXComponents = {
Tip: ({ children, title }: BlockProps) => {children},
Formula: ({ children, title }: BlockProps) => {children},
Example: ({ children, title }: BlockProps) => {children},
- Terms,
- Term,
UiStaticChart,
UiDataTable,
MarkerArt: ({ alt, figure }: { alt: string; figure: string }) => ,
diff --git a/scripts/stack127/content/allowed-components.mjs b/scripts/stack127/content/allowed-components.mjs
index bf2c03cda..318681134 100644
--- a/scripts/stack127/content/allowed-components.mjs
+++ b/scripts/stack127/content/allowed-components.mjs
@@ -1,5 +1,5 @@
// Keep this list aligned with the shared components in mdx-components.tsx.
export const allowedContentComponents = [
- 'Note', 'Warning', 'Tip', 'Formula', 'Example', 'Terms', 'Term',
+ 'Note', 'Warning', 'Tip', 'Formula', 'Example',
'UiStaticChart', 'UiChart', 'UiDataTable', 'MarkerArt',
];
diff --git a/scripts/stack127/content/convert-markdown.mjs b/scripts/stack127/content/convert-markdown.mjs
index 179ad2475..e5a75e2f6 100644
--- a/scripts/stack127/content/convert-markdown.mjs
+++ b/scripts/stack127/content/convert-markdown.mjs
@@ -21,6 +21,11 @@ function escapeText(value) {
.replace(/^(import|export)(?=\s)/gm, word => `${word.charCodeAt(0)};${word.slice(1)}`);
}
+// A table cell is one line, and an unescaped pipe would end it.
+function tableCell(value) {
+ return value.replace(/[ \t]*\n[ \t]*/g, ' ').replace(/\\[\s\S]|\|/g, match => match === '|' ? '\\|' : match);
+}
+
function linkLabel(value) {
return value.replace(/[\\`*_~[\]{}<&|]/g, character => `${character.charCodeAt(0)};`);
}
@@ -131,12 +136,19 @@ export function convertMarkdown(markdown, { source = 'Markdown', lineOffset = 0
if (entries.length) {
const start = node.children[first];
const end = node.children[index - 1];
- const items = entries.map(entry => {
- const description = raw(entry.node).replace(/^[^\n]+\n[ \t]*:\s+/, '');
- return `\n\n${nested(description, entry.node, 1)}\n\n`;
+ // Terms become a two-column table. Rename the headers to fit the page.
+ const rows = entries.map(entry => {
+ const [term, ...rest] = raw(entry.node).split('\n');
+ const description = rest.join('\n').replace(/^[ \t]*:\s+/, '');
+ const meaning = nested(description, entry.node, 1).trim();
+ // A table cell holds one paragraph; more would show as raw Markdown.
+ if (/\n[ \t]*\n/.test(meaning) || /^[ \t]*(?:\||[-*+][ \t]|>|#{1,6}[ \t]|\d+[.)][ \t]|```|~~~|<[A-Z])/m.test(meaning)) {
+ fail(source, entry.node, 'A term meaning with more than one paragraph or block needs a manual MDX conversion', lineOffset);
+ }
+ return `| ${tableCell(escapeText(term.trim()))} | ${tableCell(meaning)} |`;
});
edits.push({ start: start.position.start.offset, end: end.position.end.offset,
- value: indent(`\n\n${items.join('\n\n')}\n\n`, start) });
+ value: indent(['| Term | Meaning |', '| --- | --- |', ...rows].join('\n'), start) });
index--;
} else visit(node.children[index]);
}
diff --git a/stack127_docs/page-content.md b/stack127_docs/page-content.md
index e5a42d024..66520c95e 100644
--- a/stack127_docs/page-content.md
+++ b/stack127_docs/page-content.md
@@ -53,7 +53,7 @@ MDX renders on the server, so pages stay static and send no extra script.
## Writing in MDX
-MDX is Markdown that can hold our components. Write ordinary Markdown for headings, paragraphs, lists, links, tables, and code. Use a component only for the blocks below.
+MDX is Markdown that can hold our components. Write ordinary Markdown for headings, paragraphs, lists, links, tables, and code. Terms and their meanings are a two-column Markdown table, with headers that fit the page, such as "Word" and "What it counts". Use a component only for the blocks below.
| Block | Write |
| --- | --- |
@@ -62,7 +62,6 @@ MDX is Markdown that can hold our components. Write ordinary Markdown for headin
| Recommendation | `…` |
| Formula | `…` |
| Worked example | `…` |
-| Terms and their meanings | `` holding `…` items |
| Chart | ``. Use `` only when the reader needs hover, toggles, or ranges. |
| Data table with row headers or mobile rows | `` |
| Marker art | `` |
@@ -115,7 +114,7 @@ Copy fixes, such as a typo, leave the date alone.
Move a page before you change its content. Make the move and the content change as separate commits, so the text check below stays easy.
-1. Recover its archived content when needed: `npm run stack127:content:export -- --slug `. It reads `stack127_retired_page_content` after column retirement and writes the `content/` files and turns callouts, terms, and marker art into components. A directory with section markers gets one file per section, named for the marker. The export reads development by default; add `--environment production` to read the live text. It never writes to the database, and it won't overwrite files unless you add `--force`.
+1. Recover its archived content when needed: `npm run stack127:content:export -- --slug `. It reads `stack127_retired_page_content` after column retirement and writes the `content/` files. It turns callouts and marker art into components, and terms into a two-column table headed "Term" and "Meaning". Rename those headers to fit the page. A directory with section markers gets one file per section, named for the marker. The export reads development by default; add `--environment production` to read the live text. It never writes to the database, and it won't overwrite files unless you add `--force`.
2. If the export stops on a chart fence or raw HTML, it names the line. Those need a manual conversion, such as a chart to ``.
3. Read every file and clean it to the rules above. Keep the reader's text the same unless a sentence breaks a rule.
4. Change the route to import the files, as the pilot pages do.
diff --git a/stack127_docs/shared-ui.md b/stack127_docs/shared-ui.md
index 5d3a695b2..73fc3adca 100644
--- a/stack127_docs/shared-ui.md
+++ b/stack127_docs/shared-ui.md
@@ -231,7 +231,7 @@ Content tables written in Markdown use a scrollable `TableFrame`. Keep tables re
## Content blocks
-Page content is MDX beside each route. [page-content.md](page-content.md) lists every block content can use and how to write it: notes, warnings, tips, formulas, examples, terms, charts, data tables, and marker art. `mdx-components.tsx` maps them to the shared elements in `app/ui/content-elements.tsx`, so every page renders them the same way.
+Page content is MDX beside each route. [page-content.md](page-content.md) lists every block content can use and how to write it: notes, warnings, tips, formulas, examples, charts, data tables, and marker art. `mdx-components.tsx` maps them to the shared elements in `app/ui/content-elements.tsx`, so every page renders them the same way.
- Use a note for context, a warning for something that can cost money, data, or time, and a tip for a recommendation.
- Give a formula or example one clear home instead of repeating it around the page.
diff --git a/tests/stack127/page-content.test.mjs b/tests/stack127/page-content.test.mjs
index 99f2e7542..d5eeece8a 100644
--- a/tests/stack127/page-content.test.mjs
+++ b/tests/stack127/page-content.test.mjs
@@ -29,18 +29,16 @@ test('converts all callout tones, optional titles, and nested Markdown', () => {
valid(nested);
});
-test('converts definition lists into Terms and Term components', () => {
- const result = convertMarkdown('Churn\n: Customers who leave.\n\nGross margin\n: Revenue less costs.');
- assert.match(result, /^/);
- assert.equal((result.match(//g) ?? []).length, 1);
- assert.match(result, /\n\nCustomers who leave\./);
- assert.match(result, /\n\nRevenue less costs\./);
+test('converts definition lists into one two-column table', () => {
+ const result = convertMarkdown('Churn\n: Customers who leave.\n\nGross margin\n: Revenue less costs\n over time.');
+ assert.equal(result, '| Term | Meaning |\n| --- | --- |\n| Churn | Customers who leave. |\n| Gross margin | Revenue less costs over time. |\n');
valid(result);
+ assert.throws(() => convertMarkdown('Term\n: One\n: Two'), /manual MDX conversion/);
});
test('preserves escaped text in terms and converts nested callouts without breaking lists or quotes', () => {
- const terms = convertMarkdown('Term\n: A \\*literal\\* and {value}.');
- assert.ok(terms.includes('A \\*literal\\* and {value}.'));
+ const terms = convertMarkdown('A \\*b\\* | c\n: A \\*literal\\* and {value} | \\| pipe.');
+ assert.ok(terms.includes('| A \\*b\\* \\| c | A \\*literal\\* and {value} \\| \\| pipe. |'));
valid(terms);
const quote = convertMarkdown('> Quote\n>\n> > [!NOTE] Nested\n> > Body');
assert.match(quote, /> /);