efficient-shell 0.0.0-stage → 4.126.2

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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +753 -2
  3. package/dist/Browser-DBRSIQ4J.js +478 -0
  4. package/dist/Browser-DBRSIQ4J.js.map +1 -0
  5. package/dist/Calculator-HCKFTSJR.js +188 -0
  6. package/dist/Calculator-HCKFTSJR.js.map +1 -0
  7. package/dist/ConfirmDialog-LJXMB45A.js +3 -0
  8. package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
  9. package/dist/CurrencyConverter-BKKORN6K.js +232 -0
  10. package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
  11. package/dist/Documents-5MD5SKPB.js +437 -0
  12. package/dist/Documents-5MD5SKPB.js.map +1 -0
  13. package/dist/Files-KY35DPWB.js +801 -0
  14. package/dist/Files-KY35DPWB.js.map +1 -0
  15. package/dist/Notepad-2BO5VEUG.js +406 -0
  16. package/dist/Notepad-2BO5VEUG.js.map +1 -0
  17. package/dist/PdfViewer-LSP3YNFC.js +4 -0
  18. package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
  19. package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
  20. package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
  21. package/dist/Preview-N4UYYXG6.js +2955 -0
  22. package/dist/Preview-N4UYYXG6.js.map +1 -0
  23. package/dist/Sidebar-J76PV4QF.js +290 -0
  24. package/dist/Sidebar-J76PV4QF.js.map +1 -0
  25. package/dist/Spreadsheet-5EWZGMXN.js +515 -0
  26. package/dist/Spreadsheet-5EWZGMXN.js.map +1 -0
  27. package/dist/Stock-CDUS4VCZ.js +154 -0
  28. package/dist/Stock-CDUS4VCZ.js.map +1 -0
  29. package/dist/Weather-EEDBWBPQ.js +427 -0
  30. package/dist/Weather-EEDBWBPQ.js.map +1 -0
  31. package/dist/WorldClock-FFFULVTJ.js +198 -0
  32. package/dist/WorldClock-FFFULVTJ.js.map +1 -0
  33. package/dist/apps/index.d.ts +267 -0
  34. package/dist/apps/index.js +147 -0
  35. package/dist/apps/index.js.map +1 -0
  36. package/dist/brand.css +2 -0
  37. package/dist/chunk-2VM2FRZJ.js +215 -0
  38. package/dist/chunk-2VM2FRZJ.js.map +1 -0
  39. package/dist/chunk-32W6VY2J.js +549 -0
  40. package/dist/chunk-32W6VY2J.js.map +1 -0
  41. package/dist/chunk-3CUSBZWG.js +59 -0
  42. package/dist/chunk-3CUSBZWG.js.map +1 -0
  43. package/dist/chunk-3L463T5H.js +26 -0
  44. package/dist/chunk-3L463T5H.js.map +1 -0
  45. package/dist/chunk-4TVV2BIU.js +13334 -0
  46. package/dist/chunk-4TVV2BIU.js.map +1 -0
  47. package/dist/chunk-6XQHH7D2.js +105 -0
  48. package/dist/chunk-6XQHH7D2.js.map +1 -0
  49. package/dist/chunk-7CCHEEYC.js +94 -0
  50. package/dist/chunk-7CCHEEYC.js.map +1 -0
  51. package/dist/chunk-7KIOEKHS.js +617 -0
  52. package/dist/chunk-7KIOEKHS.js.map +1 -0
  53. package/dist/chunk-AXWC3332.js +1103 -0
  54. package/dist/chunk-AXWC3332.js.map +1 -0
  55. package/dist/chunk-BFBWLCO6.js +16 -0
  56. package/dist/chunk-BFBWLCO6.js.map +1 -0
  57. package/dist/chunk-C3NELUTX.js +79 -0
  58. package/dist/chunk-C3NELUTX.js.map +1 -0
  59. package/dist/chunk-CT3HVZRZ.js +26 -0
  60. package/dist/chunk-CT3HVZRZ.js.map +1 -0
  61. package/dist/chunk-D7PYW2QS.js +265 -0
  62. package/dist/chunk-D7PYW2QS.js.map +1 -0
  63. package/dist/chunk-DMCFOWOJ.js +153 -0
  64. package/dist/chunk-DMCFOWOJ.js.map +1 -0
  65. package/dist/chunk-FQB24B42.js +416 -0
  66. package/dist/chunk-FQB24B42.js.map +1 -0
  67. package/dist/chunk-GA35NEND.js +56 -0
  68. package/dist/chunk-GA35NEND.js.map +1 -0
  69. package/dist/chunk-HNH7FOYB.js +69 -0
  70. package/dist/chunk-HNH7FOYB.js.map +1 -0
  71. package/dist/chunk-HYZ5QMVO.js +462 -0
  72. package/dist/chunk-HYZ5QMVO.js.map +1 -0
  73. package/dist/chunk-I464GYJJ.js +17 -0
  74. package/dist/chunk-I464GYJJ.js.map +1 -0
  75. package/dist/chunk-JFQGPY7W.js +819 -0
  76. package/dist/chunk-JFQGPY7W.js.map +1 -0
  77. package/dist/chunk-MGGY74RY.js +14 -0
  78. package/dist/chunk-MGGY74RY.js.map +1 -0
  79. package/dist/chunk-N3CG5HG5.js +144 -0
  80. package/dist/chunk-N3CG5HG5.js.map +1 -0
  81. package/dist/chunk-W6XTG23K.js +3708 -0
  82. package/dist/chunk-W6XTG23K.js.map +1 -0
  83. package/dist/chunk-YG45DAUT.js +96 -0
  84. package/dist/chunk-YG45DAUT.js.map +1 -0
  85. package/dist/chunk-YMGXZABY.js +7 -0
  86. package/dist/chunk-YMGXZABY.js.map +1 -0
  87. package/dist/chunk-YNGPTOL6.js +46 -0
  88. package/dist/chunk-YNGPTOL6.js.map +1 -0
  89. package/dist/chunk-YNHLQ24V.js +400 -0
  90. package/dist/chunk-YNHLQ24V.js.map +1 -0
  91. package/dist/chunk-YNKK7NE3.js +171 -0
  92. package/dist/chunk-YNKK7NE3.js.map +1 -0
  93. package/dist/chunk-YPO6RDMX.js +20 -0
  94. package/dist/chunk-YPO6RDMX.js.map +1 -0
  95. package/dist/file-intake/index.d.ts +87 -0
  96. package/dist/file-intake/index.js +3 -0
  97. package/dist/file-intake/index.js.map +1 -0
  98. package/dist/index-dQia6NpB.d.ts +5904 -0
  99. package/dist/index.d.ts +2495 -0
  100. package/dist/index.js +7460 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/markdown/index.d.ts +118 -0
  103. package/dist/markdown/index.js +184 -0
  104. package/dist/markdown/index.js.map +1 -0
  105. package/dist/markup/index.d.ts +235 -0
  106. package/dist/markup/index.js +135 -0
  107. package/dist/markup/index.js.map +1 -0
  108. package/dist/page-tabs/index.d.ts +154 -0
  109. package/dist/page-tabs/index.js +706 -0
  110. package/dist/page-tabs/index.js.map +1 -0
  111. package/dist/shell.css +105 -0
  112. package/dist/sounds-NT4DEZGD.js +3 -0
  113. package/dist/sounds-NT4DEZGD.js.map +1 -0
  114. package/dist/styles.css +22 -0
  115. package/dist/themes.css +249 -0
  116. package/dist/types-BOinJXUf.d.ts +118 -0
  117. package/dist/ui/index.d.ts +4 -0
  118. package/dist/ui/index.js +15 -0
  119. package/dist/ui/index.js.map +1 -0
  120. package/dist/ui.css +1518 -0
  121. package/package.json +178 -4
