@misoto22/design 0.7.0 → 0.8.0
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/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/README.md +50 -5
- package/bin/misoto22-design.mjs +59 -13
- package/dist/agent/Accordion.md +24 -1
- package/dist/agent/Alert.md +23 -1
- package/dist/agent/AppShell.md +30 -2
- package/dist/agent/ArchitectureFigure.md +25 -2
- package/dist/agent/AreaChart.md +27 -2
- package/dist/agent/Article.md +26 -3
- package/dist/agent/AspectRatio.md +57 -0
- package/dist/agent/Avatar.md +25 -5
- package/dist/agent/Badge.md +22 -1
- package/dist/agent/BarChart.md +29 -2
- package/dist/agent/BarList.md +27 -1
- package/dist/agent/BigNumber.md +26 -2
- package/dist/agent/BoxPlot.md +24 -1
- package/dist/agent/Breadcrumb.md +24 -2
- package/dist/agent/BulletChart.md +23 -1
- package/dist/agent/Button.md +25 -1
- package/dist/agent/Calendar.md +37 -3
- package/dist/agent/Card.md +24 -2
- package/dist/agent/Checkbox.md +23 -2
- package/dist/agent/Code.md +50 -0
- package/dist/agent/CodeBlock.md +74 -0
- package/dist/agent/Collapsible.md +22 -1
- package/dist/agent/Combobox.md +29 -3
- package/dist/agent/Command.md +25 -2
- package/dist/agent/ComposedChart.md +26 -2
- package/dist/agent/ContextMenu.md +37 -4
- package/dist/agent/DataflowFigure.md +22 -1
- package/dist/agent/DatePicker.md +34 -6
- package/dist/agent/DescriptionList.md +68 -0
- package/dist/agent/Diagram.md +27 -3
- package/dist/agent/DiagramCanvas.md +24 -1
- package/dist/agent/DiagramExportMenu.md +29 -3
- package/dist/agent/DiagramInspector.md +23 -1
- package/dist/agent/DiagramLegend.md +22 -1
- package/dist/agent/DiagramMinimap.md +24 -4
- package/dist/agent/DiagramToolbar.md +23 -1
- package/dist/agent/Dialog.md +26 -3
- package/dist/agent/DropdownMenu.md +45 -6
- package/dist/agent/EmptyState.md +26 -1
- package/dist/agent/ErrorState.md +27 -2
- package/dist/agent/Facet.md +25 -1
- package/dist/agent/Field.md +45 -4
- package/dist/agent/FigureBand.md +24 -1
- package/dist/agent/FloatingIconButton.md +23 -1
- package/dist/agent/FunnelChart.md +23 -1
- package/dist/agent/Heading.md +64 -0
- package/dist/agent/Heatmap.md +25 -1
- package/dist/agent/Histogram.md +26 -2
- package/dist/agent/Input.md +22 -1
- package/dist/agent/Kbd.md +19 -1
- package/dist/agent/LifecycleFigure.md +22 -1
- package/dist/agent/LineChart.md +26 -2
- package/dist/agent/LinkArrow.md +20 -1
- package/dist/agent/Markdown.md +68 -0
- package/dist/agent/NativeSelect.md +23 -2
- package/dist/agent/NavItem.md +21 -1
- package/dist/agent/Pagination.md +28 -2
- package/dist/agent/PieChart.md +26 -2
- package/dist/agent/Popover.md +23 -1
- package/dist/agent/Progress.md +27 -3
- package/dist/agent/RadarChart.md +26 -2
- package/dist/agent/RadialChart.md +26 -3
- package/dist/agent/RadioGroup.md +23 -2
- package/dist/agent/SankeyChart.md +25 -2
- package/dist/agent/ScatterChart.md +27 -2
- package/dist/agent/ScrollArea.md +24 -3
- package/dist/agent/SearchableMenu.md +23 -1
- package/dist/agent/Select.md +29 -3
- package/dist/agent/Separator.md +28 -4
- package/dist/agent/SequenceFigure.md +23 -1
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +27 -4
- package/dist/agent/Sparkline.md +23 -1
- package/dist/agent/Spinner.md +24 -2
- package/dist/agent/StatusDot.md +20 -1
- package/dist/agent/StatusPill.md +28 -2
- package/dist/agent/Steps.md +24 -1
- package/dist/agent/Switch.md +21 -1
- package/dist/agent/Table.md +26 -3
- package/dist/agent/Tabs.md +24 -2
- package/dist/agent/Tag.md +40 -9
- package/dist/agent/Text.md +65 -0
- package/dist/agent/Textarea.md +20 -1
- package/dist/agent/Timestamp.md +64 -0
- package/dist/agent/Toast.md +26 -2
- package/dist/agent/ToggleGroup.md +24 -2
- package/dist/agent/Toolbar.md +71 -0
- package/dist/agent/Tooltip.md +23 -1
- package/dist/agent/TreemapChart.md +24 -1
- package/dist/agent/WaterfallChart.md +24 -1
- package/dist/agent/WorkflowFigure.md +24 -2
- package/dist/agent/catalog.json +5602 -745
- package/dist/agent/index.md +13 -4
- package/dist/article.css +16 -4
- package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
- package/dist/charts/AreaChart/AreaChart.js +12 -13
- package/dist/charts/AreaChart/AreaChart.js.map +1 -1
- package/dist/charts/BarChart/BarChart.d.ts +14 -3
- package/dist/charts/BarChart/BarChart.js +20 -12
- package/dist/charts/BarChart/BarChart.js.map +1 -1
- package/dist/charts/BarList/BarList.d.ts +9 -1
- package/dist/charts/BarList/BarList.js +9 -1
- package/dist/charts/BarList/BarList.js.map +1 -1
- package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
- package/dist/charts/BigNumber/BigNumber.js +25 -5
- package/dist/charts/BigNumber/BigNumber.js.map +1 -1
- package/dist/charts/BoxPlot/BoxPlot.js +4 -2
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/charts/BulletChart/BulletChart.js +28 -7
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
- package/dist/charts/ComposedChart/ComposedChart.js +10 -10
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
- package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
- package/dist/charts/FunnelChart/FunnelChart.js +6 -16
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
- package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
- package/dist/charts/Heatmap/Heatmap.js +20 -11
- package/dist/charts/Heatmap/Heatmap.js.map +1 -1
- package/dist/charts/Histogram/Histogram.d.ts +24 -1
- package/dist/charts/Histogram/Histogram.js +43 -11
- package/dist/charts/Histogram/Histogram.js.map +1 -1
- package/dist/charts/LineChart/LineChart.d.ts +9 -2
- package/dist/charts/LineChart/LineChart.js +11 -12
- package/dist/charts/LineChart/LineChart.js.map +1 -1
- package/dist/charts/PieChart/PieChart.d.ts +15 -3
- package/dist/charts/PieChart/PieChart.js +14 -6
- package/dist/charts/PieChart/PieChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
- package/dist/charts/RadarChart/RadarChart.js +11 -9
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
- package/dist/charts/RadialChart/RadialChart.js +28 -6
- package/dist/charts/RadialChart/RadialChart.js.map +1 -1
- package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
- package/dist/charts/SankeyChart/SankeyChart.js +14 -6
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
- package/dist/charts/ScatterChart/ScatterChart.js +21 -15
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
- package/dist/charts/Sparkline/Sparkline.js +5 -3
- package/dist/charts/Sparkline/Sparkline.js.map +1 -1
- package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
- package/dist/charts/TreemapChart/TreemapChart.js +20 -11
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
- package/dist/charts/lib/export.d.ts +1 -0
- package/dist/charts/lib/figure.d.ts +21 -1
- package/dist/charts/lib/figure.js +5 -2
- package/dist/charts/lib/figure.js.map +1 -1
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +1 -0
- package/dist/components/Accordion/Accordion.js +8 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/AppShell/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +34 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +11 -4
- package/dist/components/Article/Article.js +13 -0
- package/dist/components/Article/Article.js.map +1 -1
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +12 -3
- package/dist/components/Avatar/Avatar.js +6 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.js +14 -0
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +29 -3
- package/dist/components/Calendar/Calendar.js +60 -12
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +5 -2
- package/dist/components/Checkbox/Checkbox.js +12 -2
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
- package/dist/components/CodeBlock/CodeBlock.js +123 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +10 -2
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +15 -1
- package/dist/components/Combobox/Combobox.js +25 -5
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.d.ts +4 -2
- package/dist/components/Command/Command.js +13 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +32 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +36 -4
- package/dist/components/DatePicker/DatePicker.js +55 -12
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +8 -3
- package/dist/components/Diagram/Diagram.js +82 -10
- package/dist/components/Diagram/Diagram.js.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +18 -3
- package/dist/components/Dialog/Dialog.js +20 -2
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.d.ts +25 -1
- package/dist/components/EmptyState/EmptyState.js +3 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.d.ts +22 -1
- package/dist/components/ErrorState/ErrorState.js +11 -2
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/Field/Field.d.ts +70 -3
- package/dist/components/Field/Field.js +53 -12
- package/dist/components/Field/Field.js.map +1 -1
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
- package/dist/components/NativeSelect/NativeSelect.js +2 -3
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +24 -1
- package/dist/components/Pagination/Pagination.js +6 -3
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Progress/Progress.d.ts +17 -5
- package/dist/components/Progress/Progress.js +18 -6
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
- package/dist/components/RadioGroup/RadioGroup.js +11 -1
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
- package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
- package/dist/components/Select/Select.d.ts +18 -2
- package/dist/components/Select/Select.js +20 -3
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Separator/Separator.d.ts +21 -3
- package/dist/components/Separator/Separator.js +9 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Sheet/Sheet.d.ts +3 -0
- package/dist/components/Sheet/Sheet.js +11 -2
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +8 -1
- package/dist/components/Skeleton/Skeleton.js +1 -1
- package/dist/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/Slider/Slider.d.ts +21 -3
- package/dist/components/Slider/Slider.js +54 -27
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +8 -0
- package/dist/components/Spinner/Spinner.js +3 -2
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/StatusPill/StatusPill.d.ts +5 -0
- package/dist/components/StatusPill/StatusPill.js +6 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -1
- package/dist/components/Table/Table.d.ts +6 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tag/Tag.d.ts +68 -9
- package/dist/components/Tag/Tag.js +50 -10
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -1
- package/dist/components/Toast/Toast.js +27 -4
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
- package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
- package/dist/diagrams/index.d.ts +1 -0
- package/dist/diagrams/index.js +10 -0
- package/dist/diagrams/index.js.map +1 -1
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +48 -7
- package/dist/diagrams/lib/frame.js +17 -2
- package/dist/diagrams/lib/frame.js.map +1 -1
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +16 -4
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +17 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +39 -36
- package/dist/tokens.json +39 -36
- package/llms.txt +35 -0
- package/package.json +5 -2
- package/skills/misoto22-design/evals/evals.json +84 -0
- package/skills/misoto22-design/rules/a11y.md +55 -3
- package/skills/misoto22-design/rules/forms.md +5 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { Code } from "../Code/Code.js";
|
|
4
|
+
import { CodeBlock } from "../CodeBlock/CodeBlock.js";
|
|
5
|
+
import { Heading } from "../Heading/Heading.js";
|
|
6
|
+
import { LinkArrow } from "../LinkArrow/LinkArrow.js";
|
|
7
|
+
import { Separator } from "../Separator/Separator.js";
|
|
8
|
+
import { Text } from "../Text/Text.js";
|
|
9
|
+
import { isOutbound } from "./inline.js";
|
|
10
|
+
import { parseMarkdown } from "./parse.js";
|
|
11
|
+
import { slugify } from "./slug.js";
|
|
12
|
+
import { parseMarkdown as parseMarkdown2 } from "./parse.js";
|
|
13
|
+
import { slugify as slugify2 } from "./slug.js";
|
|
14
|
+
function textOf(nodes) {
|
|
15
|
+
return nodes.map((node) => {
|
|
16
|
+
if (node.type === "text" || node.type === "code") return node.value;
|
|
17
|
+
if (node.type === "image") return node.alt;
|
|
18
|
+
return textOf(node.children);
|
|
19
|
+
}).join("");
|
|
20
|
+
}
|
|
21
|
+
function headingId(text, context) {
|
|
22
|
+
const base = slugify(text) || "section";
|
|
23
|
+
const seen = (context.taken.get(base) ?? 0) + 1;
|
|
24
|
+
context.taken.set(base, seen);
|
|
25
|
+
const id = seen === 1 ? base : `${base}-${seen}`;
|
|
26
|
+
return context.idPrefix ? `${context.idPrefix}-${id}` : id;
|
|
27
|
+
}
|
|
28
|
+
function renderInline(node, key, context) {
|
|
29
|
+
switch (node.type) {
|
|
30
|
+
case "text":
|
|
31
|
+
return node.value;
|
|
32
|
+
case "code":
|
|
33
|
+
return /* @__PURE__ */ jsx(Code, { children: node.value }, key);
|
|
34
|
+
case "strong":
|
|
35
|
+
return /* @__PURE__ */ jsx("strong", { className: "font-medium text-(--ink)", children: node.children.map((child, index) => renderInline(child, index, context)) }, key);
|
|
36
|
+
case "emphasis":
|
|
37
|
+
return /* @__PURE__ */ jsx("em", { className: "italic", children: node.children.map((child, index) => renderInline(child, index, context)) }, key);
|
|
38
|
+
case "image":
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
"img",
|
|
41
|
+
{
|
|
42
|
+
src: node.src,
|
|
43
|
+
alt: node.alt,
|
|
44
|
+
loading: "lazy",
|
|
45
|
+
className: "h-auto max-w-full rounded-(--radius)"
|
|
46
|
+
},
|
|
47
|
+
key
|
|
48
|
+
);
|
|
49
|
+
case "link": {
|
|
50
|
+
const outbound = isOutbound(node.href);
|
|
51
|
+
return /* @__PURE__ */ jsxs(
|
|
52
|
+
"a",
|
|
53
|
+
{
|
|
54
|
+
href: node.href,
|
|
55
|
+
rel: outbound ? "noreferrer nofollow" : void 0,
|
|
56
|
+
className: "text-(--ink) underline decoration-[var(--rule-2)] underline-offset-4 transition-[text-decoration-color] duration-(--duration-fast) hover:decoration-[var(--ink)]",
|
|
57
|
+
children: [
|
|
58
|
+
node.children.map((child, index) => renderInline(child, index, context)),
|
|
59
|
+
outbound && context.markExternalLinks ? /* @__PURE__ */ jsx(LinkArrow, {}) : null
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
key
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function renderItem(blocks, key, context) {
|
|
68
|
+
const first = blocks[0];
|
|
69
|
+
const only = blocks.length === 1 && first?.type === "paragraph" ? first : void 0;
|
|
70
|
+
return /* @__PURE__ */ jsx("li", { className: "relative", children: only ? only.children.map((child, i) => renderInline(child, i, context)) : blocks.map((block, i) => renderBlock(block, i, context)) }, key);
|
|
71
|
+
}
|
|
72
|
+
function renderBlock(node, key, context) {
|
|
73
|
+
switch (node.type) {
|
|
74
|
+
case "heading": {
|
|
75
|
+
const level = Math.min(6, node.level + context.headingLevelStart - 1);
|
|
76
|
+
return /* @__PURE__ */ jsx(Heading, { level, id: headingId(textOf(node.children), context), children: node.children.map((child, index) => renderInline(child, index, context)) }, key);
|
|
77
|
+
}
|
|
78
|
+
case "paragraph":
|
|
79
|
+
return /* @__PURE__ */ jsx(Text, { children: node.children.map((child, index) => renderInline(child, index, context)) }, key);
|
|
80
|
+
case "code":
|
|
81
|
+
return /* @__PURE__ */ jsx(CodeBlock, { code: node.value, lang: node.lang }, key);
|
|
82
|
+
case "rule":
|
|
83
|
+
return /* @__PURE__ */ jsx(Separator, { weight: "edge", decorative: false }, key);
|
|
84
|
+
case "quote":
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
"blockquote",
|
|
87
|
+
{
|
|
88
|
+
className: "m-0 border-s-2 border-(--rule-hard) ps-[1.25em] font-serif text-[length:var(--fs-item)] leading-[1.55] text-(--ink) [&>*+*]:mt-[0.6em]",
|
|
89
|
+
children: node.children.map((child, i) => renderBlock(child, i, context))
|
|
90
|
+
},
|
|
91
|
+
key
|
|
92
|
+
);
|
|
93
|
+
case "list":
|
|
94
|
+
return node.ordered ? /* @__PURE__ */ jsx(
|
|
95
|
+
"ol",
|
|
96
|
+
{
|
|
97
|
+
className: "m-0 list-decimal ps-[1.4em] text-(--ink-2) marker:text-(--ink-3-aa) [&>li+li]:mt-[0.4em]",
|
|
98
|
+
children: node.items.map((item, i) => renderItem(item, i, context))
|
|
99
|
+
},
|
|
100
|
+
key
|
|
101
|
+
) : (
|
|
102
|
+
// The marker is a hairline dash drawn on `::before`, not a disc: the
|
|
103
|
+
// system's only filled circle is a status dot. `article.css` draws the
|
|
104
|
+
// same dash on the same pseudo-element, so markdown dropped into an
|
|
105
|
+
// Article coincides with the stylesheet rather than doubling it.
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
"ul",
|
|
108
|
+
{
|
|
109
|
+
className: "m-0 list-none ps-[1.15em] text-(--ink-2) [&>li+li]:mt-[0.4em] [&>li]:before:absolute [&>li]:before:start-[-1.15em] [&>li]:before:top-[0.85em] [&>li]:before:h-px [&>li]:before:w-[0.55em] [&>li]:before:bg-(--rule-2) [&>li]:before:content-['']",
|
|
110
|
+
children: node.items.map((item, i) => renderItem(item, i, context))
|
|
111
|
+
},
|
|
112
|
+
key
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function Markdown({
|
|
118
|
+
children,
|
|
119
|
+
headingLevelStart = 1,
|
|
120
|
+
idPrefix,
|
|
121
|
+
markExternalLinks = false,
|
|
122
|
+
parse = parseMarkdown
|
|
123
|
+
}) {
|
|
124
|
+
const context = { headingLevelStart, idPrefix, markExternalLinks, taken: /* @__PURE__ */ new Map() };
|
|
125
|
+
const nodes = typeof children === "string" ? parse(children) : [];
|
|
126
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node, index) => renderBlock(node, index, context)) });
|
|
127
|
+
}
|
|
128
|
+
var Markdown_default = Markdown;
|
|
129
|
+
export {
|
|
130
|
+
Markdown,
|
|
131
|
+
Markdown_default as default,
|
|
132
|
+
parseMarkdown2 as parseMarkdown,
|
|
133
|
+
slugify2 as slugify
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=Markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { Fragment, type ReactNode } from 'react'\nimport { Code } from '../Code/Code'\nimport { CodeBlock } from '../CodeBlock/CodeBlock'\nimport { Heading } from '../Heading/Heading'\nimport { LinkArrow } from '../LinkArrow/LinkArrow'\nimport { Separator } from '../Separator/Separator'\nimport { Text } from '../Text/Text'\nimport { isOutbound } from './inline'\nimport type { MarkdownHeadingLevel, MarkdownInline, MarkdownNode } from './nodes'\nimport { parseMarkdown } from './parse'\nimport { slugify } from './slug'\n\nexport type { MarkdownHeadingLevel, MarkdownInline, MarkdownNode } from './nodes'\nexport { parseMarkdown } from './parse'\nexport { slugify } from './slug'\n\n/** The plain text of an inline run — what a heading's id is derived from. */\nfunction textOf(nodes: MarkdownInline[]): string {\n return nodes\n .map((node) => {\n if (node.type === 'text' || node.type === 'code') return node.value\n if (node.type === 'image') return node.alt\n return textOf(node.children)\n })\n .join('')\n}\n\ninterface RenderContext {\n headingLevelStart: MarkdownHeadingLevel\n idPrefix?: string\n markExternalLinks: boolean\n /** How many headings have already claimed each slug, for the `-2` suffix. */\n taken: Map<string, number>\n}\n\nfunction headingId(text: string, context: RenderContext): string {\n const base = slugify(text) || 'section'\n const seen = (context.taken.get(base) ?? 0) + 1\n context.taken.set(base, seen)\n const id = seen === 1 ? base : `${base}-${seen}`\n return context.idPrefix ? `${context.idPrefix}-${id}` : id\n}\n\nfunction renderInline(node: MarkdownInline, key: number, context: RenderContext): ReactNode {\n switch (node.type) {\n case 'text':\n return node.value\n case 'code':\n return <Code key={key}>{node.value}</Code>\n case 'strong':\n return (\n <strong key={key} className=\"font-medium text-(--ink)\">\n {node.children.map((child, index) => renderInline(child, index, context))}\n </strong>\n )\n case 'emphasis':\n return (\n <em key={key} className=\"italic\">\n {node.children.map((child, index) => renderInline(child, index, context))}\n </em>\n )\n case 'image':\n return (\n <img\n key={key}\n src={node.src}\n alt={node.alt}\n loading=\"lazy\"\n className=\"h-auto max-w-full rounded-(--radius)\"\n />\n )\n case 'link': {\n // Refusing `javascript:` keeps the link from EXECUTING; `rel` keeps it\n // from spending the page's reputation and its reader's location. The\n // author of this href is the untrusted party the component exists for,\n // so an outbound link they wrote passes no ranking on (`nofollow`) and\n // hands the destination no `Referer` to read the current URL out of\n // (`noreferrer`). A relative href cannot leave, so it is left alone.\n const outbound = isOutbound(node.href)\n return (\n <a\n key={key}\n href={node.href}\n rel={outbound ? 'noreferrer nofollow' : undefined}\n className=\"text-(--ink) underline decoration-[var(--rule-2)] underline-offset-4 transition-[text-decoration-color] duration-(--duration-fast) hover:decoration-[var(--ink)]\"\n >\n {node.children.map((child, index) => renderInline(child, index, context))}\n {outbound && context.markExternalLinks ? <LinkArrow /> : null}\n </a>\n )\n }\n }\n}\n\n/**\n * A list item's blocks, unwrapped when there is only prose in it.\n *\n * A one-paragraph item renders its inline content directly, so a tight list\n * does not put a `<p>` inside every `<li>` — which is what Markdown means by a\n * tight list, and what keeps the item's line spacing the list's rather than the\n * paragraph's.\n */\nfunction renderItem(blocks: MarkdownNode[], key: number, context: RenderContext): ReactNode {\n const first = blocks[0]\n const only = blocks.length === 1 && first?.type === 'paragraph' ? first : undefined\n return (\n <li key={key} className=\"relative\">\n {only\n ? only.children.map((child, i) => renderInline(child, i, context))\n : blocks.map((block, i) => renderBlock(block, i, context))}\n </li>\n )\n}\n\nfunction renderBlock(node: MarkdownNode, key: number, context: RenderContext): ReactNode {\n switch (node.type) {\n case 'heading': {\n // Clamped, not wrapped: a level past six has nowhere to go, and an <h7>\n // is a <span> as far as every screen reader is concerned.\n const level = Math.min(6, node.level + context.headingLevelStart - 1) as MarkdownHeadingLevel\n return (\n <Heading key={key} level={level} id={headingId(textOf(node.children), context)}>\n {node.children.map((child, index) => renderInline(child, index, context))}\n </Heading>\n )\n }\n case 'paragraph':\n return (\n <Text key={key}>{node.children.map((child, index) => renderInline(child, index, context))}</Text>\n )\n case 'code':\n return <CodeBlock key={key} code={node.value} lang={node.lang} />\n case 'rule':\n // Not decorative: a thematic break is a change of subject the author\n // wrote, so it is announced rather than drawn and hidden.\n return <Separator key={key} weight=\"edge\" decorative={false} />\n case 'quote':\n return (\n <blockquote\n key={key}\n className=\"m-0 border-s-2 border-(--rule-hard) ps-[1.25em] font-serif text-[length:var(--fs-item)] leading-[1.55] text-(--ink) [&>*+*]:mt-[0.6em]\"\n >\n {node.children.map((child, i) => renderBlock(child, i, context))}\n </blockquote>\n )\n case 'list':\n return node.ordered ? (\n <ol\n key={key}\n className=\"m-0 list-decimal ps-[1.4em] text-(--ink-2) marker:text-(--ink-3-aa) [&>li+li]:mt-[0.4em]\"\n >\n {node.items.map((item, i) => renderItem(item, i, context))}\n </ol>\n ) : (\n // The marker is a hairline dash drawn on `::before`, not a disc: the\n // system's only filled circle is a status dot. `article.css` draws the\n // same dash on the same pseudo-element, so markdown dropped into an\n // Article coincides with the stylesheet rather than doubling it.\n <ul\n key={key}\n className=\"m-0 list-none ps-[1.15em] text-(--ink-2) [&>li+li]:mt-[0.4em] [&>li]:before:absolute [&>li]:before:start-[-1.15em] [&>li]:before:top-[0.85em] [&>li]:before:h-px [&>li]:before:w-[0.55em] [&>li]:before:bg-(--rule-2) [&>li]:before:content-['']\"\n >\n {node.items.map((item, i) => renderItem(item, i, context))}\n </ul>\n )\n }\n}\n\nexport interface MarkdownProps {\n /** The Markdown source. A string — not nodes, not HTML. */\n children: string\n /**\n * The level the document's top heading renders at.\n *\n * Markdown is written as a document, so its `#` is an `<h1>`. Dropped into a\n * page that already has one — inside an `<h2>` section, in a card, under a\n * dialog's title — that produces two first-level headings and an outline a\n * screen reader cannot navigate. Set this to the level BELOW the heading the\n * content sits under, and every level in the document shifts with it.\n */\n headingLevelStart?: MarkdownHeadingLevel\n /**\n * Namespaces the generated heading ids.\n *\n * Two documents on one page both containing \"Installation\" would otherwise\n * both claim `#installation`, and a fragment link would land on whichever the\n * browser found first.\n */\n idPrefix?: string\n /**\n * Adds the system's outbound arrow to links that leave for another site.\n *\n * Off by default, and opt-in rather than automatic for two reasons. The mark\n * is an addition to a sentence the component did not write, which is a\n * different kind of act from styling one. And `Markdown` has no idea what\n * origin it is being rendered on, so \"leaves for another site\" can only mean\n * \"carries an `http`/`https` scheme\" — an absolute link back to your own\n * domain gets the arrow too. A caller who knows the shape of their content\n * turns it on; one who does not should not have it turned on for them.\n *\n * The `rel` on an outbound link is NOT opt-in and cannot be turned off: it\n * is the security half of the same boundary as the scheme check.\n */\n markExternalLinks?: boolean\n /**\n * Bring your own parser.\n *\n * Anything that turns the source into `MarkdownNode`s: markdown-it, remark,\n * or a pipeline that already has an AST and only needs mapping. The node\n * shapes are exported alongside the component as `MarkdownNode`,\n * `MarkdownInline` and `MarkdownHeadingLevel`. The built-in parser covers the\n * subset this system styles; this is the seam for tables, footnotes, task\n * lists and everything else.\n */\n parse?: (source: string) => MarkdownNode[]\n}\n\n/**\n * A Markdown string, rendered as this system's components.\n *\n * The gap it fills is content nobody on this side wrote: a comment, a README, a\n * model's answer, a description out of a database. `Article` cannot take those\n * — it renders HTML through `dangerouslySetInnerHTML` and is documented as\n * trusted-input-only — and until now the alternative was a `<pre>` or a\n * sanitiser bolted onto a styling primitive.\n *\n * IT IS NOT AN `Article`, and the two do not merge. `Markdown` turns a string\n * into nodes; `Article` is the reading column those nodes can sit in. It\n * renders a FRAGMENT rather than a wrapper, which is what makes that nesting\n * work — `Article`'s rhythm is a direct-child combinator, so any element\n * between the two, `display: contents` included, would cost every paragraph\n * its spacing.\n *\n * ── It brings type and colour, and no vertical rhythm ──\n *\n * The nodes carry the system's faces and inks on their own, so one of these\n * looks right in isolation. It is not SPACED in isolation: every node renders\n * `m-0`, and a fragment has no box of its own to put a gap in. Nor does a\n * uniform `gap` on a container reproduce it, because prose spacing is not\n * uniform — `article.css` gives a heading 2.25em above and 0.75em below, which\n * is what sits a heading with the paragraph it introduces rather than midway\n * between two. For anything longer than a sentence — a comment, an answer, a\n * README — put it in `<Article as=\"div\">` and get the real rhythm for the cost\n * of one element.\n *\n * ── A fenced code block brings a client component with it ──\n *\n * The renderer is plain functions and has no state, but a fenced block renders\n * `CodeBlock`, which is `'use client'` and arrives with `useState`,\n * `useEffect` and two icons. That is correct — the copy button is the reason\n * the block exists — but \"server-rendered\" only holds for content with no code\n * in it, which is worth knowing before this goes into a route that has no\n * client bundle yet.\n *\n * ── Why there is no parser in `dependencies` ──\n *\n * Three options, and the decision is written here because it is the one a\n * reader will want to argue with.\n *\n * 1. BUNDLE A PARSER. markdown-it is the obvious pick, and the documentation\n * site already depends on it — but the site is an app and this is a\n * library, where the dependency list is part of the contract every consumer\n * signs. Measured with the same esbuild pass `check:size` uses, markdown-it\n * is 110.7 kB minified against the 38.9 kB this package had left under its\n * bundle budget. It is not close, and \"we will raise the budget\" is how a\n * design system becomes the largest thing on a page.\n * 2. TAKE PRE-PARSED HTML, the way `Article` does. Cheapest, and wrong for the\n * case this exists for: HTML from an untrusted author is the input this is\n * supposed to make safe, and it cannot carry `headingLevelStart` or stable\n * heading ids without the component parsing markup back apart to find them.\n * 3. PARSE THE SUBSET WE STYLE, and take a `parse` function for the rest.\n * Chosen. The block grammar is line-based and small (see `parse.ts`), it\n * emits React elements rather than markup — so there is no\n * `dangerouslySetInnerHTML` in this path at all, no sanitiser to configure\n * and no sanitiser to get wrong — and a consumer who needs tables,\n * footnotes or task lists passes `parse` and keeps every other promise the\n * component makes.\n *\n * Headings carry an `id` slugged from their own text and deduplicated within\n * the document, so a table of contents can link to them without the caller\n * post-processing the DOM to find out where they went.\n *\n * A link in the source is validated here, at the boundary: a scheme that is not\n * `http`, `https`, `mailto` or `tel` never becomes a link, and one that leaves\n * for another site carries `rel=\"noreferrer nofollow\"` — so an author the page\n * does not trust cannot spend its ranking or read its URL out of the `Referer`.\n *\n * @example\n * // A comment body or an answer: the reading column is what spaces it.\n * <Article as=\"div\">\n * <Markdown headingLevelStart={3} idPrefix=\"answer\">{answer}</Markdown>\n * </Article>\n * @example\n * // A whole document, in the column it was written for.\n * <Article><Markdown>{readme}</Markdown></Article>\n * @example\n * // Tables and footnotes, via the consumer's own pipeline.\n * <Markdown parse={(source) => toMarkdownNodes(md.parse(source, {}))}>{post}</Markdown>\n */\nexport function Markdown({\n children,\n headingLevelStart = 1,\n idPrefix,\n markExternalLinks = false,\n parse = parseMarkdown,\n}: MarkdownProps) {\n const context: RenderContext = { headingLevelStart, idPrefix, markExternalLinks, taken: new Map() }\n const nodes = typeof children === 'string' ? parse(children) : []\n\n return <Fragment>{nodes.map((node, index) => renderBlock(node, index, context))}</Fragment>\n}\n\nexport default Markdown\n"],"mappings":"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","names":["parseMarkdown","slugify"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MarkdownInline } from './nodes.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The schemes a link is allowed to carry, and the reason this function exists
|
|
5
|
+
* at all.
|
|
6
|
+
*
|
|
7
|
+
* `Markdown` renders strings nobody on this side wrote — a comment, a README,
|
|
8
|
+
* a model's answer — so the href is untrusted input arriving at a system
|
|
9
|
+
* boundary and is validated there (HAR-SEC-002). A URL with no scheme is
|
|
10
|
+
* relative and cannot reach a scheme handler, so it passes; a URL WITH one has
|
|
11
|
+
* to be on this list, which is what turns `[click](javascript:…)` into plain
|
|
12
|
+
* text instead of a working control.
|
|
13
|
+
*
|
|
14
|
+
* @returns the href, or `undefined` when it must not be linked.
|
|
15
|
+
*/
|
|
16
|
+
declare function safeHref(raw: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a validated href can leave this origin.
|
|
19
|
+
*
|
|
20
|
+
* `safeHref` decides what may be linked at all; this decides what a link is
|
|
21
|
+
* allowed to take with it. Only `http` and `https` reach another site — a
|
|
22
|
+
* relative href resolves against the page, and `mailto:` and `tel:` hand the
|
|
23
|
+
* URL to a handler rather than to a document that could read a `Referer`.
|
|
24
|
+
*/
|
|
25
|
+
declare function isOutbound(href: string): boolean;
|
|
26
|
+
/** One run of text, as inline nodes. */
|
|
27
|
+
declare function parseInline(text: string): MarkdownInline[];
|
|
28
|
+
|
|
29
|
+
export { isOutbound, parseInline, safeHref };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function safeHref(raw) {
|
|
2
|
+
const href = raw.trim();
|
|
3
|
+
if (href === "") return void 0;
|
|
4
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(href);
|
|
5
|
+
if (!scheme) return href;
|
|
6
|
+
return ["http", "https", "mailto", "tel"].includes((scheme[1] ?? "").toLowerCase()) ? href : void 0;
|
|
7
|
+
}
|
|
8
|
+
function isOutbound(href) {
|
|
9
|
+
return /^https?:/i.test(href);
|
|
10
|
+
}
|
|
11
|
+
const INLINE = new RegExp(
|
|
12
|
+
[
|
|
13
|
+
// `\x60` is a backtick. Written as an escape because these are raw strings
|
|
14
|
+
// in a template literal, where a real backtick needs escaping and the raw
|
|
15
|
+
// form then keeps the backslash — which is a different pattern than the one
|
|
16
|
+
// it looks like.
|
|
17
|
+
String.raw`(?<escape>\\[\\\x60*_{}[\]()#+\-.!>])`,
|
|
18
|
+
String.raw`(?<ticks>\x60+)(?<code>[\s\S]*?)\k<ticks>`,
|
|
19
|
+
String.raw`!\[(?<alt>[^\]]*)\]\((?<src>[^)\s]+)\)`,
|
|
20
|
+
String.raw`\[(?<text>[^\]]*)\]\((?<href>[^)\s]+)\)`,
|
|
21
|
+
String.raw`\*\*(?<strongStar>[\s\S]+?)\*\*`,
|
|
22
|
+
String.raw`(?<![A-Za-z0-9])__(?<strongUnderscore>[\s\S]+?)__(?![A-Za-z0-9])`,
|
|
23
|
+
String.raw`\*(?<emphasisStar>[^*\n][\s\S]*?)\*`,
|
|
24
|
+
String.raw`(?<![A-Za-z0-9])_(?<emphasisUnderscore>[^_\n][\s\S]*?)_(?![A-Za-z0-9])`
|
|
25
|
+
].join("|"),
|
|
26
|
+
"g"
|
|
27
|
+
);
|
|
28
|
+
function tokenOf(groups) {
|
|
29
|
+
if (groups.escape !== void 0) return { type: "text", value: groups.escape.slice(1) };
|
|
30
|
+
if (groups.code !== void 0) return { type: "code", value: groups.code.trim() };
|
|
31
|
+
if (groups.src !== void 0) {
|
|
32
|
+
const src = safeHref(groups.src);
|
|
33
|
+
return src ? { type: "image", src, alt: groups.alt ?? "" } : { type: "text", value: groups.alt ?? "" };
|
|
34
|
+
}
|
|
35
|
+
if (groups.href !== void 0) {
|
|
36
|
+
const href = safeHref(groups.href);
|
|
37
|
+
const children = parseInline(groups.text ?? "");
|
|
38
|
+
return href ? { type: "link", href, children } : { type: "text", value: groups.text ?? "" };
|
|
39
|
+
}
|
|
40
|
+
const strong = groups.strongStar ?? groups.strongUnderscore;
|
|
41
|
+
if (strong !== void 0) return { type: "strong", children: parseInline(strong) };
|
|
42
|
+
return {
|
|
43
|
+
type: "emphasis",
|
|
44
|
+
children: parseInline(groups.emphasisStar ?? groups.emphasisUnderscore ?? "")
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function merge(nodes) {
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const node of nodes) {
|
|
50
|
+
const last = out[out.length - 1];
|
|
51
|
+
if (node.type === "text" && node.value === "") continue;
|
|
52
|
+
if (node.type === "text" && last?.type === "text") last.value += node.value;
|
|
53
|
+
else out.push(node);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function parseInline(text) {
|
|
58
|
+
const nodes = [];
|
|
59
|
+
let cursor = 0;
|
|
60
|
+
for (const match of text.matchAll(INLINE)) {
|
|
61
|
+
const at = match.index;
|
|
62
|
+
if (at > cursor) nodes.push({ type: "text", value: text.slice(cursor, at) });
|
|
63
|
+
nodes.push(tokenOf(match.groups ?? {}));
|
|
64
|
+
cursor = at + match[0].length;
|
|
65
|
+
}
|
|
66
|
+
if (cursor < text.length) nodes.push({ type: "text", value: text.slice(cursor) });
|
|
67
|
+
return merge(nodes);
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
isOutbound,
|
|
71
|
+
parseInline,
|
|
72
|
+
safeHref
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=inline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Markdown/inline.ts"],"sourcesContent":["import type { MarkdownInline } from './nodes'\n\n/**\n * The schemes a link is allowed to carry, and the reason this function exists\n * at all.\n *\n * `Markdown` renders strings nobody on this side wrote — a comment, a README,\n * a model's answer — so the href is untrusted input arriving at a system\n * boundary and is validated there (HAR-SEC-002). A URL with no scheme is\n * relative and cannot reach a scheme handler, so it passes; a URL WITH one has\n * to be on this list, which is what turns `[click](javascript:…)` into plain\n * text instead of a working control.\n *\n * @returns the href, or `undefined` when it must not be linked.\n */\nexport function safeHref(raw: string): string | undefined {\n const href = raw.trim()\n if (href === '') return undefined\n const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(href)\n if (!scheme) return href\n return ['http', 'https', 'mailto', 'tel'].includes((scheme[1] ?? '').toLowerCase())\n ? href\n : undefined\n}\n\n/**\n * Whether a validated href can leave this origin.\n *\n * `safeHref` decides what may be linked at all; this decides what a link is\n * allowed to take with it. Only `http` and `https` reach another site — a\n * relative href resolves against the page, and `mailto:` and `tel:` hand the\n * URL to a handler rather than to a document that could read a `Referer`.\n */\nexport function isOutbound(href: string): boolean {\n return /^https?:/i.test(href)\n}\n\n/**\n * Every inline construct, in one ordered alternation.\n *\n * Order is the precedence: an escape beats everything, a code span beats the\n * emphasis markers inside it, an image beats the link it otherwise looks like,\n * and `**` beats `*`. One pass with `matchAll` rather than a nest of\n * `String.replace` calls, because replace-based Markdown is how a link's URL\n * ends up italicised by an underscore in a query string.\n *\n * The `_` forms carry word-boundary guards and the `*` forms do not, which is\n * the rule every implementation converges on: `snake_case_name` is an\n * identifier, and `a*b*c` is emphasis.\n */\nconst INLINE = new RegExp(\n [\n // `\\x60` is a backtick. Written as an escape because these are raw strings\n // in a template literal, where a real backtick needs escaping and the raw\n // form then keeps the backslash — which is a different pattern than the one\n // it looks like.\n String.raw`(?<escape>\\\\[\\\\\\x60*_{}[\\]()#+\\-.!>])`,\n String.raw`(?<ticks>\\x60+)(?<code>[\\s\\S]*?)\\k<ticks>`,\n String.raw`!\\[(?<alt>[^\\]]*)\\]\\((?<src>[^)\\s]+)\\)`,\n String.raw`\\[(?<text>[^\\]]*)\\]\\((?<href>[^)\\s]+)\\)`,\n String.raw`\\*\\*(?<strongStar>[\\s\\S]+?)\\*\\*`,\n String.raw`(?<![A-Za-z0-9])__(?<strongUnderscore>[\\s\\S]+?)__(?![A-Za-z0-9])`,\n String.raw`\\*(?<emphasisStar>[^*\\n][\\s\\S]*?)\\*`,\n String.raw`(?<![A-Za-z0-9])_(?<emphasisUnderscore>[^_\\n][\\s\\S]*?)_(?![A-Za-z0-9])`,\n ].join('|'),\n 'g',\n)\n\n/** One match, as the node it stands for. */\nfunction tokenOf(groups: Record<string, string | undefined>): MarkdownInline {\n if (groups.escape !== undefined) return { type: 'text', value: groups.escape.slice(1) }\n if (groups.code !== undefined) return { type: 'code', value: groups.code.trim() }\n\n if (groups.src !== undefined) {\n const src = safeHref(groups.src)\n // A blocked image degrades to its own alt text, which is the description\n // the author wrote for exactly this situation.\n return src ? { type: 'image', src, alt: groups.alt ?? '' } : { type: 'text', value: groups.alt ?? '' }\n }\n\n if (groups.href !== undefined) {\n const href = safeHref(groups.href)\n const children = parseInline(groups.text ?? '')\n return href ? { type: 'link', href, children } : { type: 'text', value: groups.text ?? '' }\n }\n\n const strong = groups.strongStar ?? groups.strongUnderscore\n if (strong !== undefined) return { type: 'strong', children: parseInline(strong) }\n\n return {\n type: 'emphasis',\n children: parseInline(groups.emphasisStar ?? groups.emphasisUnderscore ?? ''),\n }\n}\n\n/** Adjacent text runs are one run; an empty one is nothing at all. */\nfunction merge(nodes: MarkdownInline[]): MarkdownInline[] {\n const out: MarkdownInline[] = []\n for (const node of nodes) {\n const last = out[out.length - 1]\n if (node.type === 'text' && node.value === '') continue\n if (node.type === 'text' && last?.type === 'text') last.value += node.value\n else out.push(node)\n }\n return out\n}\n\n/** One run of text, as inline nodes. */\nexport function parseInline(text: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = []\n let cursor = 0\n\n for (const match of text.matchAll(INLINE)) {\n const at = match.index\n if (at > cursor) nodes.push({ type: 'text', value: text.slice(cursor, at) })\n nodes.push(tokenOf(match.groups ?? {}))\n cursor = at + match[0].length\n }\n if (cursor < text.length) nodes.push({ type: 'text', value: text.slice(cursor) })\n\n return merge(nodes)\n}\n"],"mappings":"AAeO,SAAS,SAAS,KAAiC;AACxD,QAAM,OAAO,IAAI,KAAK;AACtB,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,SAAS,yBAAyB,KAAK,IAAI;AACjD,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,CAAC,QAAQ,SAAS,UAAU,KAAK,EAAE,UAAU,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,IAC9E,OACA;AACN;AAUO,SAAS,WAAW,MAAuB;AAChD,SAAO,YAAY,KAAK,IAAI;AAC9B;AAeA,MAAM,SAAS,IAAI;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT,EAAE,KAAK,GAAG;AAAA,EACV;AACF;AAGA,SAAS,QAAQ,QAA4D;AAC3E,MAAI,OAAO,WAAW,OAAW,QAAO,EAAE,MAAM,QAAQ,OAAO,OAAO,OAAO,MAAM,CAAC,EAAE;AACtF,MAAI,OAAO,SAAS,OAAW,QAAO,EAAE,MAAM,QAAQ,OAAO,OAAO,KAAK,KAAK,EAAE;AAEhF,MAAI,OAAO,QAAQ,QAAW;AAC5B,UAAM,MAAM,SAAS,OAAO,GAAG;AAG/B,WAAO,MAAM,EAAE,MAAM,SAAS,KAAK,KAAK,OAAO,OAAO,GAAG,IAAI,EAAE,MAAM,QAAQ,OAAO,OAAO,OAAO,GAAG;AAAA,EACvG;AAEA,MAAI,OAAO,SAAS,QAAW;AAC7B,UAAM,OAAO,SAAS,OAAO,IAAI;AACjC,UAAM,WAAW,YAAY,OAAO,QAAQ,EAAE;AAC9C,WAAO,OAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,IAAI,EAAE,MAAM,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAAA,EAC5F;AAEA,QAAM,SAAS,OAAO,cAAc,OAAO;AAC3C,MAAI,WAAW,OAAW,QAAO,EAAE,MAAM,UAAU,UAAU,YAAY,MAAM,EAAE;AAEjF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,YAAY,OAAO,gBAAgB,OAAO,sBAAsB,EAAE;AAAA,EAC9E;AACF;AAGA,SAAS,MAAM,OAA2C;AACxD,QAAM,MAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,QAAI,KAAK,SAAS,UAAU,KAAK,UAAU,GAAI;AAC/C,QAAI,KAAK,SAAS,UAAU,MAAM,SAAS,OAAQ,MAAK,SAAS,KAAK;AAAA,QACjE,KAAI,KAAK,IAAI;AAAA,EACpB;AACA,SAAO;AACT;AAGO,SAAS,YAAY,MAAgC;AAC1D,QAAM,QAA0B,CAAC;AACjC,MAAI,SAAS;AAEb,aAAW,SAAS,KAAK,SAAS,MAAM,GAAG;AACzC,UAAM,KAAK,MAAM;AACjB,QAAI,KAAK,OAAQ,OAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,EAAE,EAAE,CAAC;AAC3E,UAAM,KAAK,QAAQ,MAAM,UAAU,CAAC,CAAC,CAAC;AACtC,aAAS,KAAK,MAAM,CAAC,EAAE;AAAA,EACzB;AACA,MAAI,SAAS,KAAK,OAAQ,OAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,MAAM,EAAE,CAAC;AAEhF,SAAO,MAAM,KAAK;AACpB;","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The node shapes `Markdown` renders, and the contract a custom `parse`
|
|
3
|
+
* function has to meet.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately small. It is not an attempt at mdast: it is the set of blocks
|
|
6
|
+
* this design system has an opinion about, which is the set `article.css`
|
|
7
|
+
* already styles. Anything outside it — footnotes, tables, task lists,
|
|
8
|
+
* definition lists, embedded HTML — is a `parse` away, and the point of naming
|
|
9
|
+
* the shapes here rather than accepting a foreign AST is that a consumer
|
|
10
|
+
* bringing markdown-it or remark maps into a surface the package can promise
|
|
11
|
+
* to keep rendering.
|
|
12
|
+
*/
|
|
13
|
+
type MarkdownHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
14
|
+
/** A run of text, and the four things that can happen inside one. */
|
|
15
|
+
type MarkdownInline = {
|
|
16
|
+
type: 'text';
|
|
17
|
+
value: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'code';
|
|
20
|
+
value: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'strong';
|
|
23
|
+
children: MarkdownInline[];
|
|
24
|
+
} | {
|
|
25
|
+
type: 'emphasis';
|
|
26
|
+
children: MarkdownInline[];
|
|
27
|
+
} | {
|
|
28
|
+
type: 'link';
|
|
29
|
+
href: string;
|
|
30
|
+
children: MarkdownInline[];
|
|
31
|
+
} | {
|
|
32
|
+
type: 'image';
|
|
33
|
+
src: string;
|
|
34
|
+
alt: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A block.
|
|
38
|
+
*
|
|
39
|
+
* `list` items and `quote` children are blocks in turn, which is what makes a
|
|
40
|
+
* nested list, or a fence inside a bullet, fall out of the same recursion
|
|
41
|
+
* rather than needing a case of its own.
|
|
42
|
+
*/
|
|
43
|
+
type MarkdownNode = {
|
|
44
|
+
type: 'heading';
|
|
45
|
+
level: MarkdownHeadingLevel;
|
|
46
|
+
children: MarkdownInline[];
|
|
47
|
+
} | {
|
|
48
|
+
type: 'paragraph';
|
|
49
|
+
children: MarkdownInline[];
|
|
50
|
+
} | {
|
|
51
|
+
type: 'code';
|
|
52
|
+
value: string;
|
|
53
|
+
lang?: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: 'list';
|
|
56
|
+
ordered: boolean;
|
|
57
|
+
items: MarkdownNode[][];
|
|
58
|
+
} | {
|
|
59
|
+
type: 'quote';
|
|
60
|
+
children: MarkdownNode[];
|
|
61
|
+
} | {
|
|
62
|
+
type: 'rule';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type { MarkdownHeadingLevel, MarkdownInline, MarkdownNode };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=nodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MarkdownNode } from './nodes.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A Markdown string, as nodes.
|
|
5
|
+
*
|
|
6
|
+
* The supported subset, stated so nobody has to find its edges by experiment:
|
|
7
|
+
* ATX headings, paragraphs, fenced code with an info string, blockquotes,
|
|
8
|
+
* ordered and unordered lists including nesting, thematic breaks — and inline
|
|
9
|
+
* emphasis, strong, code spans, links, images and backslash escapes.
|
|
10
|
+
*
|
|
11
|
+
* What is NOT here, and is deliberate: tables, footnotes, task lists, setext
|
|
12
|
+
* headings, reference links, hard line breaks, and raw HTML. Raw HTML is the
|
|
13
|
+
* one that matters — it is left as text rather than rendered, so this path has
|
|
14
|
+
* no `dangerouslySetInnerHTML` anywhere in it and cannot be talked into one. A
|
|
15
|
+
* document that needs the rest brings its own parser through `Markdown`'s
|
|
16
|
+
* `parse` prop.
|
|
17
|
+
*
|
|
18
|
+
* Never throws. Malformed input is not an error condition here — it is Tuesday
|
|
19
|
+
* — so an unterminated fence, a stray bracket or an empty string each produce
|
|
20
|
+
* the nodes they can and no more.
|
|
21
|
+
*/
|
|
22
|
+
declare function parseMarkdown(source: string): MarkdownNode[];
|
|
23
|
+
|
|
24
|
+
export { parseMarkdown };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { parseInline } from "./inline.js";
|
|
2
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})\s*([^\s`]*)\s*$/;
|
|
3
|
+
const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
4
|
+
const RULE = /^ {0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
5
|
+
const QUOTE = /^ {0,3}>\s?(.*)$/;
|
|
6
|
+
const ITEM = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
|
|
7
|
+
const lineAt = (lines, index) => lines[index] ?? "";
|
|
8
|
+
const opensBlock = (line) => HEADING.test(line) || RULE.test(line) || FENCE.test(line) || QUOTE.test(line) || ITEM.test(line);
|
|
9
|
+
const closesFence = (line, marker) => new RegExp(`^ {0,3}${marker.charAt(0)}{${marker.length},}\\s*$`).test(line);
|
|
10
|
+
function takeFence(lines, start, out) {
|
|
11
|
+
const fence = FENCE.exec(lineAt(lines, start));
|
|
12
|
+
if (!fence) return start + 1;
|
|
13
|
+
const [, marker = "", info = ""] = fence;
|
|
14
|
+
const body = [];
|
|
15
|
+
let i = start + 1;
|
|
16
|
+
while (i < lines.length && !closesFence(lineAt(lines, i), marker)) {
|
|
17
|
+
body.push(lineAt(lines, i));
|
|
18
|
+
i += 1;
|
|
19
|
+
}
|
|
20
|
+
out.push({ type: "code", value: body.join("\n"), lang: info || void 0 });
|
|
21
|
+
return i < lines.length ? i + 1 : i;
|
|
22
|
+
}
|
|
23
|
+
function takeQuote(lines, start, out) {
|
|
24
|
+
const body = [];
|
|
25
|
+
let i = start;
|
|
26
|
+
while (i < lines.length) {
|
|
27
|
+
const quoted = QUOTE.exec(lineAt(lines, i));
|
|
28
|
+
if (!quoted) break;
|
|
29
|
+
body.push(quoted[1] ?? "");
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
out.push({ type: "quote", children: parseBlocks(body) });
|
|
33
|
+
return i;
|
|
34
|
+
}
|
|
35
|
+
function takeList(lines, start, out) {
|
|
36
|
+
const first = ITEM.exec(lineAt(lines, start));
|
|
37
|
+
if (!first) return start + 1;
|
|
38
|
+
const baseIndent = (first[1] ?? "").length;
|
|
39
|
+
const ordered = /\d/.test(first[2] ?? "");
|
|
40
|
+
const items = [];
|
|
41
|
+
let current;
|
|
42
|
+
let indent = 0;
|
|
43
|
+
let i = start;
|
|
44
|
+
while (i < lines.length) {
|
|
45
|
+
const line = lineAt(lines, i);
|
|
46
|
+
const item = ITEM.exec(line);
|
|
47
|
+
const [, gap = "", bullet = "", rest = ""] = item ?? [];
|
|
48
|
+
if (item && gap.length <= baseIndent) {
|
|
49
|
+
if (ordered !== /\d/.test(bullet)) break;
|
|
50
|
+
if (current) items.push(parseBlocks(current));
|
|
51
|
+
indent = gap.length + bullet.length + 1;
|
|
52
|
+
current = [rest];
|
|
53
|
+
i += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (current !== void 0 && (line.trim() === "" || line.startsWith(" ".repeat(indent)))) {
|
|
57
|
+
current.push(line.slice(indent));
|
|
58
|
+
i += 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (current) items.push(parseBlocks(current));
|
|
64
|
+
out.push({ type: "list", ordered, items });
|
|
65
|
+
return i;
|
|
66
|
+
}
|
|
67
|
+
function takeParagraph(lines, start, out) {
|
|
68
|
+
const body = [];
|
|
69
|
+
let i = start;
|
|
70
|
+
for (let line = lineAt(lines, i); i < lines.length; line = lineAt(lines, i)) {
|
|
71
|
+
if (line.trim() === "" || opensBlock(line)) break;
|
|
72
|
+
body.push(line.trim());
|
|
73
|
+
i += 1;
|
|
74
|
+
}
|
|
75
|
+
if (body.length === 0) return start + 1;
|
|
76
|
+
out.push({ type: "paragraph", children: parseInline(body.join(" ")) });
|
|
77
|
+
return i;
|
|
78
|
+
}
|
|
79
|
+
function parseBlocks(lines) {
|
|
80
|
+
const out = [];
|
|
81
|
+
let i = 0;
|
|
82
|
+
while (i < lines.length) {
|
|
83
|
+
const line = lineAt(lines, i);
|
|
84
|
+
const heading = HEADING.exec(line);
|
|
85
|
+
if (line.trim() === "") i += 1;
|
|
86
|
+
else if (FENCE.test(line)) i = takeFence(lines, i, out);
|
|
87
|
+
else if (heading) {
|
|
88
|
+
out.push({
|
|
89
|
+
type: "heading",
|
|
90
|
+
level: (heading[1] ?? "#").length,
|
|
91
|
+
children: parseInline(heading[2] ?? "")
|
|
92
|
+
});
|
|
93
|
+
i += 1;
|
|
94
|
+
} else if (RULE.test(line)) {
|
|
95
|
+
out.push({ type: "rule" });
|
|
96
|
+
i += 1;
|
|
97
|
+
} else if (QUOTE.test(line)) i = takeQuote(lines, i, out);
|
|
98
|
+
else if (ITEM.test(line)) i = takeList(lines, i, out);
|
|
99
|
+
else i = takeParagraph(lines, i, out);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function parseMarkdown(source) {
|
|
104
|
+
if (typeof source !== "string" || source.trim() === "") return [];
|
|
105
|
+
return parseBlocks(source.replace(/\r\n?/g, "\n").split("\n"));
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
parseMarkdown
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Markdown/parse.ts"],"sourcesContent":["import { parseInline } from './inline'\nimport type { MarkdownHeadingLevel, MarkdownNode } from './nodes'\n\n/**\n * The block grammar, as the six line shapes it recognises.\n *\n * Line-based rather than character-based, which is what keeps this small enough\n * to live in a design system: Markdown's blocks are decided by how a line\n * STARTS, and the interesting half — emphasis, links, code spans — is the\n * inline pass, which runs once per block.\n */\nconst FENCE = /^ {0,3}(`{3,}|~{3,})\\s*([^\\s`]*)\\s*$/\nconst HEADING = /^ {0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/\nconst RULE = /^ {0,3}(?:-{3,}|\\*{3,}|_{3,})\\s*$/\nconst QUOTE = /^ {0,3}>\\s?(.*)$/\nconst ITEM = /^(\\s*)([-*+]|\\d{1,9}[.)])\\s+(.*)$/\n\n/**\n * A line, or the empty string past the end.\n *\n * `noUncheckedIndexedAccess` is on, and every scanner below walks off the end\n * of the array by construction — reading one line ahead is how a list decides\n * whether a blank line ended it. An empty string matches none of the patterns,\n * which is exactly the answer wanted there.\n */\nconst lineAt = (lines: string[], index: number): string => lines[index] ?? ''\n\nconst opensBlock = (line: string): boolean =>\n HEADING.test(line) || RULE.test(line) || FENCE.test(line) || QUOTE.test(line) || ITEM.test(line)\n\n/** A fence closes on the same character, at the same length or longer. */\nconst closesFence = (line: string, marker: string): boolean =>\n new RegExp(`^ {0,3}${marker.charAt(0)}{${marker.length},}\\\\s*$`).test(line)\n\nfunction takeFence(lines: string[], start: number, out: MarkdownNode[]): number {\n const fence = FENCE.exec(lineAt(lines, start))\n if (!fence) return start + 1\n const [, marker = '', info = ''] = fence\n\n const body: string[] = []\n let i = start + 1\n while (i < lines.length && !closesFence(lineAt(lines, i), marker)) {\n body.push(lineAt(lines, i))\n i += 1\n }\n out.push({ type: 'code', value: body.join('\\n'), lang: info || undefined })\n\n // An unclosed fence runs to the end of the document rather than throwing.\n // That is what CommonMark specifies, and it is also the exact shape of a\n // model's answer that hit its token limit mid-snippet.\n return i < lines.length ? i + 1 : i\n}\n\nfunction takeQuote(lines: string[], start: number, out: MarkdownNode[]): number {\n const body: string[] = []\n let i = start\n while (i < lines.length) {\n const quoted = QUOTE.exec(lineAt(lines, i))\n if (!quoted) break\n body.push(quoted[1] ?? '')\n i += 1\n }\n // Recursive, so a list, a fence or a nested quote inside a blockquote is the\n // same code path rather than five more cases.\n out.push({ type: 'quote', children: parseBlocks(body) })\n return i\n}\n\nfunction takeList(lines: string[], start: number, out: MarkdownNode[]): number {\n const first = ITEM.exec(lineAt(lines, start))\n if (!first) return start + 1\n\n const baseIndent = (first[1] ?? '').length\n const ordered = /\\d/.test(first[2] ?? '')\n const items: MarkdownNode[][] = []\n let current: string[] | undefined\n let indent = 0\n let i = start\n\n while (i < lines.length) {\n const line = lineAt(lines, i)\n const item = ITEM.exec(line)\n const [, gap = '', bullet = '', rest = ''] = item ?? []\n\n if (item && gap.length <= baseIndent) {\n // A bullet directly under a number is a second list, not a stray item.\n if (ordered !== /\\d/.test(bullet)) break\n if (current) items.push(parseBlocks(current))\n indent = gap.length + bullet.length + 1\n current = [rest]\n i += 1\n continue\n }\n if (current !== undefined && (line.trim() === '' || line.startsWith(' '.repeat(indent)))) {\n current.push(line.slice(indent))\n i += 1\n continue\n }\n break\n }\n\n if (current) items.push(parseBlocks(current))\n out.push({ type: 'list', ordered, items })\n return i\n}\n\nfunction takeParagraph(lines: string[], start: number, out: MarkdownNode[]): number {\n const body: string[] = []\n let i = start\n for (let line = lineAt(lines, i); i < lines.length; line = lineAt(lines, i)) {\n if (line.trim() === '' || opensBlock(line)) break\n body.push(line.trim())\n i += 1\n }\n if (body.length === 0) return start + 1\n\n // Soft line breaks join with a space, which is what Markdown means by them:\n // a paragraph hard-wrapped at 80 columns is one paragraph.\n out.push({ type: 'paragraph', children: parseInline(body.join(' ')) })\n return i\n}\n\nfunction parseBlocks(lines: string[]): MarkdownNode[] {\n const out: MarkdownNode[] = []\n let i = 0\n while (i < lines.length) {\n const line = lineAt(lines, i)\n const heading = HEADING.exec(line)\n\n if (line.trim() === '') i += 1\n else if (FENCE.test(line)) i = takeFence(lines, i, out)\n else if (heading) {\n out.push({\n type: 'heading',\n level: (heading[1] ?? '#').length as MarkdownHeadingLevel,\n children: parseInline(heading[2] ?? ''),\n })\n i += 1\n } else if (RULE.test(line)) {\n out.push({ type: 'rule' })\n i += 1\n } else if (QUOTE.test(line)) i = takeQuote(lines, i, out)\n else if (ITEM.test(line)) i = takeList(lines, i, out)\n else i = takeParagraph(lines, i, out)\n }\n return out\n}\n\n/**\n * A Markdown string, as nodes.\n *\n * The supported subset, stated so nobody has to find its edges by experiment:\n * ATX headings, paragraphs, fenced code with an info string, blockquotes,\n * ordered and unordered lists including nesting, thematic breaks — and inline\n * emphasis, strong, code spans, links, images and backslash escapes.\n *\n * What is NOT here, and is deliberate: tables, footnotes, task lists, setext\n * headings, reference links, hard line breaks, and raw HTML. Raw HTML is the\n * one that matters — it is left as text rather than rendered, so this path has\n * no `dangerouslySetInnerHTML` anywhere in it and cannot be talked into one. A\n * document that needs the rest brings its own parser through `Markdown`'s\n * `parse` prop.\n *\n * Never throws. Malformed input is not an error condition here — it is Tuesday\n * — so an unterminated fence, a stray bracket or an empty string each produce\n * the nodes they can and no more.\n */\nexport function parseMarkdown(source: string): MarkdownNode[] {\n if (typeof source !== 'string' || source.trim() === '') return []\n return parseBlocks(source.replace(/\\r\\n?/g, '\\n').split('\\n'))\n}\n"],"mappings":"AAAA,SAAS,mBAAmB;AAW5B,MAAM,QAAQ;AACd,MAAM,UAAU;AAChB,MAAM,OAAO;AACb,MAAM,QAAQ;AACd,MAAM,OAAO;AAUb,MAAM,SAAS,CAAC,OAAiB,UAA0B,MAAM,KAAK,KAAK;AAE3E,MAAM,aAAa,CAAC,SAClB,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI;AAGjG,MAAM,cAAc,CAAC,MAAc,WACjC,IAAI,OAAO,UAAU,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,MAAM,SAAS,EAAE,KAAK,IAAI;AAE5E,SAAS,UAAU,OAAiB,OAAe,KAA6B;AAC9E,QAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,KAAK,CAAC;AAC7C,MAAI,CAAC,MAAO,QAAO,QAAQ;AAC3B,QAAM,CAAC,EAAE,SAAS,IAAI,OAAO,EAAE,IAAI;AAEnC,QAAM,OAAiB,CAAC;AACxB,MAAI,IAAI,QAAQ;AAChB,SAAO,IAAI,MAAM,UAAU,CAAC,YAAY,OAAO,OAAO,CAAC,GAAG,MAAM,GAAG;AACjE,SAAK,KAAK,OAAO,OAAO,CAAC,CAAC;AAC1B,SAAK;AAAA,EACP;AACA,MAAI,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,KAAK,IAAI,GAAG,MAAM,QAAQ,OAAU,CAAC;AAK1E,SAAO,IAAI,MAAM,SAAS,IAAI,IAAI;AACpC;AAEA,SAAS,UAAU,OAAiB,OAAe,KAA6B;AAC9E,QAAM,OAAiB,CAAC;AACxB,MAAI,IAAI;AACR,SAAO,IAAI,MAAM,QAAQ;AACvB,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO,CAAC,CAAC;AAC1C,QAAI,CAAC,OAAQ;AACb,SAAK,KAAK,OAAO,CAAC,KAAK,EAAE;AACzB,SAAK;AAAA,EACP;AAGA,MAAI,KAAK,EAAE,MAAM,SAAS,UAAU,YAAY,IAAI,EAAE,CAAC;AACvD,SAAO;AACT;AAEA,SAAS,SAAS,OAAiB,OAAe,KAA6B;AAC7E,QAAM,QAAQ,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC;AAC5C,MAAI,CAAC,MAAO,QAAO,QAAQ;AAE3B,QAAM,cAAc,MAAM,CAAC,KAAK,IAAI;AACpC,QAAM,UAAU,KAAK,KAAK,MAAM,CAAC,KAAK,EAAE;AACxC,QAAM,QAA0B,CAAC;AACjC,MAAI;AACJ,MAAI,SAAS;AACb,MAAI,IAAI;AAER,SAAO,IAAI,MAAM,QAAQ;AACvB,UAAM,OAAO,OAAO,OAAO,CAAC;AAC5B,UAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,UAAM,CAAC,EAAE,MAAM,IAAI,SAAS,IAAI,OAAO,EAAE,IAAI,QAAQ,CAAC;AAEtD,QAAI,QAAQ,IAAI,UAAU,YAAY;AAEpC,UAAI,YAAY,KAAK,KAAK,MAAM,EAAG;AACnC,UAAI,QAAS,OAAM,KAAK,YAAY,OAAO,CAAC;AAC5C,eAAS,IAAI,SAAS,OAAO,SAAS;AACtC,gBAAU,CAAC,IAAI;AACf,WAAK;AACL;AAAA,IACF;AACA,QAAI,YAAY,WAAc,KAAK,KAAK,MAAM,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,IAAI;AACxF,cAAQ,KAAK,KAAK,MAAM,MAAM,CAAC;AAC/B,WAAK;AACL;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,QAAS,OAAM,KAAK,YAAY,OAAO,CAAC;AAC5C,MAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,MAAM,CAAC;AACzC,SAAO;AACT;AAEA,SAAS,cAAc,OAAiB,OAAe,KAA6B;AAClF,QAAM,OAAiB,CAAC;AACxB,MAAI,IAAI;AACR,WAAS,OAAO,OAAO,OAAO,CAAC,GAAG,IAAI,MAAM,QAAQ,OAAO,OAAO,OAAO,CAAC,GAAG;AAC3E,QAAI,KAAK,KAAK,MAAM,MAAM,WAAW,IAAI,EAAG;AAC5C,SAAK,KAAK,KAAK,KAAK,CAAC;AACrB,SAAK;AAAA,EACP;AACA,MAAI,KAAK,WAAW,EAAG,QAAO,QAAQ;AAItC,MAAI,KAAK,EAAE,MAAM,aAAa,UAAU,YAAY,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;AACrE,SAAO;AACT;AAEA,SAAS,YAAY,OAAiC;AACpD,QAAM,MAAsB,CAAC;AAC7B,MAAI,IAAI;AACR,SAAO,IAAI,MAAM,QAAQ;AACvB,UAAM,OAAO,OAAO,OAAO,CAAC;AAC5B,UAAM,UAAU,QAAQ,KAAK,IAAI;AAEjC,QAAI,KAAK,KAAK,MAAM,GAAI,MAAK;AAAA,aACpB,MAAM,KAAK,IAAI,EAAG,KAAI,UAAU,OAAO,GAAG,GAAG;AAAA,aAC7C,SAAS;AAChB,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,QAAQ,QAAQ,CAAC,KAAK,KAAK;AAAA,QAC3B,UAAU,YAAY,QAAQ,CAAC,KAAK,EAAE;AAAA,MACxC,CAAC;AACD,WAAK;AAAA,IACP,WAAW,KAAK,KAAK,IAAI,GAAG;AAC1B,UAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AACzB,WAAK;AAAA,IACP,WAAW,MAAM,KAAK,IAAI,EAAG,KAAI,UAAU,OAAO,GAAG,GAAG;AAAA,aAC/C,KAAK,KAAK,IAAI,EAAG,KAAI,SAAS,OAAO,GAAG,GAAG;AAAA,QAC/C,KAAI,cAAc,OAAO,GAAG,GAAG;AAAA,EACtC;AACA,SAAO;AACT;AAqBO,SAAS,cAAc,QAAgC;AAC5D,MAAI,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM,GAAI,QAAO,CAAC;AAChE,SAAO,YAAY,OAAO,QAAQ,UAAU,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/D;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A heading's text, as an anchor id.
|
|
3
|
+
*
|
|
4
|
+
* Exported because a table of contents has to arrive at the SAME id the
|
|
5
|
+
* rendered heading got, and the alternative is building the list by reading it
|
|
6
|
+
* back off the DOM — which a server render does not have.
|
|
7
|
+
*
|
|
8
|
+
* Letters and numbers survive in any script (`\p{L}`, not `a-z`), so a Chinese
|
|
9
|
+
* or Greek heading keeps its characters instead of slugging away to nothing and
|
|
10
|
+
* colliding with every other one on the page. `Markdown` deduplicates what does
|
|
11
|
+
* collide by appending `-2`, `-3`, and so on in document order.
|
|
12
|
+
*/
|
|
13
|
+
declare function slugify(text: string): string;
|
|
14
|
+
|
|
15
|
+
export { slugify };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Markdown/slug.ts"],"sourcesContent":["/**\n * A heading's text, as an anchor id.\n *\n * Exported because a table of contents has to arrive at the SAME id the\n * rendered heading got, and the alternative is building the list by reading it\n * back off the DOM — which a server render does not have.\n *\n * Letters and numbers survive in any script (`\\p{L}`, not `a-z`), so a Chinese\n * or Greek heading keeps its characters instead of slugging away to nothing and\n * colliding with every other one on the page. `Markdown` deduplicates what does\n * collide by appending `-2`, `-3`, and so on in document order.\n */\nexport function slugify(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n}\n"],"mappings":"AAYO,SAAS,QAAQ,MAAsB;AAC5C,SAAO,KACJ,KAAK,EACL,YAAY,EACZ,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,YAAY,EAAE;AAC3B;","names":[]}
|
|
@@ -19,6 +19,12 @@ interface NativeSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
|
|
19
19
|
* list is drawn by the operating system, so it carries none of these tokens —
|
|
20
20
|
* which is exactly why it stopped being the default.
|
|
21
21
|
*
|
|
22
|
+
* `className` sizes the WRAPPER, not the `<select>` inside it. This is the one
|
|
23
|
+
* control here that renders two elements, and the chevron is pinned to the
|
|
24
|
+
* wrapper's end edge: a width on the select alone narrowed the box and left the
|
|
25
|
+
* arrow floating at the far side of the row. The select fills whatever the
|
|
26
|
+
* wrapper is.
|
|
27
|
+
*
|
|
22
28
|
* @example
|
|
23
29
|
* <NativeSelect defaultValue="au"><option value="au">Australia</option></NativeSelect>
|
|
24
30
|
*/
|
|
@@ -11,7 +11,7 @@ function NativeSelect({
|
|
|
11
11
|
...rest
|
|
12
12
|
}) {
|
|
13
13
|
const bad = isInvalid(invalid, ariaInvalid);
|
|
14
|
-
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
15
15
|
/* @__PURE__ */ jsx(
|
|
16
16
|
"select",
|
|
17
17
|
{
|
|
@@ -20,8 +20,7 @@ function NativeSelect({
|
|
|
20
20
|
className: cn(
|
|
21
21
|
CONTROL_BASE,
|
|
22
22
|
"cursor-pointer appearance-none pe-9",
|
|
23
|
-
bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting
|
|
24
|
-
className
|
|
23
|
+
bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting
|
|
25
24
|
),
|
|
26
25
|
...rest,
|
|
27
26
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/NativeSelect/NativeSelect.tsx"],"sourcesContent":["import { ChevronDown } from 'lucide-react'\nimport type { Ref, SelectHTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\nexport interface NativeSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n ref?: Ref<HTMLSelectElement>\n}\n\n/**\n * A native `<select>`, restyled.\n *\n * The escape hatch, not the default — `Select` is the styled one. Reach for\n * this where the platform's own picker is genuinely better: a very long list on\n * a phone, a form that must work without JavaScript, a page where the last\n * kilobyte matters. The browser gives typeahead and the mobile wheel for free,\n * and those are real.\n *\n * What it cannot do is look like the rest of the system once open. The option\n * list is drawn by the operating system, so it carries none of these tokens —\n * which is exactly why it stopped being the default.\n *\n * @example\n * <NativeSelect defaultValue=\"au\"><option value=\"au\">Australia</option></NativeSelect>\n */\nexport function NativeSelect({\n invalid,\n className,\n ref,\n children,\n 'aria-invalid': ariaInvalid,\n ...rest\n}: NativeSelectProps) {\n const bad = isInvalid(invalid, ariaInvalid)\n return (\n <div className
|
|
1
|
+
{"version":3,"sources":["../../../src/components/NativeSelect/NativeSelect.tsx"],"sourcesContent":["import { ChevronDown } from 'lucide-react'\nimport type { Ref, SelectHTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\nexport interface NativeSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n ref?: Ref<HTMLSelectElement>\n}\n\n/**\n * A native `<select>`, restyled.\n *\n * The escape hatch, not the default — `Select` is the styled one. Reach for\n * this where the platform's own picker is genuinely better: a very long list on\n * a phone, a form that must work without JavaScript, a page where the last\n * kilobyte matters. The browser gives typeahead and the mobile wheel for free,\n * and those are real.\n *\n * What it cannot do is look like the rest of the system once open. The option\n * list is drawn by the operating system, so it carries none of these tokens —\n * which is exactly why it stopped being the default.\n *\n * `className` sizes the WRAPPER, not the `<select>` inside it. This is the one\n * control here that renders two elements, and the chevron is pinned to the\n * wrapper's end edge: a width on the select alone narrowed the box and left the\n * arrow floating at the far side of the row. The select fills whatever the\n * wrapper is.\n *\n * @example\n * <NativeSelect defaultValue=\"au\"><option value=\"au\">Australia</option></NativeSelect>\n */\nexport function NativeSelect({\n invalid,\n className,\n ref,\n children,\n 'aria-invalid': ariaInvalid,\n ...rest\n}: NativeSelectProps) {\n const bad = isInvalid(invalid, ariaInvalid)\n return (\n <div className={cn('relative', className)}>\n <select\n ref={ref}\n aria-invalid={ariaInvalid ?? (invalid ? true : undefined)}\n className={cn(\n CONTROL_BASE,\n 'cursor-pointer appearance-none pe-9',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n )}\n {...rest}\n >\n {children}\n </select>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n className=\"pointer-events-none absolute end-3 top-1/2 -translate-y-1/2 text-(--ink-3-aa)\"\n aria-hidden\n />\n </div>\n )\n}\n\nexport default NativeSelect\n"],"mappings":"AA2CI,SACE,KADF;AA3CJ,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AACnB,SAAS,cAAc,gBAAgB,iBAAiB;AA8BjD,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,GAAG;AACL,GAAsB;AACpB,QAAM,MAAM,UAAU,SAAS,WAAW;AAC1C,SACE,qBAAC,SAAI,WAAW,GAAG,YAAY,SAAS,GACtC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,gBAAc,gBAAgB,UAAU,OAAO;AAAA,QAC/C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,MAAM,eAAe,UAAU,eAAe;AAAA,QAChD;AAAA,QACC,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAU;AAAA,QACV,eAAW;AAAA;AAAA,IACb;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;","names":[]}
|