efficient-shell 0.0.0 → 4.126.3
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/LICENSE +21 -0
- package/README.md +758 -3
- package/dist/Browser-LM4RRZIL.js +478 -0
- package/dist/Browser-LM4RRZIL.js.map +1 -0
- package/dist/Calculator-HCKFTSJR.js +188 -0
- package/dist/Calculator-HCKFTSJR.js.map +1 -0
- package/dist/ConfirmDialog-LJXMB45A.js +3 -0
- package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
- package/dist/CurrencyConverter-BKKORN6K.js +232 -0
- package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
- package/dist/Documents-DD3QI5ER.js +437 -0
- package/dist/Documents-DD3QI5ER.js.map +1 -0
- package/dist/Files-BOPCPMJE.js +801 -0
- package/dist/Files-BOPCPMJE.js.map +1 -0
- package/dist/Notepad-KJOLF63O.js +406 -0
- package/dist/Notepad-KJOLF63O.js.map +1 -0
- package/dist/PdfViewer-LSP3YNFC.js +4 -0
- package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
- package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
- package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
- package/dist/Preview-5UOC7ZRU.js +2955 -0
- package/dist/Preview-5UOC7ZRU.js.map +1 -0
- package/dist/Sidebar-J76PV4QF.js +290 -0
- package/dist/Sidebar-J76PV4QF.js.map +1 -0
- package/dist/Spreadsheet-J7BH7UHC.js +515 -0
- package/dist/Spreadsheet-J7BH7UHC.js.map +1 -0
- package/dist/Stock-CDUS4VCZ.js +154 -0
- package/dist/Stock-CDUS4VCZ.js.map +1 -0
- package/dist/Weather-EEDBWBPQ.js +427 -0
- package/dist/Weather-EEDBWBPQ.js.map +1 -0
- package/dist/WorldClock-FFFULVTJ.js +198 -0
- package/dist/WorldClock-FFFULVTJ.js.map +1 -0
- package/dist/apps/index.d.ts +267 -0
- package/dist/apps/index.js +147 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/brand.css +2 -0
- package/dist/chunk-2VM2FRZJ.js +215 -0
- package/dist/chunk-2VM2FRZJ.js.map +1 -0
- package/dist/chunk-32W6VY2J.js +549 -0
- package/dist/chunk-32W6VY2J.js.map +1 -0
- package/dist/chunk-3CUSBZWG.js +59 -0
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/chunk-3L463T5H.js +26 -0
- package/dist/chunk-3L463T5H.js.map +1 -0
- package/dist/chunk-4TVV2BIU.js +13334 -0
- package/dist/chunk-4TVV2BIU.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/chunk-7CCHEEYC.js +94 -0
- package/dist/chunk-7CCHEEYC.js.map +1 -0
- package/dist/chunk-7KIOEKHS.js +617 -0
- package/dist/chunk-7KIOEKHS.js.map +1 -0
- package/dist/chunk-AXWC3332.js +1103 -0
- package/dist/chunk-AXWC3332.js.map +1 -0
- package/dist/chunk-BFBWLCO6.js +16 -0
- package/dist/chunk-BFBWLCO6.js.map +1 -0
- package/dist/chunk-CT3HVZRZ.js +26 -0
- package/dist/chunk-CT3HVZRZ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-DMCFOWOJ.js +153 -0
- package/dist/chunk-DMCFOWOJ.js.map +1 -0
- package/dist/chunk-FQB24B42.js +416 -0
- package/dist/chunk-FQB24B42.js.map +1 -0
- package/dist/chunk-GA35NEND.js +56 -0
- package/dist/chunk-GA35NEND.js.map +1 -0
- package/dist/chunk-HNH7FOYB.js +69 -0
- package/dist/chunk-HNH7FOYB.js.map +1 -0
- package/dist/chunk-HYZ5QMVO.js +462 -0
- package/dist/chunk-HYZ5QMVO.js.map +1 -0
- package/dist/chunk-I464GYJJ.js +17 -0
- package/dist/chunk-I464GYJJ.js.map +1 -0
- package/dist/chunk-JFQGPY7W.js +819 -0
- package/dist/chunk-JFQGPY7W.js.map +1 -0
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/chunk-N3CG5HG5.js +144 -0
- package/dist/chunk-N3CG5HG5.js.map +1 -0
- package/dist/chunk-QRGFTK2X.js +79 -0
- package/dist/chunk-QRGFTK2X.js.map +1 -0
- package/dist/chunk-SZ4WMHUX.js +7 -0
- package/dist/chunk-SZ4WMHUX.js.map +1 -0
- package/dist/chunk-W6XTG23K.js +3708 -0
- package/dist/chunk-W6XTG23K.js.map +1 -0
- package/dist/chunk-YG45DAUT.js +96 -0
- package/dist/chunk-YG45DAUT.js.map +1 -0
- package/dist/chunk-YNGPTOL6.js +46 -0
- package/dist/chunk-YNGPTOL6.js.map +1 -0
- package/dist/chunk-YNHLQ24V.js +400 -0
- package/dist/chunk-YNHLQ24V.js.map +1 -0
- package/dist/chunk-YNKK7NE3.js +171 -0
- package/dist/chunk-YNKK7NE3.js.map +1 -0
- package/dist/chunk-YPO6RDMX.js +20 -0
- package/dist/chunk-YPO6RDMX.js.map +1 -0
- package/dist/file-intake/index.d.ts +87 -0
- package/dist/file-intake/index.js +3 -0
- package/dist/file-intake/index.js.map +1 -0
- package/dist/index-dQia6NpB.d.ts +5904 -0
- package/dist/index.d.ts +2495 -0
- package/dist/index.js +7460 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +118 -0
- package/dist/markdown/index.js +184 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markup/index.d.ts +235 -0
- package/dist/markup/index.js +135 -0
- package/dist/markup/index.js.map +1 -0
- package/dist/page-tabs/index.d.ts +154 -0
- package/dist/page-tabs/index.js +706 -0
- package/dist/page-tabs/index.js.map +1 -0
- package/dist/shell.css +105 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +22 -0
- package/dist/themes.css +249 -0
- package/dist/types-BOinJXUf.d.ts +118 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +1518 -0
- package/package.json +172 -3
|
@@ -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 };
|