@@ -0,0 +1,118 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { Components } from 'react-markdown';
5
+ export { Components as MarkdownComponents } from 'react-markdown';
6
+ import remarkBreaks from 'remark-breaks';
7
+ import remarkGfm from 'remark-gfm';
8
+
9
+ /**
10
+ * The full Markdown renderer — CommonMark + GFM, behind its own subpath.
11
+ *
12
+ * ## Why this is not in the UI kit
13
+ *
14
+ * `react-os-shell/ui` promises to import nothing but React, and two checks
15
+ * hold that line: `tests/uiEntryIsPeerFree.test.ts` walks the source graph and
16
+ * `scripts/verify-dist.mjs` walks the built one (only the second catches a peer
17
+ * arriving through a shared chunk). That promise is what lets the till, which
18
+ * has no `@heroicons/react` installed at all, take the kit.
19
+ *
20
+ * The kit's `Markdown` was written under that constraint — a regex renderer,
21
+ * because a regex was all it could afford. It is still there, still correct for
22
+ * what it covers, and still what `HelpCenter` falls back to. But a regex cannot
23
+ * nest, so it cannot grow tables inside list items, task lists, footnotes, or
24
+ * anything else with structure, and the day arrived when a body needed more.
25
+ *
26
+ * So: a second entry, with a real parser, whose peers are OPTIONAL. A consumer
27
+ * that never imports `react-os-shell/markdown` never installs react-markdown
28
+ * and never pays a byte for it — the same bargain `react-os-shell/markup`
29
+ * already strikes from the other direction. The package's `dependencies` stay
30
+ * empty, which is the promise underneath all of the others.
31
+ *
32
+ * ## What is deliberately NOT here
33
+ *
34
+ * - **Syntax highlighting.** Shiki and Prism each cost more than this whole
35
+ * package. Code blocks are monospace with their own horizontal scroll, and
36
+ * the info string is carried through as `data-language` so a host that wants
37
+ * highlighting can add it without a fork.
38
+ * - **`rehype-raw`.** Adding it would re-open raw HTML, and raw HTML in a body
39
+ * a customer typed is an XSS surface. The dialect below closes it instead.
40
+ * - **A sanitiser.** Not needed, and worth stating so nobody adds one out of
41
+ * caution: no raw HTML is parsed, and react-markdown's default `urlTransform`
42
+ * neutralises `javascript:` URLs. There is no HTML string anywhere in this
43
+ * pipeline to sanitise — the output is React elements throughout.
44
+ * - **MDX.** The input is a string a person or a service wrote, not a module.
45
+ */
46
+ /**
47
+ * Four constructs disabled, each because a body that predates markdown would
48
+ * otherwise render surprisingly. Every one of these was found in stored copy,
49
+ * not imagined:
50
+ *
51
+ * - `codeIndented` — a pasted, quoted email is indented four spaces and would
52
+ * become a code block.
53
+ * - `htmlFlow` / `htmlText` — `<John>` and `<script>` must render as the text
54
+ * they are. react-markdown DROPS raw-HTML nodes silently, so without this a
55
+ * name in angle brackets simply disappears from the message.
56
+ * - `setextUnderline` — `Title` followed by `----` is a horizontal rule under a
57
+ * line of text far more often than it is a heading.
58
+ *
59
+ * Fenced ``` blocks and `<https://…>` autolinks are untouched.
60
+ */
61
+ declare function remarkDialect(this: unknown): void;
62
+ /**
63
+ * The plugin lists, exported for a host assembling its own pipeline.
64
+ *
65
+ * `note` carries `remark-breaks`; `article` does not, and the difference is
66
+ * about who wrote the text. A note is typed into a box, where pressing Return
67
+ * means a line break. An article is authored markdown whose source is hard
68
+ * wrapped at ~78 columns, where a single newline is a soft wrap and honouring
69
+ * it would shred every paragraph.
70
+ */
71
+ declare const MARKDOWN_PLUGINS: {
72
+ readonly note: readonly [typeof remarkGfm, typeof remarkBreaks, typeof remarkDialect];
73
+ readonly article: readonly [typeof remarkGfm, typeof remarkDialect];
74
+ };
75
+ type MarkdownVariant = 'note' | 'article';
76
+ interface MarkdownProps {
77
+ /** The markdown source. */
78
+ children: string;
79
+ /**
80
+ * `note` (default) — typed copy inside a card: capped headings, single
81
+ * newlines honoured as line breaks, tight blocks.
82
+ * `article` — authored documentation: full heading scale, soft wraps, callout
83
+ * blockquotes, screenshot placeholders, a capped measure.
84
+ */
85
+ variant?: MarkdownVariant;
86
+ /**
87
+ * Collapse to ~6 text lines with a fade. The "Show more" control belongs to
88
+ * the caller; this only clamps. The fade appears only when the clamped
89
+ * content actually OVERFLOWS, measured after layout — a note that is long in
90
+ * characters but short on screen would otherwise get a fade under nothing.
91
+ *
92
+ * Needs `react-os-shell/ui.css` for `.rosmd-clamp` / `.rosmd-fade`.
93
+ */
94
+ clamp?: boolean;
95
+ /**
96
+ * Rewrite a relative image `src` before it is fetched. Article bodies
97
+ * reference screenshots by a repo-relative path that only the host knows how
98
+ * to serve.
99
+ */
100
+ resolveImageSrc?: (src: string) => string;
101
+ /** Override individual element renderers. Merged over the variant's set. */
102
+ components?: Partial<Components>;
103
+ className?: string;
104
+ }
105
+ /**
106
+ * Render markdown as React elements.
107
+ *
108
+ * No HTML string is produced at any point, and no raw HTML is parsed, so there
109
+ * is nothing here to sanitise — see the note at the top of this file before
110
+ * reaching for DOMPurify.
111
+ */
112
+ declare function Markdown({ children, variant, clamp, resolveImageSrc, components, className, }: MarkdownProps): react_jsx_runtime.JSX.Element;
113
+ declare const MemoMarkdown: react.MemoExoticComponent<typeof Markdown>;
114
+
115
+ /** Re-exported so a host can render a placeholder in its own layout. */
116
+ type MarkdownNode = ReactNode;
117
+
118
+ export { MARKDOWN_PLUGINS, MemoMarkdown as Markdown, type MarkdownNode, type MarkdownProps, type MarkdownVariant, MemoMarkdown as default };
@@ -0,0 +1,184 @@
1
+ import { memo, useRef, useState, useLayoutEffect, useMemo } from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkBreaks from 'remark-breaks';
4
+ import remarkGfm from 'remark-gfm';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ // src/markdown/index.tsx
8
+ function remarkDialect() {
9
+ const data = this.data();
10
+ (data.micromarkExtensions ??= []).push({
11
+ disable: { null: ["codeIndented", "htmlFlow", "htmlText", "setextUnderline"] }
12
+ });
13
+ }
14
+ var MARKDOWN_PLUGINS = {
15
+ note: [remarkGfm, remarkBreaks, remarkDialect],
16
+ article: [remarkGfm, remarkDialect]
17
+ };
18
+ var linkRenderer = ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", { ...props, target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 hover:underline break-all" });
19
+ var codeRenderer = ({ node: _n, ...props }) => /* @__PURE__ */ jsx("code", { className: "rounded bg-gray-200 px-1 py-0.5 font-mono text-[0.85em] text-gray-800", ...props });
20
+ var preRenderer = (extra) => ({ node: _n, ...props }) => /* @__PURE__ */ jsx(
21
+ "pre",
22
+ {
23
+ className: `${extra} overflow-x-auto rounded-lg border border-gray-200 bg-gray-50 font-mono text-xs [&>code]:bg-transparent [&>code]:p-0 [&>code]:[font-size:inherit]`,
24
+ ...props
25
+ }
26
+ );
27
+ var tableRenderers = {
28
+ th: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("th", { className: "border border-gray-200 bg-gray-50 px-2 py-1 text-left font-semibold text-gray-700", ...props }),
29
+ td: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("td", { className: "border border-gray-200 px-2 py-1 align-top", ...props })
30
+ };
31
+ var NOTE_COMPONENTS = {
32
+ a: linkRenderer,
33
+ h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", ...props }),
34
+ h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-900", ...props }),
35
+ h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", ...props }),
36
+ h4: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-700", ...props }),
37
+ h5: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h5", { className: "text-sm font-semibold text-gray-700", ...props }),
38
+ h6: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h6", { className: "text-sm font-semibold text-gray-700", ...props }),
39
+ ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", { className: "list-disc pl-5", ...props }),
40
+ ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", { className: "list-decimal pl-5", ...props }),
41
+ code: codeRenderer,
42
+ pre: preRenderer("p-2"),
43
+ blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", { className: "border-l-2 border-gray-300 pl-2.5 text-gray-600", ...props }),
44
+ hr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("hr", { className: "border-gray-200", ...props }),
45
+ table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsx("table", { className: "text-xs border-collapse", ...props }) }),
46
+ ...tableRenderers,
47
+ img: ({ node: _n, alt, ...props }) => /* @__PURE__ */ jsx("img", { ...props, alt: alt ?? "", loading: "lazy", className: "max-w-full max-h-64 rounded-lg border border-gray-200" })
48
+ };
49
+ function noteComponents(resolveImageSrc) {
50
+ if (!resolveImageSrc) return NOTE_COMPONENTS;
51
+ return {
52
+ ...NOTE_COMPONENTS,
53
+ img: ({ node: _n, src, alt, ...props }) => {
54
+ const raw = typeof src === "string" ? src : void 0;
55
+ return /* @__PURE__ */ jsx(
56
+ "img",
57
+ {
58
+ ...props,
59
+ src: raw ? resolveImageSrc(raw) : raw,
60
+ alt: alt ?? "",
61
+ loading: "lazy",
62
+ className: "max-w-full max-h-64 rounded-lg border border-gray-200"
63
+ }
64
+ );
65
+ }
66
+ };
67
+ }
68
+ function ArticleImage({ src, alt }) {
69
+ const [failed, setFailed] = useState(false);
70
+ const label = (alt ?? "").replace(/^screenshot:\s*/i, "");
71
+ if (!src || failed) {
72
+ return /* @__PURE__ */ jsxs("span", { className: "my-3 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5", children: [
73
+ /* @__PURE__ */ jsx(
74
+ "svg",
75
+ {
76
+ className: "mt-0.5 h-4 w-4 shrink-0 text-gray-400",
77
+ viewBox: "0 0 20 20",
78
+ fill: "currentColor",
79
+ "aria-hidden": "true",
80
+ children: /* @__PURE__ */ jsx("path", { d: "M3 6a1.5 1.5 0 0 1 1.5-1.5h1.1l.4-.9a1 1 0 0 1 .9-.6h6.2a1 1 0 0 1 .9.6l.4.9h1.1A1.5 1.5 0 0 1 18 6v8.5A1.5 1.5 0 0 1 16.5 16h-13A1.5 1.5 0 0 1 2 14.5V6Zm7.5 1.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Z" })
81
+ }
82
+ ),
83
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: label || "Screenshot" })
84
+ ] });
85
+ }
86
+ return /* @__PURE__ */ jsx(
87
+ "img",
88
+ {
89
+ src,
90
+ alt: alt ?? "",
91
+ loading: "lazy",
92
+ onError: () => setFailed(true),
93
+ className: "my-3 block max-w-full rounded-lg border border-gray-200"
94
+ }
95
+ );
96
+ }
97
+ function articleComponents(resolveImageSrc) {
98
+ return {
99
+ a: linkRenderer,
100
+ h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", { className: "mt-6 mb-3 text-xl font-semibold text-gray-900 first:mt-0", ...props }),
101
+ h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", { className: "mt-6 mb-2.5 border-b border-gray-200 pb-1.5 text-lg font-semibold text-gray-900 first:mt-0", ...props }),
102
+ h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", { className: "mt-5 mb-2 text-base font-semibold text-gray-900 first:mt-0", ...props }),
103
+ h4: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h4", { className: "mt-4 mb-1.5 text-sm font-semibold text-gray-900 first:mt-0", ...props }),
104
+ h5: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h5", { className: "mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0", ...props }),
105
+ h6: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h6", { className: "mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0", ...props }),
106
+ p: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("p", { className: "my-2.5 leading-relaxed", ...props }),
107
+ ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", { className: "my-2.5 list-disc space-y-1 pl-5", ...props }),
108
+ ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", { className: "my-2.5 list-decimal space-y-1 pl-5", ...props }),
109
+ // A numbered walkthrough has multi-paragraph steps (text, then a
110
+ // screenshot); paragraphs inside an item stay tighter than the gap between
111
+ // items, or the numbering stops reading as a sequence.
112
+ li: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("li", { className: "leading-relaxed [&>p]:my-1", ...props }),
113
+ code: codeRenderer,
114
+ pre: preRenderer("my-3 p-3"),
115
+ blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx(
116
+ "blockquote",
117
+ {
118
+ className: "my-3 rounded-r-lg border-l-4 border-blue-400/60 bg-blue-50 px-3 py-2 text-gray-700 [&>p]:my-1",
119
+ ...props
120
+ }
121
+ ),
122
+ hr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("hr", { className: "my-4 border-gray-200", ...props }),
123
+ table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", { className: "my-3 overflow-x-auto", children: /* @__PURE__ */ jsx("table", { className: "border-collapse text-sm", ...props }) }),
124
+ ...tableRenderers,
125
+ img: ({ node: _n, src, alt }) => {
126
+ const raw = typeof src === "string" ? src : void 0;
127
+ return /* @__PURE__ */ jsx(ArticleImage, { src: raw && resolveImageSrc ? resolveImageSrc(raw) : raw, alt });
128
+ }
129
+ };
130
+ }
131
+ function Markdown({
132
+ children,
133
+ variant = "note",
134
+ clamp = false,
135
+ resolveImageSrc,
136
+ components,
137
+ className = ""
138
+ }) {
139
+ const ref = useRef(null);
140
+ const [overflowing, setOverflowing] = useState(false);
141
+ useLayoutEffect(() => {
142
+ const el = ref.current;
143
+ if (!clamp || !el) {
144
+ setOverflowing(false);
145
+ return;
146
+ }
147
+ const check = () => setOverflowing(el.scrollHeight > el.clientHeight + 1);
148
+ check();
149
+ const ro = new ResizeObserver(check);
150
+ ro.observe(el);
151
+ return () => ro.disconnect();
152
+ }, [clamp, children]);
153
+ const resolved = useMemo(
154
+ () => ({
155
+ ...variant === "article" ? articleComponents(resolveImageSrc) : noteComponents(resolveImageSrc),
156
+ ...components
157
+ }),
158
+ [variant, resolveImageSrc, components]
159
+ );
160
+ const named = ` ${className} `;
161
+ const ink = /\stext-(?:[a-z]+-\d{2,3}(?:\/\d+)?|black|white|inherit)\s/.test(named) ? "" : "text-gray-800";
162
+ const size = /\stext-(?:xs|sm|base|lg|[2-9]?xl|\[[^\]]+\])\s/.test(named) ? "" : "text-sm";
163
+ return /* @__PURE__ */ jsx(
164
+ "div",
165
+ {
166
+ ref,
167
+ className: [
168
+ variant === "article" ? "max-w-3xl break-words" : "break-words space-y-1.5",
169
+ size,
170
+ ink,
171
+ clamp ? "rosmd-clamp" : "",
172
+ clamp && overflowing ? "rosmd-fade" : "",
173
+ className
174
+ ].filter(Boolean).join(" "),
175
+ children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: MARKDOWN_PLUGINS[variant], components: resolved, children })
176
+ }
177
+ );
178
+ }
179
+ var MemoMarkdown = memo(Markdown);
180
+ var markdown_default = MemoMarkdown;
181
+
182
+ export { MARKDOWN_PLUGINS, MemoMarkdown as Markdown, markdown_default as default };
183
+ //# sourceMappingURL=index.js.map
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/markdown/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { memo, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkBreaks from 'remark-breaks';\nimport remarkGfm from 'remark-gfm';\n\n/**\n * The full Markdown renderer — CommonMark + GFM, behind its own subpath.\n *\n * ## Why this is not in the UI kit\n *\n * `react-os-shell/ui` promises to import nothing but React, and two checks\n * hold that line: `tests/uiEntryIsPeerFree.test.ts` walks the source graph and\n * `scripts/verify-dist.mjs` walks the built one (only the second catches a peer\n * arriving through a shared chunk). That promise is what lets the till, which\n * has no `@heroicons/react` installed at all, take the kit.\n *\n * The kit's `Markdown` was written under that constraint — a regex renderer,\n * because a regex was all it could afford. It is still there, still correct for\n * what it covers, and still what `HelpCenter` falls back to. But a regex cannot\n * nest, so it cannot grow tables inside list items, task lists, footnotes, or\n * anything else with structure, and the day arrived when a body needed more.\n *\n * So: a second entry, with a real parser, whose peers are OPTIONAL. A consumer\n * that never imports `react-os-shell/markdown` never installs react-markdown\n * and never pays a byte for it — the same bargain `react-os-shell/markup`\n * already strikes from the other direction. The package's `dependencies` stay\n * empty, which is the promise underneath all of the others.\n *\n * ## What is deliberately NOT here\n *\n * - **Syntax highlighting.** Shiki and Prism each cost more than this whole\n * package. Code blocks are monospace with their own horizontal scroll, and\n * the info string is carried through as `data-language` so a host that wants\n * highlighting can add it without a fork.\n * - **`rehype-raw`.** Adding it would re-open raw HTML, and raw HTML in a body\n * a customer typed is an XSS surface. The dialect below closes it instead.\n * - **A sanitiser.** Not needed, and worth stating so nobody adds one out of\n * caution: no raw HTML is parsed, and react-markdown's default `urlTransform`\n * neutralises `javascript:` URLs. There is no HTML string anywhere in this\n * pipeline to sanitise — the output is React elements throughout.\n * - **MDX.** The input is a string a person or a service wrote, not a module.\n */\n\n// ── Dialect ────────────────────────────────────────────────────────────────\n\n/**\n * Four constructs disabled, each because a body that predates markdown would\n * otherwise render surprisingly. Every one of these was found in stored copy,\n * not imagined:\n *\n * - `codeIndented` — a pasted, quoted email is indented four spaces and would\n * become a code block.\n * - `htmlFlow` / `htmlText` — `<John>` and `<script>` must render as the text\n * they are. react-markdown DROPS raw-HTML nodes silently, so without this a\n * name in angle brackets simply disappears from the message.\n * - `setextUnderline` — `Title` followed by `----` is a horizontal rule under a\n * line of text far more often than it is a heading.\n *\n * Fenced ``` blocks and `<https://…>` autolinks are untouched.\n */\nfunction remarkDialect(this: unknown) {\n const data = (this as { data: () => { micromarkExtensions?: unknown[] } }).data();\n (data.micromarkExtensions ??= []).push({\n disable: { null: ['codeIndented', 'htmlFlow', 'htmlText', 'setextUnderline'] },\n });\n}\n\n/**\n * The plugin lists, exported for a host assembling its own pipeline.\n *\n * `note` carries `remark-breaks`; `article` does not, and the difference is\n * about who wrote the text. A note is typed into a box, where pressing Return\n * means a line break. An article is authored markdown whose source is hard\n * wrapped at ~78 columns, where a single newline is a soft wrap and honouring\n * it would shred every paragraph.\n */\nexport const MARKDOWN_PLUGINS = {\n note: [remarkGfm, remarkBreaks, remarkDialect],\n article: [remarkGfm, remarkDialect],\n} as const;\n\n// ── Shared element renderers ───────────────────────────────────────────────\n\n/**\n * Colours here are utility classes that `ui.css` REMAPS under\n * `[data-theme=\"dark\"]` — this package's dark mode is not a Tailwind variant.\n * Two consequences worth carrying in your head when editing this file:\n *\n * 1. A class with no remap stays light-on-light. `tests/markdown.test.tsx`\n * fails the build rather than let one through.\n * 2. `bg-gray-200`, never `bg-gray-100`, for anything drawn ON a panel:\n * `bg-gray-100` remaps to `--surface`, and so does `bg-white`, so a chip\n * using it is the exact colour of the surface behind it. It was invisible\n * in dark mode in the kit renderer for a year.\n */\nconst linkRenderer: Components['a'] = ({ node: _n, ...props }) => (\n <a {...props} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 hover:underline break-all\" />\n);\n\nconst codeRenderer: Components['code'] = ({ node: _n, ...props }) => (\n <code className=\"rounded bg-gray-200 px-1 py-0.5 font-mono text-[0.85em] text-gray-800\" {...props} />\n);\n\n/**\n * `pre` un-chips the `code` inside it: one `code` renderer serves both inline\n * spans and block content, so the block case has to undo the chip's own\n * background, padding and size.\n */\nconst preRenderer = (extra: string): Components['pre'] =>\n ({ node: _n, ...props }) => (\n <pre\n className={\n `${extra} overflow-x-auto rounded-lg border border-gray-200 bg-gray-50 font-mono text-xs ` +\n '[&>code]:bg-transparent [&>code]:p-0 [&>code]:[font-size:inherit]'\n }\n {...props}\n />\n );\n\nconst tableRenderers: Pick<Components, 'th' | 'td'> = {\n th: ({ node: _n, ...props }) => (\n <th className=\"border border-gray-200 bg-gray-50 px-2 py-1 text-left font-semibold text-gray-700\" {...props} />\n ),\n td: ({ node: _n, ...props }) => <td className=\"border border-gray-200 px-2 py-1 align-top\" {...props} />,\n};\n\n// ── variant: note ──────────────────────────────────────────────────────────\n\n/**\n * Timeline cards, message bodies, bug-report descriptions — anything a person\n * or a service TYPED, shown inside a card that has other things in it.\n *\n * Headings cap one step above body text. An author writing `# heading` in a\n * note means emphasis, not a document outline, and a 30px line inside a\n * timeline card reads as a rendering fault.\n */\nconst NOTE_COMPONENTS: Components = {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => <h1 className=\"text-base font-semibold text-gray-900\" {...props} />,\n h2: ({ node: _n, ...props }) => <h2 className=\"text-sm font-semibold text-gray-900\" {...props} />,\n h3: ({ node: _n, ...props }) => <h3 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h4: ({ node: _n, ...props }) => <h4 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h5: ({ node: _n, ...props }) => <h5 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h6: ({ node: _n, ...props }) => <h6 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"list-disc pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"list-decimal pl-5\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('p-2'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote className=\"border-l-2 border-gray-300 pl-2.5 text-gray-600\" {...props} />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"text-xs border-collapse\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, alt, ...props }) => (\n <img {...props} alt={alt ?? ''} loading=\"lazy\" className=\"max-w-full max-h-64 rounded-lg border border-gray-200\" />\n ),\n};\n\n/** The note variant's set, with `resolveImageSrc` wired the same way the\n * article's is — a resolver that silently applied to one variant and not the\n * other was a broken image with no signal. */\nfunction noteComponents(resolveImageSrc?: (src: string) => string): Components {\n if (!resolveImageSrc) return NOTE_COMPONENTS;\n return {\n ...NOTE_COMPONENTS,\n img: ({ node: _n, src, alt, ...props }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return (\n <img\n {...props}\n src={raw ? resolveImageSrc(raw) : raw}\n alt={alt ?? ''}\n loading=\"lazy\"\n className=\"max-w-full max-h-64 rounded-lg border border-gray-200\"\n />\n );\n },\n };\n}\n\n// ── variant: article ───────────────────────────────────────────────────────\n\n/**\n * A screenshot that has not been uploaded yet shows a labelled box rather than\n * a broken-image icon: most seeded article screenshots do not exist, and a\n * broken icon per step reads as a bug where the box reads as intent.\n *\n * Spans, not divs — react-markdown puts a lone image inside a `<p>`.\n */\nfunction ArticleImage({ src, alt }: { src?: string; alt?: string }) {\n const [failed, setFailed] = useState(false);\n const label = (alt ?? '').replace(/^screenshot:\\s*/i, '');\n if (!src || failed) {\n return (\n <span className=\"my-3 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5\">\n <svg\n className=\"mt-0.5 h-4 w-4 shrink-0 text-gray-400\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 6a1.5 1.5 0 0 1 1.5-1.5h1.1l.4-.9a1 1 0 0 1 .9-.6h6.2a1 1 0 0 1 .9.6l.4.9h1.1A1.5 1.5 0 0 1 18 6v8.5A1.5 1.5 0 0 1 16.5 16h-13A1.5 1.5 0 0 1 2 14.5V6Zm7.5 1.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Z\" />\n </svg>\n <span className=\"text-xs text-gray-500\">{label || 'Screenshot'}</span>\n </span>\n );\n }\n return (\n <img\n src={src}\n alt={alt ?? ''}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className=\"my-3 block max-w-full rounded-lg border border-gray-200\"\n />\n );\n}\n\n/**\n * Help articles and other authored documentation: full heading scale, airier\n * blocks, `>` styled as a callout rather than an email-quote bar, and a capped\n * measure so a wide window does not produce 200-character lines.\n */\nfunction articleComponents(resolveImageSrc?: (src: string) => string): Components {\n return {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => (\n <h1 className=\"mt-6 mb-3 text-xl font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h2: ({ node: _n, ...props }) => (\n <h2 className=\"mt-6 mb-2.5 border-b border-gray-200 pb-1.5 text-lg font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h3: ({ node: _n, ...props }) => (\n <h3 className=\"mt-5 mb-2 text-base font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h4: ({ node: _n, ...props }) => (\n <h4 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h5: ({ node: _n, ...props }) => (\n <h5 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n h6: ({ node: _n, ...props }) => (\n <h6 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n p: ({ node: _n, ...props }) => <p className=\"my-2.5 leading-relaxed\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"my-2.5 list-disc space-y-1 pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"my-2.5 list-decimal space-y-1 pl-5\" {...props} />,\n // A numbered walkthrough has multi-paragraph steps (text, then a\n // screenshot); paragraphs inside an item stay tighter than the gap between\n // items, or the numbering stops reading as a sequence.\n li: ({ node: _n, ...props }) => <li className=\"leading-relaxed [&>p]:my-1\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('my-3 p-3'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote\n className=\"my-3 rounded-r-lg border-l-4 border-blue-400/60 bg-blue-50 px-3 py-2 text-gray-700 [&>p]:my-1\"\n {...props}\n />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"my-4 border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"my-3 overflow-x-auto\">\n <table className=\"border-collapse text-sm\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, src, alt }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return <ArticleImage src={raw && resolveImageSrc ? resolveImageSrc(raw) : raw} alt={alt} />;\n },\n };\n}\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport type MarkdownVariant = 'note' | 'article';\n\nexport interface MarkdownProps {\n /** The markdown source. */\n children: string;\n /**\n * `note` (default) — typed copy inside a card: capped headings, single\n * newlines honoured as line breaks, tight blocks.\n * `article` — authored documentation: full heading scale, soft wraps, callout\n * blockquotes, screenshot placeholders, a capped measure.\n */\n variant?: MarkdownVariant;\n /**\n * Collapse to ~6 text lines with a fade. The \"Show more\" control belongs to\n * the caller; this only clamps. The fade appears only when the clamped\n * content actually OVERFLOWS, measured after layout — a note that is long in\n * characters but short on screen would otherwise get a fade under nothing.\n *\n * Needs `react-os-shell/ui.css` for `.rosmd-clamp` / `.rosmd-fade`.\n */\n clamp?: boolean;\n /**\n * Rewrite a relative image `src` before it is fetched. Article bodies\n * reference screenshots by a repo-relative path that only the host knows how\n * to serve.\n */\n resolveImageSrc?: (src: string) => string;\n /** Override individual element renderers. Merged over the variant's set. */\n components?: Partial<Components>;\n className?: string;\n}\n\n/**\n * Render markdown as React elements.\n *\n * No HTML string is produced at any point, and no raw HTML is parsed, so there\n * is nothing here to sanitise — see the note at the top of this file before\n * reaching for DOMPurify.\n */\nfunction Markdown({\n children,\n variant = 'note',\n clamp = false,\n resolveImageSrc,\n components,\n className = '',\n}: MarkdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [overflowing, setOverflowing] = useState(false);\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!clamp || !el) {\n setOverflowing(false);\n return;\n }\n const check = () => setOverflowing(el.scrollHeight > el.clientHeight + 1);\n check();\n // A ResizeObserver rather than a one-off measure: the same note reflows\n // when its window is resized, and a fade that was correct at mount is\n // wrong the moment it is.\n const ro = new ResizeObserver(check);\n ro.observe(el);\n return () => ro.disconnect();\n }, [clamp, children]);\n\n const resolved = useMemo<Components>(\n () => ({\n ...(variant === 'article' ? articleComponents(resolveImageSrc) : noteComponents(resolveImageSrc)),\n ...components,\n }),\n [variant, resolveImageSrc, components],\n );\n\n // `className` wins over the variant's own ink and type size.\n //\n // Both are ordinary utilities on the same element, so the one that wins is\n // decided by the order Tailwind emits them, not by the order they appear in\n // the attribute — a host asking for `text-green-900` on a green panel would\n // be gambling. The alternative is an important utility at every call site,\n // and the two syntaxes for that (v3's `!text-green-900`, v4's\n // `text-green-900!`) are each silently INERT in the other version, which is\n // a bad thing to ask a caller to get right for a colour.\n //\n // So: a host that names one simply gets it.\n const named = ` ${className} `;\n const ink = /\\stext-(?:[a-z]+-\\d{2,3}(?:\\/\\d+)?|black|white|inherit)\\s/.test(named)\n ? ''\n : 'text-gray-800';\n const size = /\\stext-(?:xs|sm|base|lg|[2-9]?xl|\\[[^\\]]+\\])\\s/.test(named) ? '' : 'text-sm';\n\n return (\n <div\n ref={ref}\n className={[\n variant === 'article' ? 'max-w-3xl break-words' : 'break-words space-y-1.5',\n size,\n ink,\n clamp ? 'rosmd-clamp' : '',\n clamp && overflowing ? 'rosmd-fade' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ReactMarkdown remarkPlugins={MARKDOWN_PLUGINS[variant] as never} components={resolved}>\n {children}\n </ReactMarkdown>\n </div>\n );\n}\n\nconst MemoMarkdown = memo(Markdown);\n\nexport { MemoMarkdown as Markdown };\nexport default MemoMarkdown;\n\nexport type { Components as MarkdownComponents } from 'react-markdown';\n\n/** Re-exported so a host can render a placeholder in its own layout. */\nexport type MarkdownNode = ReactNode;\n"]}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * The editorial markup rule — ONE grammar, shared by every product that lets a
3
+ * human type formatted copy into a plain text box.
4
+ *
5
+ * Why this lives in react-os-shell, and why behind its own subpath:
6
+ * the rule is consumed by the admin portal (agent messages + the campaign
7
+ * designer) AND by the public storefront, which has ten dependencies in total
8
+ * and cannot afford the shell's peer graph (a 3D viewer, a PDF renderer, a DXF
9
+ * viewer, an xlsx parser, a Word converter). So this module is deliberately
10
+ * FRAMEWORK-FREE: no React, no JSX, no DOM, no imports at all. `react-os-shell/markup`
11
+ * therefore adds a few hundred bytes of first-party code to a bundle and nothing
12
+ * else — see the subpath's own tsup entry.
13
+ *
14
+ * It is also the ONE subpath whose `exports` entry carries a `default`
15
+ * condition alongside `import`, and that is deliberate rather than untidy. The
16
+ * root and `./apps` are React component entries; a bundler resolves them and
17
+ * nothing else has any business to. This module is consumed by build scripts
18
+ * too — the storefront's media-manifest generator reaches it through its Puck
19
+ * config — and those run under `tsx`, which resolves through Node's CJS
20
+ * loader. With only `types` and `import` declared, that resolution fails with
21
+ * ERR_PACKAGE_PATH_NOT_EXPORTED, which reads like a missing file rather than a
22
+ * missing condition. A module this portable should answer to any resolver.
23
+ *
24
+ * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token
25
+ * list its own way, because it must: the web uses CSS classes bound to theme
26
+ * tokens, email must inline every style (mail clients drop classes). Two
27
+ * renderers, one grammar, one writer ({@link applyMark}) — so a toolbar button
28
+ * and a parser can never disagree about what a delimiter means.
29
+ *
30
+ * ## The delimiters
31
+ *
32
+ * Standard markdown, in every product:
33
+ *
34
+ * **phrase** bold
35
+ * _phrase_ italic
36
+ * ~~phrase~~ strikethrough
37
+ * ==phrase== highlight — the brand accent colour, applied to a selection
38
+ *
39
+ * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /
40
+ * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly
41
+ * as it does today. Those legacy rules are the only part of this grammar that is
42
+ * meant to be deleted: once stored content has been converted, drop the rule
43
+ * from the product's rule list and nothing else changes.
44
+ *
45
+ * ## Two deliberate delimiter choices
46
+ *
47
+ * 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign
48
+ * designer a single asterisk already means the ACCENT colour, on 704 live
49
+ * instances. Standard markdown says italic. Both cannot be true, so the
50
+ * asterisk keeps its existing meaning and italic takes markdown's OTHER
51
+ * italic marker. Nothing published moves.
52
+ * 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line
53
+ * holding `{{first_name}}` and `{{last_name}}` would italicise everything
54
+ * between the two underscores. The guard is CommonMark's own rule for `_`
55
+ * (an intraword underscore is literal) and it is checked with plain character
56
+ * tests rather than a lookbehind, because a lookbehind is a parse error on
57
+ * Safari below 16.4 and this code ships to a public storefront.
58
+ *
59
+ * ## One KNOWN divergence from the standard named above
60
+ *
61
+ * `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;
62
+ * this grammar has a single `_` rule, so the outer pair matches, the inner
63
+ * underscores are ordinary characters, and `__all__` comes back as `_all_`.
64
+ *
65
+ * Left alone deliberately, and recorded rather than fixed. A census of stored
66
+ * copy found 14 rows in this shape and every one of them is the literal link
67
+ * key `__all__`, which no renderer is handed — so nothing published is being
68
+ * mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how
69
+ * a stored `_` pair parses for the benefit of no existing string, and this
70
+ * module's promise is that the grammar grows while nothing published moves.
71
+ * Bold is `**phrase**` in every product; that is the form the toolbar writes
72
+ * and the form to reach for. Pinned by a test, so it stays a decision rather
73
+ * than becoming a discovery.
74
+ */
75
+ /** A style a toolbar button can apply. The first five are inline marks; the
76
+ * last four are the composer-only affordances (a link and the three line
77
+ * prefixes), which only a renderer that draws block content can honour. */
78
+ type MarkStyle = 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'link' | 'bullet' | 'number' | 'quote';
79
+ /** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —
80
+ * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;
81
+ * both resolve to the same paint, which is what makes converting stored copy
82
+ * from one to the other invisible. */
83
+ type InlineKind = 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent';
84
+ interface WrapResult {
85
+ text: string;
86
+ /** Where the caret should land — inside the new marks, or after them. */
87
+ selectionStart: number;
88
+ selectionEnd: number;
89
+ }
90
+ /**
91
+ * Apply a style to `text[start:end]`, returning the new text and where the
92
+ * selection should sit. Toggling is deliberate: pressing Bold on already-bold
93
+ * text unwraps it, so the shortcut behaves like every other editor.
94
+ *
95
+ * Unwrapping only ever recognises the style's OWN fence. An italic button that
96
+ * also unwrapped `*phrase*` would silently strip an accent run in the storefront
97
+ * and the campaign designer, where the asterisk means colour, not slant.
98
+ */
99
+ declare function applyMark(text: string, start: number, end: number, style: MarkStyle): WrapResult;
100
+ /** One toolbar button. `label` is a text glyph for hosts that draw type
101
+ * (the message composer); hosts with an icon set map `style` to their own.
102
+ * `shortcut` is the bare key the host combines with its platform modifier. */
103
+ interface MarkupTool {
104
+ style: MarkStyle;
105
+ label: string;
106
+ title: string;
107
+ shortcut?: string;
108
+ }
109
+ /** Every button, in the order a writer reaches for them. */
110
+ declare const MARKUP_TOOLS: readonly MarkupTool[];
111
+ /** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}
112
+ * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a
113
+ * tooltip) just to pick a subset. */
114
+ declare function markupTools(styles: readonly MarkStyle[]): MarkupTool[];
115
+ /**
116
+ * One delimited run. `open`/`close` are literal strings; a run's inner text may
117
+ * never contain the first character of `close`, which is what keeps this
118
+ * equivalent to the "asterisk, run of non-asterisks, asterisk" regexes the
119
+ * products used before — the same leftmost match, the same single close
120
+ * candidate, so today's stored copy parses exactly as it always has.
121
+ */
122
+ interface InlineRule {
123
+ readonly open: string;
124
+ readonly close: string;
125
+ readonly kind: Exclude<InlineKind, 'text'>;
126
+ /** CommonMark's `_` rule: neither delimiter may touch a word character, so
127
+ * `snake_case` and `{{first_name}}` stay literal. */
128
+ readonly wordBoundary?: boolean;
129
+ /**
130
+ * Characters that may not FOLLOW the close. A run whose closing delimiter
131
+ * butts up against one of these does not fire, and both delimiters stay
132
+ * literal.
133
+ *
134
+ * Two legacy rules need it, for the same reason: a delimiter that is also
135
+ * ordinary punctuation will otherwise claim text that was never markup. `#`
136
+ * must not pair across rank markers ("Ranked #1 and #2"), and `[` must not
137
+ * claim the label of a markdown link ("[terms](https://…)"), where the URL
138
+ * would be stranded outside the run and printed to the reader.
139
+ *
140
+ * A character SET rather than a flag per case, so the next collision is one
141
+ * more rule field and no new branch in {@link tokenizeInline}.
142
+ */
143
+ readonly notBefore?: string;
144
+ }
145
+ /**
146
+ * The standard-markdown marks, shared by every product.
147
+ *
148
+ * `**` and `*` cannot collide even though they share a prefix: a `*` followed
149
+ * immediately by another `*` is an EMPTY accent run, which no rule matches, so
150
+ * at any one position exactly one of the two can fire. Order between them is
151
+ * therefore not observable — which is worth stating, because the obvious
152
+ * assumption ("longest delimiter must be listed first") would make a future
153
+ * reader think this array's order is load-bearing when the empty-run rule is
154
+ * what actually does the work.
155
+ */
156
+ declare const STANDARD_MARKUP: readonly InlineRule[];
157
+ /**
158
+ * The buttons a COPY FIELD offers — a storefront section's copy box and a
159
+ * campaign block's copy box show exactly these, so a merchant meets one toolbar
160
+ * wherever they type.
161
+ *
162
+ * DERIVED, not listed. A copy field renders inline runs only — no lists, no
163
+ * block quote, no anchor — so the set it may advertise is precisely the tools
164
+ * whose output this grammar parses back. Writing that as a literal list was the
165
+ * bug's hiding place: the list and the grammar were two hand-maintained facts
166
+ * that agreed by luck, and nothing failed when a tool wrote a delimiter the
167
+ * parser read as something else. Now a style with no rule cannot appear here,
168
+ * whatever anyone types into the array.
169
+ *
170
+ * It resolves to bold, italic, strike and highlight. `code` is the one that
171
+ * surprises: it is an inline mark and it has a fence, but no rule reads a
172
+ * backtick, so a copy field would print `` `word` `` verbatim — and a button
173
+ * whose output the renderer draws as literal text is worse than no button.
174
+ *
175
+ * Declared here, below the rules, because it now depends on them.
176
+ */
177
+ declare const COPY_FIELD_TOOLS: readonly MarkStyle[];
178
+ /**
179
+ * The storefront's rule set: standard markdown plus the ONE legacy run it has
180
+ * published — `*phrase*` in the theme accent. Delete that last entry once
181
+ * stored copy has been converted to `==phrase==`; both kinds already paint the
182
+ * same, so the deletion is invisible.
183
+ */
184
+ declare const STOREFRONT_MARKUP: readonly InlineRule[];
185
+ /**
186
+ * The campaign designer's rule set: standard markdown plus its three legacy
187
+ * runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard
188
+ * is the one the email parser already carried: a `#` followed by a digit does
189
+ * not close a run, so "Ranked #1 and #2" is literal while "Save #20%#" bolds.
190
+ *
191
+ * The italic guard exists because `[phrase]` collides with the one piece of
192
+ * markdown syntax every writer already knows. Without it, "Read [our
193
+ * terms](https://…) first" italicises the label AND prints the bare URL to the
194
+ * reader — the words come back wearing a mark nobody chose. It is not a
195
+ * toolbar problem: a merchant typing an ordinary link into a copy box hits it,
196
+ * which is why the guard is on the RULE and not on which buttons a field
197
+ * offers. `[phrase]` on its own is untouched, so every published line still
198
+ * reads exactly as it did.
199
+ */
200
+ declare const CAMPAIGN_MARKUP: readonly InlineRule[];
201
+ /** A parsed run. `text` is the inner text with the delimiters removed, so a
202
+ * caller that only wants words ({@link stripInline}) joins the texts and a
203
+ * caller that paints ({@link InlineKind}) switches on the kind. */
204
+ interface InlineToken {
205
+ kind: InlineKind;
206
+ text: string;
207
+ }
208
+ /**
209
+ * Split authored text into its runs.
210
+ *
211
+ * The returned tokens ALTERNATE, starting and ending with a `text` token that
212
+ * may be empty — exactly the shape `String.split` with one capture group
213
+ * produced in the parsers this replaces. That contract is load-bearing, not
214
+ * cosmetic: the storefront wraps every segment (including the empty ones) in a
215
+ * `<span>`, so preserving the empty tokens is what keeps a published page's
216
+ * markup byte-for-byte identical.
217
+ *
218
+ * A lone delimiter with no partner stays literal — the products' copy contains
219
+ * real stray asterisks, and swallowing one would silently eat a word.
220
+ */
221
+ declare function tokenizeInline(value: string, rules: readonly InlineRule[]): InlineToken[];
222
+ /**
223
+ * The plain words of an authored string — every paired run unwrapped to its
224
+ * inner text, every unpaired delimiter left alone.
225
+ *
226
+ * This is the ONLY correct way to fill an `alt`, an `aria-label` or a
227
+ * structured-data field from authored copy. Deleting a delimiter character by
228
+ * hand — "replace every asterisk with nothing" — breaks the moment the grammar
229
+ * grows a marker, and it disagrees with the renderer about a stray asterisk,
230
+ * which matters: search engines expect structured data to match the text a
231
+ * visitor actually sees.
232
+ */
233
+ declare function stripInline(value: string, rules: readonly InlineRule[]): string;
234
+
235
+ export { CAMPAIGN_MARKUP, COPY_FIELD_TOOLS, type InlineKind, type InlineRule, type InlineToken, MARKUP_TOOLS, type MarkStyle, type MarkupTool, STANDARD_MARKUP, STOREFRONT_MARKUP, type WrapResult, applyMark, markupTools, stripInline, tokenizeInline };