tempest-react-sdk 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/bin/lib/alias/index.mjs +9 -3
  2. package/bin/lib/alias/tsconfig.mjs +48 -5
  3. package/bin/lib/alias/typescript.mjs +72 -2
  4. package/bin/lib/alias/typescript.test.mjs +120 -0
  5. package/bin/lib/css/extract.mjs +2 -3
  6. package/bin/lib/doctor/doctor.e2e.test.mjs +44 -0
  7. package/bin/tempest.mjs +17 -2
  8. package/dist/components/Chat/Chat.cjs +2 -0
  9. package/dist/components/Chat/Chat.cjs.map +1 -0
  10. package/dist/components/Chat/Chat.js +126 -0
  11. package/dist/components/Chat/Chat.js.map +1 -0
  12. package/dist/components/Chat/Chat.module.cjs +2 -0
  13. package/dist/components/Chat/Chat.module.cjs.map +1 -0
  14. package/dist/components/Chat/Chat.module.js +32 -0
  15. package/dist/components/Chat/Chat.module.js.map +1 -0
  16. package/dist/components/Chat/ChatComposer.cjs +2 -0
  17. package/dist/components/Chat/ChatComposer.cjs.map +1 -0
  18. package/dist/components/Chat/ChatComposer.js +67 -0
  19. package/dist/components/Chat/ChatComposer.js.map +1 -0
  20. package/dist/components/Chat/chat-groups.cjs +2 -0
  21. package/dist/components/Chat/chat-groups.cjs.map +1 -0
  22. package/dist/components/Chat/chat-groups.js +89 -0
  23. package/dist/components/Chat/chat-groups.js.map +1 -0
  24. package/dist/components/Markdown/Markdown.cjs +2 -0
  25. package/dist/components/Markdown/Markdown.cjs.map +1 -0
  26. package/dist/components/Markdown/Markdown.js +124 -0
  27. package/dist/components/Markdown/Markdown.js.map +1 -0
  28. package/dist/components/Markdown/Markdown.module.cjs +2 -0
  29. package/dist/components/Markdown/Markdown.module.cjs.map +1 -0
  30. package/dist/components/Markdown/Markdown.module.js +23 -0
  31. package/dist/components/Markdown/Markdown.module.js.map +1 -0
  32. package/dist/components/Markdown/markdown-parse.cjs +7 -0
  33. package/dist/components/Markdown/markdown-parse.cjs.map +1 -0
  34. package/dist/components/Markdown/markdown-parse.js +269 -0
  35. package/dist/components/Markdown/markdown-parse.js.map +1 -0
  36. package/dist/components/Markdown/markdown-url.cjs +2 -0
  37. package/dist/components/Markdown/markdown-url.cjs.map +1 -0
  38. package/dist/components/Markdown/markdown-url.js +28 -0
  39. package/dist/components/Markdown/markdown-url.js.map +1 -0
  40. package/dist/components/Masonry/Masonry.cjs +2 -0
  41. package/dist/components/Masonry/Masonry.cjs.map +1 -0
  42. package/dist/components/Masonry/Masonry.js +70 -0
  43. package/dist/components/Masonry/Masonry.js.map +1 -0
  44. package/dist/components/Masonry/Masonry.module.cjs +2 -0
  45. package/dist/components/Masonry/Masonry.module.cjs.map +1 -0
  46. package/dist/components/Masonry/Masonry.module.js +10 -0
  47. package/dist/components/Masonry/Masonry.module.js.map +1 -0
  48. package/dist/components/Masonry/masonry-layout.cjs +2 -0
  49. package/dist/components/Masonry/masonry-layout.cjs.map +1 -0
  50. package/dist/components/Masonry/masonry-layout.js +20 -0
  51. package/dist/components/Masonry/masonry-layout.js.map +1 -0
  52. package/dist/components/Transfer/Transfer.cjs +2 -0
  53. package/dist/components/Transfer/Transfer.cjs.map +1 -0
  54. package/dist/components/Transfer/Transfer.js +163 -0
  55. package/dist/components/Transfer/Transfer.js.map +1 -0
  56. package/dist/components/Transfer/Transfer.module.cjs +2 -0
  57. package/dist/components/Transfer/Transfer.module.cjs.map +1 -0
  58. package/dist/components/Transfer/Transfer.module.js +19 -0
  59. package/dist/components/Transfer/Transfer.module.js.map +1 -0
  60. package/dist/components/Transfer/transfer-state.cjs +2 -0
  61. package/dist/components/Transfer/transfer-state.cjs.map +1 -0
  62. package/dist/components/Transfer/transfer-state.js +59 -0
  63. package/dist/components/Transfer/transfer-state.js.map +1 -0
  64. package/dist/styles.css +1 -1
  65. package/dist/tempest-react-sdk.cjs +1 -1
  66. package/dist/tempest-react-sdk.d.ts +493 -0
  67. package/dist/tempest-react-sdk.js +114 -105
  68. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chat.module.cjs","names":[],"sources":["../../../src/components/Chat/Chat.module.css"],"sourcesContent":[".panel {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n height: var(--tempest-chat-height, 100%);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n flex: 0 0 auto;\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.thread {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--tempest-space-3);\n min-height: 0;\n padding: var(--tempest-space-4);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.thread:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -2px;\n}\n\n.day {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.day::before,\n.day::after {\n flex: 1 1 auto;\n height: 1px;\n background-color: var(--tempest-border);\n content: \"\";\n}\n\n.dayLabel {\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-text-xs);\n text-transform: capitalize;\n}\n\n.run {\n display: flex;\n gap: var(--tempest-space-3);\n align-items: flex-start;\n max-width: 100%;\n}\n\n/*\n * Own messages mirror the row instead of moving to a second column: the avatar,\n * the name and the bubbles keep the same source order, so a screen reader still\n * reads \"who, then what\" while the eye reads it from the other side.\n */\n.ownRun {\n flex-direction: row-reverse;\n}\n\n.avatar {\n flex: 0 0 auto;\n}\n\n.runBody {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n min-width: 0;\n max-width: min(78%, 42rem);\n}\n\n.ownRun .runBody {\n align-items: flex-end;\n}\n\n.author {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n}\n\n.bubbles {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.bubbleRow {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n}\n\n.ownRun .bubbleRow {\n flex-direction: row-reverse;\n}\n\n.bubble {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.ownBubble {\n border-color: transparent;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n}\n\n/*\n * A failed message keeps its text readable and states the failure at the border\n * and in the meta row. Tinting the whole bubble danger-red would make the message\n * itself hard to read — and re-reading it is exactly what somebody does before\n * deciding to retry.\n */\n.failedBubble {\n border-color: var(--tempest-danger);\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-text);\n}\n\n.body {\n overflow-wrap: anywhere;\n white-space: pre-wrap;\n}\n\n/*\n * The timestamp uses the muted token, not the subtle one, and switches to the\n * bubble's own foreground when the bubble is tinted.\n *\n * `--tempest-text-subtle` is solved against the page and surface backgrounds. On\n * `--tempest-primary-soft` it is a different composite, and the real browser\n * measured it below the 4.5:1 text floor — the same trap the syntax tokens hit.\n * Size is what de-emphasises the meta row here; colour still has to pass.\n */\n.meta {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n font-variant-numeric: tabular-nums;\n}\n\n.ownBubble .meta {\n color: var(--tempest-primary-on-soft);\n}\n\n.status {\n display: inline-flex;\n align-items: center;\n}\n\n.statusFailed {\n color: var(--tempest-danger);\n font-weight: var(--tempest-weight-bold);\n}\n\n.retry {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-danger);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.typing {\n flex: 0 0 auto;\n margin: 0;\n padding: 0 var(--tempest-space-4) var(--tempest-space-2);\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-text-xs);\n}\n\n.composer {\n display: flex;\n flex: 0 0 auto;\n align-items: flex-end;\n gap: var(--tempest-space-2);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-top: 1px solid var(--tempest-border);\n}\n\n.field {\n flex: 1 1 auto;\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n resize: none;\n}\n\n.field:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -1px;\n}\n\n.field:disabled {\n background-color: var(--tempest-surface-2);\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n.composerActions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n}\n\n.send {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border: none;\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.send:disabled {\n background-color: var(--tempest-surface-3);\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n@media (max-width: 480px) {\n .runBody {\n max-width: 88%;\n }\n}\n"],"mappings":""}
@@ -0,0 +1,32 @@
1
+ //#region src/components/Chat/Chat.module.css
2
+ var e = "tempest_panel_xUVw0", t = "tempest_header_muoPu", n = "tempest_thread_-j8yS", r = "tempest_day_uLsWA", i = "tempest_dayLabel_m8rDy", a = "tempest_run_jOUv7", o = "tempest_ownRun_3XTG5", s = "tempest_avatar_d9SYT", c = "tempest_runBody_nHQC3", l = "tempest_author_4MRkd", u = "tempest_bubbles_VYaQ0", d = "tempest_bubbleRow_QSc41", f = "tempest_bubble_GGFcU", p = "tempest_ownBubble_agOMW", m = "tempest_failedBubble_kT3F5", h = "tempest_body_1aH2F", g = "tempest_meta_3fNx8", _ = "tempest_status_EeiUt", v = "tempest_statusFailed_Bfro1", y = "tempest_retry_DAbIj", b = "tempest_typing_zdp3O", x = "tempest_composer_krOGl", S = "tempest_field_fBY-q", C = "tempest_composerActions_MYwQx", w = "tempest_send_NwcaR", T = {
3
+ panel: e,
4
+ header: t,
5
+ thread: n,
6
+ day: r,
7
+ dayLabel: i,
8
+ run: a,
9
+ ownRun: o,
10
+ avatar: s,
11
+ runBody: c,
12
+ author: l,
13
+ bubbles: u,
14
+ bubbleRow: d,
15
+ bubble: f,
16
+ ownBubble: p,
17
+ failedBubble: m,
18
+ body: h,
19
+ meta: g,
20
+ status: _,
21
+ statusFailed: v,
22
+ retry: y,
23
+ typing: b,
24
+ composer: x,
25
+ field: S,
26
+ composerActions: C,
27
+ send: w
28
+ };
29
+ //#endregion
30
+ export { l as author, s as avatar, h as body, f as bubble, d as bubbleRow, u as bubbles, x as composer, C as composerActions, r as day, i as dayLabel, T as default, m as failedBubble, S as field, t as header, g as meta, p as ownBubble, o as ownRun, e as panel, y as retry, a as run, c as runBody, w as send, _ as status, v as statusFailed, n as thread, b as typing };
31
+
32
+ //# sourceMappingURL=Chat.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chat.module.js","names":[],"sources":["../../../src/components/Chat/Chat.module.css"],"sourcesContent":[".panel {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n height: var(--tempest-chat-height, 100%);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n flex: 0 0 auto;\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.thread {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--tempest-space-3);\n min-height: 0;\n padding: var(--tempest-space-4);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.thread:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -2px;\n}\n\n.day {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.day::before,\n.day::after {\n flex: 1 1 auto;\n height: 1px;\n background-color: var(--tempest-border);\n content: \"\";\n}\n\n.dayLabel {\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-text-xs);\n text-transform: capitalize;\n}\n\n.run {\n display: flex;\n gap: var(--tempest-space-3);\n align-items: flex-start;\n max-width: 100%;\n}\n\n/*\n * Own messages mirror the row instead of moving to a second column: the avatar,\n * the name and the bubbles keep the same source order, so a screen reader still\n * reads \"who, then what\" while the eye reads it from the other side.\n */\n.ownRun {\n flex-direction: row-reverse;\n}\n\n.avatar {\n flex: 0 0 auto;\n}\n\n.runBody {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n min-width: 0;\n max-width: min(78%, 42rem);\n}\n\n.ownRun .runBody {\n align-items: flex-end;\n}\n\n.author {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n}\n\n.bubbles {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.bubbleRow {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n}\n\n.ownRun .bubbleRow {\n flex-direction: row-reverse;\n}\n\n.bubble {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.ownBubble {\n border-color: transparent;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n}\n\n/*\n * A failed message keeps its text readable and states the failure at the border\n * and in the meta row. Tinting the whole bubble danger-red would make the message\n * itself hard to read — and re-reading it is exactly what somebody does before\n * deciding to retry.\n */\n.failedBubble {\n border-color: var(--tempest-danger);\n background-color: var(--tempest-danger-bg);\n color: var(--tempest-text);\n}\n\n.body {\n overflow-wrap: anywhere;\n white-space: pre-wrap;\n}\n\n/*\n * The timestamp uses the muted token, not the subtle one, and switches to the\n * bubble's own foreground when the bubble is tinted.\n *\n * `--tempest-text-subtle` is solved against the page and surface backgrounds. On\n * `--tempest-primary-soft` it is a different composite, and the real browser\n * measured it below the 4.5:1 text floor — the same trap the syntax tokens hit.\n * Size is what de-emphasises the meta row here; colour still has to pass.\n */\n.meta {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n font-variant-numeric: tabular-nums;\n}\n\n.ownBubble .meta {\n color: var(--tempest-primary-on-soft);\n}\n\n.status {\n display: inline-flex;\n align-items: center;\n}\n\n.statusFailed {\n color: var(--tempest-danger);\n font-weight: var(--tempest-weight-bold);\n}\n\n.retry {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-danger);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.typing {\n flex: 0 0 auto;\n margin: 0;\n padding: 0 var(--tempest-space-4) var(--tempest-space-2);\n color: var(--tempest-text-subtle);\n font-size: var(--tempest-text-xs);\n}\n\n.composer {\n display: flex;\n flex: 0 0 auto;\n align-items: flex-end;\n gap: var(--tempest-space-2);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-top: 1px solid var(--tempest-border);\n}\n\n.field {\n flex: 1 1 auto;\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n resize: none;\n}\n\n.field:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -1px;\n}\n\n.field:disabled {\n background-color: var(--tempest-surface-2);\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n.composerActions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n}\n\n.send {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border: none;\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.send:disabled {\n background-color: var(--tempest-surface-3);\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n@media (max-width: 480px) {\n .runBody {\n max-width: 88%;\n }\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("./chat-groups.cjs"),n=require("./Chat.module.cjs");let r=require("react"),i=require("react/jsx-runtime");var a=(0,r.forwardRef)(function({onSend:a,locale:o=`pt-BR`,actions:s,maxRows:c=6,sendLabel:l,onError:u,className:d,disabled:f,placeholder:p,onKeyDown:m,onChange:h,...g},_){let v=t.chatStrings(o),y=(0,r.useRef)(null),[b,x]=(0,r.useState)(``),[S,C]=(0,r.useState)(!1);(0,r.useImperativeHandle)(_,()=>({focus:()=>y.current?.focus(),setValue:e=>{x(e),y.current?.focus()}})),(0,r.useLayoutEffect)(()=>{let e=y.current;if(!e)return;e.style.height=`auto`;let t=(Number.parseFloat(getComputedStyle(e).lineHeight)||20)*c;e.style.height=`${Math.min(e.scrollHeight,t)}px`,e.style.overflowY=e.scrollHeight>t?`auto`:`hidden`},[b,c]);let w=async()=>{let e=b.trim();if(!(!e||S||f)){C(!0);try{await a(e),x(``)}catch(e){u?.(e)}finally{C(!1)}}},T=e=>{e.preventDefault(),w()},E=e=>{m?.(e),!e.defaultPrevented&&(e.key!==`Enter`||e.shiftKey||e.nativeEvent.isComposing||e.keyCode===229||(e.preventDefault(),w()))};return(0,i.jsxs)(`form`,{className:e.cn(n.default.composer,d),onSubmit:T,children:[(0,i.jsx)(`textarea`,{...g,ref:y,className:n.default.field,rows:1,value:b,disabled:f,placeholder:p??v.placeholder,onChange:e=>{x(e.target.value),h?.(e)},onKeyDown:E}),(0,i.jsxs)(`div`,{className:n.default.composerActions,children:[s,(0,i.jsx)(`button`,{type:`submit`,className:n.default.send,disabled:f||S||b.trim()===``,children:l??v.send})]})]})});exports.ChatComposer=a;
2
+ //# sourceMappingURL=ChatComposer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatComposer.cjs","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":"0JAmEA,IAAa,GAAA,EAAA,EAAA,WAAA,CAAiE,SAC1E,CACI,SACA,SAAS,QACT,UACA,UAAU,EACV,YACA,UACA,YACA,WACA,cACA,YACA,WACA,GAAG,GAEP,EACF,CACE,IAAM,EAAU,EAAA,YAAY,CAAM,EAC5B,GAAA,EAAA,EAAA,OAAA,CAA8C,IAAI,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,EAAE,EAC/B,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAoB,EAAK,GAEtC,EAAA,EAAA,oBAAA,CAAoB,OAAY,CAC5B,UAAa,EAAS,SAAS,MAAM,EACrC,SAAW,GAAiB,CACxB,EAAS,CAAI,EACb,EAAS,SAAS,MAAM,CAC5B,CACJ,EAAE,GASF,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAS,QACtB,GAAI,CAAC,EAAM,OACX,EAAK,MAAM,OAAS,OAEpB,IAAM,GADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,GAAK,IAClD,EACzB,EAAK,MAAM,OAAS,GAAG,KAAK,IAAI,EAAK,aAAc,CAAG,EAAE,IACxD,EAAK,MAAM,UAAY,EAAK,aAAe,EAAM,OAAS,QAC9D,EAAG,CAAC,EAAO,CAAO,CAAC,EAEnB,IAAM,EAAS,SAA2B,CACtC,IAAM,EAAO,EAAM,KAAK,EACpB,MAAC,GAAQ,GAAQ,GACrB,GAAQ,EAAI,EACZ,GAAI,CACA,MAAM,EAAO,CAAI,EACjB,EAAS,EAAE,CACf,OAAS,EAAO,CACZ,IAAU,CAAK,CACnB,QAAU,CACN,EAAQ,EAAK,CACjB,CARY,CAShB,EAEM,EAAgB,GAA2B,CAC7C,EAAM,eAAe,EACrB,EAAY,CAChB,EASM,EAAiB,GAAoD,CACvE,IAAY,CAAK,EACb,GAAM,mBACN,EAAM,MAAQ,SAAW,EAAM,UAC/B,EAAM,YAAY,aAAe,EAAM,UAAY,MACvD,EAAM,eAAe,EACrB,EAAY,GAChB,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,SAAU,CAAS,EAAG,SAAU,WAA3D,EACI,EAAA,EAAA,IAAA,CAAC,WAAD,CACI,GAAI,EACJ,IAAK,EACL,UAAW,EAAA,QAAO,MAClB,KAAM,EACC,QACG,WACV,YAAa,GAAe,EAAQ,YACpC,SAAW,GAAU,CACjB,EAAS,EAAM,OAAO,KAAK,EAC3B,IAAW,CAAK,CACpB,EACA,UAAW,CACd,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,yBAAvB,CACK,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,KAClB,SAAU,GAAY,GAAQ,EAAM,KAAK,IAAM,YAE9C,GAAa,EAAQ,IAClB,CAAA,CACP,GACH,GAEd,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { chatStrings as t } from "./chat-groups.js";
3
+ import n from "./Chat.module.js";
4
+ import { forwardRef as r, useImperativeHandle as i, useLayoutEffect as a, useRef as o, useState as s } from "react";
5
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ //#region src/components/Chat/ChatComposer.tsx
7
+ var u = r(function({ onSend: r, locale: u = "pt-BR", actions: d, maxRows: f = 6, sendLabel: p, onError: m, className: h, disabled: g, placeholder: _, onKeyDown: v, onChange: y, ...b }, x) {
8
+ let S = t(u), C = o(null), [w, T] = s(""), [E, D] = s(!1);
9
+ i(x, () => ({
10
+ focus: () => C.current?.focus(),
11
+ setValue: (e) => {
12
+ T(e), C.current?.focus();
13
+ }
14
+ })), a(() => {
15
+ let e = C.current;
16
+ if (!e) return;
17
+ e.style.height = "auto";
18
+ let t = (Number.parseFloat(getComputedStyle(e).lineHeight) || 20) * f;
19
+ e.style.height = `${Math.min(e.scrollHeight, t)}px`, e.style.overflowY = e.scrollHeight > t ? "auto" : "hidden";
20
+ }, [w, f]);
21
+ let O = async () => {
22
+ let e = w.trim();
23
+ if (!(!e || E || g)) {
24
+ D(!0);
25
+ try {
26
+ await r(e), T("");
27
+ } catch (e) {
28
+ m?.(e);
29
+ } finally {
30
+ D(!1);
31
+ }
32
+ }
33
+ }, k = (e) => {
34
+ e.preventDefault(), O();
35
+ }, A = (e) => {
36
+ v?.(e), !e.defaultPrevented && (e.key !== "Enter" || e.shiftKey || e.nativeEvent.isComposing || e.keyCode === 229 || (e.preventDefault(), O()));
37
+ };
38
+ return /* @__PURE__ */ l("form", {
39
+ className: e(n.composer, h),
40
+ onSubmit: k,
41
+ children: [/* @__PURE__ */ c("textarea", {
42
+ ...b,
43
+ ref: C,
44
+ className: n.field,
45
+ rows: 1,
46
+ value: w,
47
+ disabled: g,
48
+ placeholder: _ ?? S.placeholder,
49
+ onChange: (e) => {
50
+ T(e.target.value), y?.(e);
51
+ },
52
+ onKeyDown: A
53
+ }), /* @__PURE__ */ l("div", {
54
+ className: n.composerActions,
55
+ children: [d, /* @__PURE__ */ c("button", {
56
+ type: "submit",
57
+ className: n.send,
58
+ disabled: g || E || w.trim() === "",
59
+ children: p ?? S.send
60
+ })]
61
+ })]
62
+ });
63
+ });
64
+ //#endregion
65
+ export { u as ChatComposer };
66
+
67
+ //# sourceMappingURL=ChatComposer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatComposer.js","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":";;;;;;AAmEA,IAAa,IAAe,EAAkD,SAC1E,EACI,WACA,YAAS,SACT,YACA,aAAU,GACV,cACA,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAiBtC,AAfA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;CACJ,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,IACrB;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GASM,KAAiB,MAAoD;EACvE,IAAY,CAAK,GACb,GAAM,qBACN,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;CAChB;CAEA,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;YAA3D,CACI,kBAAC,YAAD;GACI,GAAI;GACJ,KAAK;GACL,WAAW,EAAO;GAClB,MAAM;GACC;GACG;GACV,aAAa,KAAe,EAAQ;GACpC,WAAW,MAAU;IAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;GACpB;GACA,WAAW;EACd,CAAA,GACD,kBAAC,OAAD;GAAK,WAAW,EAAO;aAAvB,CACK,GACD,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAO;IAClB,UAAU,KAAY,KAAQ,EAAM,KAAK,MAAM;cAE9C,KAAa,EAAQ;GAClB,CAAA,CACP;IACH;;AAEd,CAAC"}
@@ -0,0 +1,2 @@
1
+ var e=300*1e3;function t(e){let t=new Date(e);return t.setHours(0,0,0,0),t.getTime()}function n({messages:n,currentUserId:r,windowMs:i=e}){let a=[],o=null,s=null;for(let e of n){let n=t(e.sentAt);n!==o&&(o=n,s=null,a.push({kind:`day`,key:`day-${n}`,date:n}));let c=s?.messages[s.messages.length-1];if(s!==null&&c!==void 0&&s.authorId===e.authorId&&e.sentAt-c.sentAt<=i&&s){s.messages.push(e);continue}s={kind:`run`,key:`${e.authorId}-${e.id}`,authorId:e.authorId,authorName:e.authorName,own:r!==void 0&&e.authorId===r,messages:[e]},a.push(s)}return a}var r={thread:`Conversa`,today:`Hoje`,yesterday:`Ontem`,you:`Você`,typingOne:e=>`${e} está digitando…`,typingTwo:(e,t)=>`${e} e ${t} estão digitando…`,typingMany:e=>`${e} pessoas estão digitando…`,sending:`Enviando`,sent:`Enviada`,read:`Lida`,failed:`Falhou ao enviar`,retry:`Tentar de novo`,empty:`Nenhuma mensagem ainda`,placeholder:`Escreva uma mensagem`,send:`Enviar`},i={thread:`Conversation`,today:`Today`,yesterday:`Yesterday`,you:`You`,typingOne:e=>`${e} is typing…`,typingTwo:(e,t)=>`${e} and ${t} are typing…`,typingMany:e=>`${e} people are typing…`,sending:`Sending`,sent:`Sent`,read:`Read`,failed:`Failed to send`,retry:`Try again`,empty:`No messages yet`,placeholder:`Write a message`,send:`Send`};function a(e){return e===`en`?i:r}function o(e,{locale:n=`pt-BR`,now:r}={}){let i=a(n),o=t(r??Date.now()),s=1440*60*1e3;return e===o?i.today:e===o-s?i.yesterday:new Date(e).toLocaleDateString(n===`en`?`en-US`:`pt-BR`,{day:`2-digit`,month:`short`,year:e<o-300*s?`numeric`:void 0})}function s(e,t=`pt-BR`){return new Date(e).toLocaleTimeString(t===`en`?`en-US`:`pt-BR`,{hour:`2-digit`,minute:`2-digit`})}function c(e,t=`pt-BR`){let n=a(t);return e.length===0?null:e.length===1?n.typingOne(e[0]):e.length===2?n.typingTwo(e[0],e[1]):n.typingMany(e.length)}exports.DEFAULT_GROUP_WINDOW_MS=e,exports.chatStrings=a,exports.dayLabel=o,exports.groupMessages=n,exports.timeLabel=s,exports.typingLabel=c;
2
+ //# sourceMappingURL=chat-groups.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-groups.cjs","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":"AAiDA,IAAa,EAA0B,IAAS,IAGhD,SAAS,EAAW,EAA2B,CAC3C,IAAM,EAAO,IAAI,KAAK,CAAS,EAE/B,OADA,EAAK,SAAS,EAAG,EAAG,EAAG,CAAC,EACjB,EAAK,QAAQ,CACxB,CAuBA,SAAgB,EAAc,CAC1B,WACA,gBACA,WAAW,GAKG,CACd,IAAM,EAA0B,CAAC,EAC7B,EAAqB,KACrB,EAAsB,KAE1B,IAAK,IAAM,KAAW,EAAU,CAC5B,IAAM,EAAa,EAAW,EAAQ,MAAM,EACxC,IAAe,IACf,EAAM,EACN,EAAM,KACN,EAAS,KAAK,CAAE,KAAM,MAAO,IAAK,OAAO,IAAc,KAAM,CAAW,CAAC,GAG7E,IAAM,EAAW,GAAK,SAAS,EAAI,SAAS,OAAS,GAOrD,GALI,IAAQ,MACR,IAAa,IAAA,IACb,EAAI,WAAa,EAAQ,UACzB,EAAQ,OAAS,EAAS,QAAU,GAEvB,EAAK,CAClB,EAAI,SAAS,KAAK,CAAO,EACzB,QACJ,CAEA,EAAM,CACF,KAAM,MACN,IAAK,GAAG,EAAQ,SAAS,GAAG,EAAQ,KACpC,SAAU,EAAQ,SAClB,WAAY,EAAQ,WACpB,IAAK,IAAkB,IAAA,IAAa,EAAQ,WAAa,EACzD,SAAU,CAAC,CAAO,CACtB,EACA,EAAS,KAAK,CAAG,CACrB,CAEA,OAAO,CACX,CAqBA,IAAM,EAAqB,CACvB,OAAQ,WACR,MAAO,OACP,UAAW,QACX,IAAK,OACL,UAAY,GAAS,GAAG,EAAK,kBAC7B,WAAY,EAAG,IAAM,GAAG,EAAE,KAAK,EAAE,mBACjC,WAAa,GAAM,GAAG,EAAE,2BACxB,QAAS,WACT,KAAM,UACN,KAAM,OACN,OAAQ,mBACR,MAAO,iBACP,MAAO,yBACP,YAAa,uBACb,KAAM,QACV,EAEM,EAAkB,CACpB,OAAQ,eACR,MAAO,QACP,UAAW,YACX,IAAK,MACL,UAAY,GAAS,GAAG,EAAK,aAC7B,WAAY,EAAG,IAAM,GAAG,EAAE,OAAO,EAAE,cACnC,WAAa,GAAM,GAAG,EAAE,qBACxB,QAAS,UACT,KAAM,OACN,KAAM,OACN,OAAQ,iBACR,MAAO,YACP,MAAO,kBACP,YAAa,kBACb,KAAM,MACV,EAGA,SAAgB,EAAY,EAAqC,CAC7D,OAAO,IAAW,KAAO,EAAK,CAClC,CAQA,SAAgB,EACZ,EACA,CAAE,SAAS,QAAS,OAAmD,CAAC,EAClE,CACN,IAAM,EAAU,EAAY,CAAM,EAC5B,EAAQ,EAAW,GAAO,KAAK,IAAI,CAAC,EACpC,EAAQ,KAAU,GAAK,IAG7B,OAFI,IAAS,EAAc,EAAQ,MAC/B,IAAS,EAAQ,EAAc,EAAQ,UACpC,IAAI,KAAK,CAAI,CAAC,CAAC,mBAAmB,IAAW,KAAO,QAAU,QAAS,CAC1E,IAAK,UACL,MAAO,QACP,KAAM,EAAO,EAAQ,IAAM,EAAQ,UAAY,IAAA,EACnD,CAAC,CACL,CAGA,SAAgB,EAAU,EAAmB,EAAyB,QAAiB,CACnF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,IAAW,KAAO,QAAU,QAAS,CAC/E,KAAM,UACN,OAAQ,SACZ,CAAC,CACL,CAGA,SAAgB,EACZ,EACA,EAAyB,QACZ,CACb,IAAM,EAAU,EAAY,CAAM,EAIlC,OAHI,EAAM,SAAW,EAAU,KAC3B,EAAM,SAAW,EAAU,EAAQ,UAAU,EAAM,EAAE,EACrD,EAAM,SAAW,EAAU,EAAQ,UAAU,EAAM,GAAI,EAAM,EAAE,EAC5D,EAAQ,WAAW,EAAM,MAAM,CAC1C"}
@@ -0,0 +1,89 @@
1
+ //#region src/components/Chat/chat-groups.ts
2
+ var e = 300 * 1e3;
3
+ function t(e) {
4
+ let t = new Date(e);
5
+ return t.setHours(0, 0, 0, 0), t.getTime();
6
+ }
7
+ function n({ messages: n, currentUserId: r, windowMs: i = e }) {
8
+ let a = [], o = null, s = null;
9
+ for (let e of n) {
10
+ let n = t(e.sentAt);
11
+ n !== o && (o = n, s = null, a.push({
12
+ kind: "day",
13
+ key: `day-${n}`,
14
+ date: n
15
+ }));
16
+ let c = s?.messages[s.messages.length - 1];
17
+ if (s !== null && c !== void 0 && s.authorId === e.authorId && e.sentAt - c.sentAt <= i && s) {
18
+ s.messages.push(e);
19
+ continue;
20
+ }
21
+ s = {
22
+ kind: "run",
23
+ key: `${e.authorId}-${e.id}`,
24
+ authorId: e.authorId,
25
+ authorName: e.authorName,
26
+ own: r !== void 0 && e.authorId === r,
27
+ messages: [e]
28
+ }, a.push(s);
29
+ }
30
+ return a;
31
+ }
32
+ var r = {
33
+ thread: "Conversa",
34
+ today: "Hoje",
35
+ yesterday: "Ontem",
36
+ you: "Você",
37
+ typingOne: (e) => `${e} está digitando…`,
38
+ typingTwo: (e, t) => `${e} e ${t} estão digitando…`,
39
+ typingMany: (e) => `${e} pessoas estão digitando…`,
40
+ sending: "Enviando",
41
+ sent: "Enviada",
42
+ read: "Lida",
43
+ failed: "Falhou ao enviar",
44
+ retry: "Tentar de novo",
45
+ empty: "Nenhuma mensagem ainda",
46
+ placeholder: "Escreva uma mensagem",
47
+ send: "Enviar"
48
+ }, i = {
49
+ thread: "Conversation",
50
+ today: "Today",
51
+ yesterday: "Yesterday",
52
+ you: "You",
53
+ typingOne: (e) => `${e} is typing…`,
54
+ typingTwo: (e, t) => `${e} and ${t} are typing…`,
55
+ typingMany: (e) => `${e} people are typing…`,
56
+ sending: "Sending",
57
+ sent: "Sent",
58
+ read: "Read",
59
+ failed: "Failed to send",
60
+ retry: "Try again",
61
+ empty: "No messages yet",
62
+ placeholder: "Write a message",
63
+ send: "Send"
64
+ };
65
+ function a(e) {
66
+ return e === "en" ? i : r;
67
+ }
68
+ function o(e, { locale: n = "pt-BR", now: r } = {}) {
69
+ let i = a(n), o = t(r ?? Date.now()), s = 1440 * 60 * 1e3;
70
+ return e === o ? i.today : e === o - s ? i.yesterday : new Date(e).toLocaleDateString(n === "en" ? "en-US" : "pt-BR", {
71
+ day: "2-digit",
72
+ month: "short",
73
+ year: e < o - 300 * s ? "numeric" : void 0
74
+ });
75
+ }
76
+ function s(e, t = "pt-BR") {
77
+ return new Date(e).toLocaleTimeString(t === "en" ? "en-US" : "pt-BR", {
78
+ hour: "2-digit",
79
+ minute: "2-digit"
80
+ });
81
+ }
82
+ function c(e, t = "pt-BR") {
83
+ let n = a(t);
84
+ return e.length === 0 ? null : e.length === 1 ? n.typingOne(e[0]) : e.length === 2 ? n.typingTwo(e[0], e[1]) : n.typingMany(e.length);
85
+ }
86
+ //#endregion
87
+ export { e as DEFAULT_GROUP_WINDOW_MS, a as chatStrings, o as dayLabel, n as groupMessages, s as timeLabel, c as typingLabel };
88
+
89
+ //# sourceMappingURL=chat-groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-groups.js","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":";AAiDA,IAAa,IAA0B,MAAS;AAGhD,SAAS,EAAW,GAA2B;CAC3C,IAAM,IAAO,IAAI,KAAK,CAAS;CAE/B,OADA,EAAK,SAAS,GAAG,GAAG,GAAG,CAAC,GACjB,EAAK,QAAQ;AACxB;AAuBA,SAAgB,EAAc,EAC1B,aACA,kBACA,cAAW,KAKG;CACd,IAAM,IAA0B,CAAC,GAC7B,IAAqB,MACrB,IAAsB;CAE1B,KAAK,IAAM,KAAW,GAAU;EAC5B,IAAM,IAAa,EAAW,EAAQ,MAAM;EAC5C,AAAI,MAAe,MACf,IAAM,GACN,IAAM,MACN,EAAS,KAAK;GAAE,MAAM;GAAO,KAAK,OAAO;GAAc,MAAM;EAAW,CAAC;EAG7E,IAAM,IAAW,GAAK,SAAS,EAAI,SAAS,SAAS;EAOrD,IALI,MAAQ,QACR,MAAa,KAAA,KACb,EAAI,aAAa,EAAQ,YACzB,EAAQ,SAAS,EAAS,UAAU,KAEvB,GAAK;GAClB,EAAI,SAAS,KAAK,CAAO;GACzB;EACJ;EAUA,AARA,IAAM;GACF,MAAM;GACN,KAAK,GAAG,EAAQ,SAAS,GAAG,EAAQ;GACpC,UAAU,EAAQ;GAClB,YAAY,EAAQ;GACpB,KAAK,MAAkB,KAAA,KAAa,EAAQ,aAAa;GACzD,UAAU,CAAC,CAAO;EACtB,GACA,EAAS,KAAK,CAAG;CACrB;CAEA,OAAO;AACX;AAqBA,IAAM,IAAqB;CACvB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,KAAK;CACL,YAAY,MAAS,GAAG,EAAK;CAC7B,YAAY,GAAG,MAAM,GAAG,EAAE,KAAK,EAAE;CACjC,aAAa,MAAM,GAAG,EAAE;CACxB,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,OAAO;CACP,aAAa;CACb,MAAM;AACV,GAEM,IAAkB;CACpB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,KAAK;CACL,YAAY,MAAS,GAAG,EAAK;CAC7B,YAAY,GAAG,MAAM,GAAG,EAAE,OAAO,EAAE;CACnC,aAAa,MAAM,GAAG,EAAE;CACxB,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,OAAO;CACP,aAAa;CACb,MAAM;AACV;AAGA,SAAgB,EAAY,GAAqC;CAC7D,OAAO,MAAW,OAAO,IAAK;AAClC;AAQA,SAAgB,EACZ,GACA,EAAE,YAAS,SAAS,WAAmD,CAAC,GAClE;CACN,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAQ,EAAW,KAAO,KAAK,IAAI,CAAC,GACpC,IAAQ,OAAU,KAAK;CAG7B,OAFI,MAAS,IAAc,EAAQ,QAC/B,MAAS,IAAQ,IAAc,EAAQ,YACpC,IAAI,KAAK,CAAI,CAAC,CAAC,mBAAmB,MAAW,OAAO,UAAU,SAAS;EAC1E,KAAK;EACL,OAAO;EACP,MAAM,IAAO,IAAQ,MAAM,IAAQ,YAAY,KAAA;CACnD,CAAC;AACL;AAGA,SAAgB,EAAU,GAAmB,IAAyB,SAAiB;CACnF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,MAAW,OAAO,UAAU,SAAS;EAC/E,MAAM;EACN,QAAQ;CACZ,CAAC;AACL;AAGA,SAAgB,EACZ,GACA,IAAyB,SACZ;CACb,IAAM,IAAU,EAAY,CAAM;CAIlC,OAHI,EAAM,WAAW,IAAU,OAC3B,EAAM,WAAW,IAAU,EAAQ,UAAU,EAAM,EAAE,IACrD,EAAM,WAAW,IAAU,EAAQ,UAAU,EAAM,IAAI,EAAM,EAAE,IAC5D,EAAQ,WAAW,EAAM,MAAM;AAC1C"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-scroll-overflow.cjs"),n=require("../CodeBlock/CodeBlock.cjs"),r=require("./markdown-parse.cjs"),i=require("./Markdown.module.cjs");let a=require("react"),o=require("react/jsx-runtime");function s(e){return e.map((e,t)=>{let n=t;switch(e.type){case`text`:return(0,o.jsx)(a.Fragment,{children:e.value},n);case`strong`:return(0,o.jsx)(`strong`,{children:s(e.children)},n);case`em`:return(0,o.jsx)(`em`,{children:s(e.children)},n);case`del`:return(0,o.jsx)(`del`,{children:s(e.children)},n);case`code`:return(0,o.jsx)(`code`,{className:i.default.inlineCode,children:e.value},n);case`image`:return(0,o.jsx)(`img`,{className:i.default.image,src:e.src,alt:e.alt},n);case`break`:return(0,o.jsx)(`br`,{},n);default:return null}})}function c(e){return e?{textAlign:e}:void 0}function l({source:t,headingOffset:n=2,highlightCode:s=!0,showLineNumbers:c=!1,linkProps:l,className:d,...f}){let p=(0,a.useMemo)(()=>r.parseMarkdown(t),[t]);return(0,o.jsx)(`div`,{className:e.cn(i.default.root,d),...f,children:p.map((e,t)=>(0,o.jsx)(u,{block:e,headingOffset:n,highlightCode:s,showLineNumbers:c,linkProps:l},t))})}function u({block:e,headingOffset:t,highlightCode:r,showLineNumbers:c,linkProps:l}){let f=e=>e.map((e,n)=>(0,o.jsx)(u,{block:e,headingOffset:t,highlightCode:r,showLineNumbers:c,linkProps:l},n)),p=e=>e.map((e,t)=>e.type===`link`?(0,o.jsx)(`a`,{className:i.default.link,href:e.href,...l,children:s(e.children)},t):(0,o.jsx)(a.Fragment,{children:s([e])},t));switch(e.type){case`heading`:{let n=Math.min(6,Math.max(1,e.level+t-1));return(0,o.jsx)(`h${n}`,{className:i.default[`h${n}`],children:p(e.children)})}case`paragraph`:return(0,o.jsx)(`p`,{children:p(e.children)});case`code`:return r?(0,o.jsx)(n.CodeBlock,{code:e.value,language:e.language??void 0,showLineNumbers:c}):(0,o.jsx)(`pre`,{children:(0,o.jsx)(`code`,{children:e.value})});case`quote`:return(0,o.jsx)(`blockquote`,{className:i.default.quote,children:f(e.children)});case`rule`:return(0,o.jsx)(`hr`,{className:i.default.rule});case`list`:return e.ordered?(0,o.jsx)(`ol`,{className:i.default.list,start:e.start===1?void 0:e.start,children:e.items.map((e,t)=>(0,o.jsx)(`li`,{className:i.default.item,children:f(e)},t))}):(0,o.jsx)(`ul`,{className:i.default.list,children:e.items.map((e,t)=>(0,o.jsx)(`li`,{className:i.default.item,children:f(e)},t))});case`table`:return(0,o.jsx)(d,{block:e,inline:p});default:return null}}function d({block:e,inline:n}){let r=(0,a.useRef)(null),s=t.useScrollOverflow(r,`horizontal`);return(0,o.jsx)(`div`,{ref:r,className:i.default.tableScroll,tabIndex:s?0:void 0,role:s?`region`:void 0,"aria-label":s?`Tabela rolável`:void 0,children:(0,o.jsxs)(`table`,{className:i.default.table,children:[(0,o.jsx)(`thead`,{children:(0,o.jsx)(`tr`,{children:e.head.map((t,r)=>(0,o.jsx)(`th`,{style:c(e.align[r]??null),children:n(t)},r))})}),(0,o.jsx)(`tbody`,{children:e.rows.map((t,r)=>(0,o.jsx)(`tr`,{children:t.map((t,r)=>(0,o.jsx)(`td`,{style:c(e.align[r]??null),children:n(t)},r))},r))})]})})}exports.Markdown=l;
2
+ //# sourceMappingURL=Markdown.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Markdown.cjs","names":[],"sources":["../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { Fragment, useMemo, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { useScrollOverflow } from \"@/hooks/use-scroll-overflow\";\nimport { cn } from \"@/utils/cn\";\n\nimport { CodeBlock } from \"../CodeBlock\";\nimport {\n parseMarkdown,\n type MarkdownAlign,\n type MarkdownBlock,\n type MarkdownInline,\n} from \"./markdown-parse\";\nimport styles from \"./Markdown.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MarkdownProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The Markdown source. */\n source: string;\n /**\n * Heading level the document's `#` maps to. Default 2.\n *\n * A comment body rendered inside a page whose `<h1>` is the page title must not\n * emit a second `<h1>`; shifting the whole scale keeps the outline valid.\n */\n headingOffset?: number;\n /** Render fenced code with `CodeBlock` (copy button, line numbers). Default `true`. */\n highlightCode?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Extra props for every link — `target=\"_blank\"`, an analytics handler. */\n linkProps?: HTMLAttributes<HTMLAnchorElement> & { target?: string; rel?: string };\n}\n\n/** Render inline nodes. */\nfunction renderInline(nodes: readonly MarkdownInline[]): ReactNode {\n return nodes.map((node, index) => {\n const key = index;\n switch (node.type) {\n case \"text\":\n return <Fragment key={key}>{node.value}</Fragment>;\n case \"strong\":\n return <strong key={key}>{renderInline(node.children)}</strong>;\n case \"em\":\n return <em key={key}>{renderInline(node.children)}</em>;\n case \"del\":\n return <del key={key}>{renderInline(node.children)}</del>;\n case \"code\":\n return (\n <code key={key} className={styles.inlineCode}>\n {node.value}\n </code>\n );\n case \"image\":\n return <img key={key} className={styles.image} src={node.src} alt={node.alt} />;\n case \"break\":\n return <br key={key} />;\n default:\n return null;\n }\n });\n}\n\n/** CSS `text-align` for a table column. */\nfunction alignOf(align: MarkdownAlign): { textAlign: \"left\" | \"center\" | \"right\" } | undefined {\n return align ? { textAlign: align } : undefined;\n}\n\n/**\n * Rendered Markdown, from a node tree — never from an HTML string.\n *\n * `dangerouslySetInnerHTML` appears nowhere in this component. That is what makes\n * rendering somebody else's Markdown safe by construction rather than by a promise\n * about escaping: a `<script>` in a comment is four characters of text, because text\n * is all a React child can be.\n *\n * Link and image URLs go through a scheme **allowlist** (`http`, `https`, `mailto`,\n * `tel`, `sms`, plus relative), so `[click](javascript:alert(1))` renders as plain\n * text. A blocklist would have to enumerate every spelling of `javascript:` and\n * would miss one.\n *\n * @example\n * <Markdown source={comentario.corpo} linkProps={{ target: \"_blank\", rel: \"noreferrer\" }} />\n */\nexport function Markdown({\n source,\n headingOffset = 2,\n highlightCode = true,\n showLineNumbers = false,\n linkProps,\n className,\n ...rest\n}: MarkdownProps) {\n const blocks = useMemo(() => parseMarkdown(source), [source]);\n\n return (\n <div className={cn(styles.root, className)} {...rest}>\n {blocks.map((block, index) => (\n <Block\n key={index}\n block={block}\n headingOffset={headingOffset}\n highlightCode={highlightCode}\n showLineNumbers={showLineNumbers}\n linkProps={linkProps}\n />\n ))}\n </div>\n );\n}\n\ninterface BlockProps {\n block: MarkdownBlock;\n headingOffset: number;\n highlightCode: boolean;\n showLineNumbers: boolean;\n linkProps?: MarkdownProps[\"linkProps\"];\n}\n\n/** One block, and its children. */\nfunction Block({ block, headingOffset, highlightCode, showLineNumbers, linkProps }: BlockProps) {\n const children = (nodes: readonly MarkdownBlock[]): ReactNode =>\n nodes.map((child, index) => (\n <Block\n key={index}\n block={child}\n headingOffset={headingOffset}\n highlightCode={highlightCode}\n showLineNumbers={showLineNumbers}\n linkProps={linkProps}\n />\n ));\n\n const inline = (nodes: readonly MarkdownInline[]): ReactNode =>\n nodes.map((node, index) =>\n node.type === \"link\" ? (\n <a key={index} className={styles.link} href={node.href} {...linkProps}>\n {renderInline(node.children)}\n </a>\n ) : (\n <Fragment key={index}>{renderInline([node])}</Fragment>\n ),\n );\n\n switch (block.type) {\n case \"heading\": {\n const level = Math.min(6, Math.max(1, block.level + headingOffset - 1));\n const Tag = `h${level}` as \"h1\";\n return (\n <Tag className={styles[`h${level}` as keyof typeof styles]}>\n {inline(block.children)}\n </Tag>\n );\n }\n case \"paragraph\":\n return <p>{inline(block.children)}</p>;\n case \"code\":\n return highlightCode ? (\n <CodeBlock\n code={block.value}\n language={block.language ?? undefined}\n showLineNumbers={showLineNumbers}\n />\n ) : (\n <pre>\n <code>{block.value}</code>\n </pre>\n );\n case \"quote\":\n return <blockquote className={styles.quote}>{children(block.children)}</blockquote>;\n case \"rule\":\n return <hr className={styles.rule} />;\n case \"list\":\n return block.ordered ? (\n <ol className={styles.list} start={block.start === 1 ? undefined : block.start}>\n {block.items.map((item, index) => (\n <li key={index} className={styles.item}>\n {children(item)}\n </li>\n ))}\n </ol>\n ) : (\n <ul className={styles.list}>\n {block.items.map((item, index) => (\n <li key={index} className={styles.item}>\n {children(item)}\n </li>\n ))}\n </ul>\n );\n case \"table\":\n return <Table block={block} inline={inline} />;\n default:\n return null;\n }\n}\n\n/**\n * A table, in its own scroll box.\n *\n * The box only becomes a tab stop while it actually overflows: a scroll container\n * with nothing focusable inside cannot be reached by keyboard, and adding the stop\n * unconditionally would pollute the tab order with one entry per table on the page.\n */\nfunction Table({\n block,\n inline,\n}: {\n block: Extract<MarkdownBlock, { type: \"table\" }>;\n inline: (nodes: readonly MarkdownInline[]) => ReactNode;\n}) {\n const scroll = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(scroll, \"horizontal\");\n\n return (\n <div\n ref={scroll}\n className={styles.tableScroll}\n tabIndex={overflowing ? 0 : undefined}\n role={overflowing ? \"region\" : undefined}\n aria-label={overflowing ? \"Tabela rolável\" : undefined}\n >\n <table className={styles.table}>\n <thead>\n <tr>\n {block.head.map((cell, index) => (\n <th key={index} style={alignOf(block.align[index] ?? null)}>\n {inline(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {block.rows.map((row, rowIndex) => (\n <tr key={rowIndex}>\n {row.map((cell, cellIndex) => (\n <td key={cellIndex} style={alignOf(block.align[cellIndex] ?? null)}>\n {inline(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { useScrollOverflow as t } from "../../hooks/use-scroll-overflow.js";
3
+ import { CodeBlock as n } from "../CodeBlock/CodeBlock.js";
4
+ import { parseMarkdown as r } from "./markdown-parse.js";
5
+ import i from "./Markdown.module.js";
6
+ import { Fragment as a, useMemo as o, useRef as s } from "react";
7
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
8
+ //#region src/components/Markdown/Markdown.tsx
9
+ function u(e) {
10
+ return e.map((e, t) => {
11
+ let n = t;
12
+ switch (e.type) {
13
+ case "text": return /* @__PURE__ */ c(a, { children: e.value }, n);
14
+ case "strong": return /* @__PURE__ */ c("strong", { children: u(e.children) }, n);
15
+ case "em": return /* @__PURE__ */ c("em", { children: u(e.children) }, n);
16
+ case "del": return /* @__PURE__ */ c("del", { children: u(e.children) }, n);
17
+ case "code": return /* @__PURE__ */ c("code", {
18
+ className: i.inlineCode,
19
+ children: e.value
20
+ }, n);
21
+ case "image": return /* @__PURE__ */ c("img", {
22
+ className: i.image,
23
+ src: e.src,
24
+ alt: e.alt
25
+ }, n);
26
+ case "break": return /* @__PURE__ */ c("br", {}, n);
27
+ default: return null;
28
+ }
29
+ });
30
+ }
31
+ function d(e) {
32
+ return e ? { textAlign: e } : void 0;
33
+ }
34
+ function f({ source: t, headingOffset: n = 2, highlightCode: a = !0, showLineNumbers: s = !1, linkProps: l, className: u, ...d }) {
35
+ let f = o(() => r(t), [t]);
36
+ return /* @__PURE__ */ c("div", {
37
+ className: e(i.root, u),
38
+ ...d,
39
+ children: f.map((e, t) => /* @__PURE__ */ c(p, {
40
+ block: e,
41
+ headingOffset: n,
42
+ highlightCode: a,
43
+ showLineNumbers: s,
44
+ linkProps: l
45
+ }, t))
46
+ });
47
+ }
48
+ function p({ block: e, headingOffset: t, highlightCode: r, showLineNumbers: o, linkProps: s }) {
49
+ let l = (e) => e.map((e, n) => /* @__PURE__ */ c(p, {
50
+ block: e,
51
+ headingOffset: t,
52
+ highlightCode: r,
53
+ showLineNumbers: o,
54
+ linkProps: s
55
+ }, n)), d = (e) => e.map((e, t) => e.type === "link" ? /* @__PURE__ */ c("a", {
56
+ className: i.link,
57
+ href: e.href,
58
+ ...s,
59
+ children: u(e.children)
60
+ }, t) : /* @__PURE__ */ c(a, { children: u([e]) }, t));
61
+ switch (e.type) {
62
+ case "heading": {
63
+ let n = Math.min(6, Math.max(1, e.level + t - 1));
64
+ return /* @__PURE__ */ c(`h${n}`, {
65
+ className: i[`h${n}`],
66
+ children: d(e.children)
67
+ });
68
+ }
69
+ case "paragraph": return /* @__PURE__ */ c("p", { children: d(e.children) });
70
+ case "code": return r ? /* @__PURE__ */ c(n, {
71
+ code: e.value,
72
+ language: e.language ?? void 0,
73
+ showLineNumbers: o
74
+ }) : /* @__PURE__ */ c("pre", { children: /* @__PURE__ */ c("code", { children: e.value }) });
75
+ case "quote": return /* @__PURE__ */ c("blockquote", {
76
+ className: i.quote,
77
+ children: l(e.children)
78
+ });
79
+ case "rule": return /* @__PURE__ */ c("hr", { className: i.rule });
80
+ case "list": return e.ordered ? /* @__PURE__ */ c("ol", {
81
+ className: i.list,
82
+ start: e.start === 1 ? void 0 : e.start,
83
+ children: e.items.map((e, t) => /* @__PURE__ */ c("li", {
84
+ className: i.item,
85
+ children: l(e)
86
+ }, t))
87
+ }) : /* @__PURE__ */ c("ul", {
88
+ className: i.list,
89
+ children: e.items.map((e, t) => /* @__PURE__ */ c("li", {
90
+ className: i.item,
91
+ children: l(e)
92
+ }, t))
93
+ });
94
+ case "table": return /* @__PURE__ */ c(m, {
95
+ block: e,
96
+ inline: d
97
+ });
98
+ default: return null;
99
+ }
100
+ }
101
+ function m({ block: e, inline: n }) {
102
+ let r = s(null), a = t(r, "horizontal");
103
+ return /* @__PURE__ */ c("div", {
104
+ ref: r,
105
+ className: i.tableScroll,
106
+ tabIndex: a ? 0 : void 0,
107
+ role: a ? "region" : void 0,
108
+ "aria-label": a ? "Tabela rolável" : void 0,
109
+ children: /* @__PURE__ */ l("table", {
110
+ className: i.table,
111
+ children: [/* @__PURE__ */ c("thead", { children: /* @__PURE__ */ c("tr", { children: e.head.map((t, r) => /* @__PURE__ */ c("th", {
112
+ style: d(e.align[r] ?? null),
113
+ children: n(t)
114
+ }, r)) }) }), /* @__PURE__ */ c("tbody", { children: e.rows.map((t, r) => /* @__PURE__ */ c("tr", { children: t.map((t, r) => /* @__PURE__ */ c("td", {
115
+ style: d(e.align[r] ?? null),
116
+ children: n(t)
117
+ }, r)) }, r)) })]
118
+ })
119
+ });
120
+ }
121
+ //#endregion
122
+ export { f as Markdown };
123
+
124
+ //# sourceMappingURL=Markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Markdown.js","names":[],"sources":["../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { Fragment, useMemo, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { useScrollOverflow } from \"@/hooks/use-scroll-overflow\";\nimport { cn } from \"@/utils/cn\";\n\nimport { CodeBlock } from \"../CodeBlock\";\nimport {\n parseMarkdown,\n type MarkdownAlign,\n type MarkdownBlock,\n type MarkdownInline,\n} from \"./markdown-parse\";\nimport styles from \"./Markdown.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MarkdownProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The Markdown source. */\n source: string;\n /**\n * Heading level the document's `#` maps to. Default 2.\n *\n * A comment body rendered inside a page whose `<h1>` is the page title must not\n * emit a second `<h1>`; shifting the whole scale keeps the outline valid.\n */\n headingOffset?: number;\n /** Render fenced code with `CodeBlock` (copy button, line numbers). Default `true`. */\n highlightCode?: boolean;\n /** Show line numbers in fenced code. Default `false`. */\n showLineNumbers?: boolean;\n /** Extra props for every link — `target=\"_blank\"`, an analytics handler. */\n linkProps?: HTMLAttributes<HTMLAnchorElement> & { target?: string; rel?: string };\n}\n\n/** Render inline nodes. */\nfunction renderInline(nodes: readonly MarkdownInline[]): ReactNode {\n return nodes.map((node, index) => {\n const key = index;\n switch (node.type) {\n case \"text\":\n return <Fragment key={key}>{node.value}</Fragment>;\n case \"strong\":\n return <strong key={key}>{renderInline(node.children)}</strong>;\n case \"em\":\n return <em key={key}>{renderInline(node.children)}</em>;\n case \"del\":\n return <del key={key}>{renderInline(node.children)}</del>;\n case \"code\":\n return (\n <code key={key} className={styles.inlineCode}>\n {node.value}\n </code>\n );\n case \"image\":\n return <img key={key} className={styles.image} src={node.src} alt={node.alt} />;\n case \"break\":\n return <br key={key} />;\n default:\n return null;\n }\n });\n}\n\n/** CSS `text-align` for a table column. */\nfunction alignOf(align: MarkdownAlign): { textAlign: \"left\" | \"center\" | \"right\" } | undefined {\n return align ? { textAlign: align } : undefined;\n}\n\n/**\n * Rendered Markdown, from a node tree — never from an HTML string.\n *\n * `dangerouslySetInnerHTML` appears nowhere in this component. That is what makes\n * rendering somebody else's Markdown safe by construction rather than by a promise\n * about escaping: a `<script>` in a comment is four characters of text, because text\n * is all a React child can be.\n *\n * Link and image URLs go through a scheme **allowlist** (`http`, `https`, `mailto`,\n * `tel`, `sms`, plus relative), so `[click](javascript:alert(1))` renders as plain\n * text. A blocklist would have to enumerate every spelling of `javascript:` and\n * would miss one.\n *\n * @example\n * <Markdown source={comentario.corpo} linkProps={{ target: \"_blank\", rel: \"noreferrer\" }} />\n */\nexport function Markdown({\n source,\n headingOffset = 2,\n highlightCode = true,\n showLineNumbers = false,\n linkProps,\n className,\n ...rest\n}: MarkdownProps) {\n const blocks = useMemo(() => parseMarkdown(source), [source]);\n\n return (\n <div className={cn(styles.root, className)} {...rest}>\n {blocks.map((block, index) => (\n <Block\n key={index}\n block={block}\n headingOffset={headingOffset}\n highlightCode={highlightCode}\n showLineNumbers={showLineNumbers}\n linkProps={linkProps}\n />\n ))}\n </div>\n );\n}\n\ninterface BlockProps {\n block: MarkdownBlock;\n headingOffset: number;\n highlightCode: boolean;\n showLineNumbers: boolean;\n linkProps?: MarkdownProps[\"linkProps\"];\n}\n\n/** One block, and its children. */\nfunction Block({ block, headingOffset, highlightCode, showLineNumbers, linkProps }: BlockProps) {\n const children = (nodes: readonly MarkdownBlock[]): ReactNode =>\n nodes.map((child, index) => (\n <Block\n key={index}\n block={child}\n headingOffset={headingOffset}\n highlightCode={highlightCode}\n showLineNumbers={showLineNumbers}\n linkProps={linkProps}\n />\n ));\n\n const inline = (nodes: readonly MarkdownInline[]): ReactNode =>\n nodes.map((node, index) =>\n node.type === \"link\" ? (\n <a key={index} className={styles.link} href={node.href} {...linkProps}>\n {renderInline(node.children)}\n </a>\n ) : (\n <Fragment key={index}>{renderInline([node])}</Fragment>\n ),\n );\n\n switch (block.type) {\n case \"heading\": {\n const level = Math.min(6, Math.max(1, block.level + headingOffset - 1));\n const Tag = `h${level}` as \"h1\";\n return (\n <Tag className={styles[`h${level}` as keyof typeof styles]}>\n {inline(block.children)}\n </Tag>\n );\n }\n case \"paragraph\":\n return <p>{inline(block.children)}</p>;\n case \"code\":\n return highlightCode ? (\n <CodeBlock\n code={block.value}\n language={block.language ?? undefined}\n showLineNumbers={showLineNumbers}\n />\n ) : (\n <pre>\n <code>{block.value}</code>\n </pre>\n );\n case \"quote\":\n return <blockquote className={styles.quote}>{children(block.children)}</blockquote>;\n case \"rule\":\n return <hr className={styles.rule} />;\n case \"list\":\n return block.ordered ? (\n <ol className={styles.list} start={block.start === 1 ? undefined : block.start}>\n {block.items.map((item, index) => (\n <li key={index} className={styles.item}>\n {children(item)}\n </li>\n ))}\n </ol>\n ) : (\n <ul className={styles.list}>\n {block.items.map((item, index) => (\n <li key={index} className={styles.item}>\n {children(item)}\n </li>\n ))}\n </ul>\n );\n case \"table\":\n return <Table block={block} inline={inline} />;\n default:\n return null;\n }\n}\n\n/**\n * A table, in its own scroll box.\n *\n * The box only becomes a tab stop while it actually overflows: a scroll container\n * with nothing focusable inside cannot be reached by keyboard, and adding the stop\n * unconditionally would pollute the tab order with one entry per table on the page.\n */\nfunction Table({\n block,\n inline,\n}: {\n block: Extract<MarkdownBlock, { type: \"table\" }>;\n inline: (nodes: readonly MarkdownInline[]) => ReactNode;\n}) {\n const scroll = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(scroll, \"horizontal\");\n\n return (\n <div\n ref={scroll}\n className={styles.tableScroll}\n tabIndex={overflowing ? 0 : undefined}\n role={overflowing ? \"region\" : undefined}\n aria-label={overflowing ? \"Tabela rolável\" : undefined}\n >\n <table className={styles.table}>\n <thead>\n <tr>\n {block.head.map((cell, index) => (\n <th key={index} style={alignOf(block.align[index] ?? null)}>\n {inline(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {block.rows.map((row, rowIndex) => (\n <tr key={rowIndex}>\n {row.map((cell, cellIndex) => (\n <td key={cellIndex} style={alignOf(block.align[cellIndex] ?? null)}>\n {inline(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAoCA,SAAS,EAAa,GAA6C;CAC/D,OAAO,EAAM,KAAK,GAAM,MAAU;EAC9B,IAAM,IAAM;EACZ,QAAQ,EAAK,MAAb;GACI,KAAK,QACD,OAAO,kBAAC,GAAD,EAAA,UAAqB,EAAK,MAAgB,GAA3B,CAA2B;GACrD,KAAK,UACD,OAAO,kBAAC,UAAD,EAAA,UAAmB,EAAa,EAAK,QAAQ,EAAU,GAA1C,CAA0C;GAClE,KAAK,MACD,OAAO,kBAAC,MAAD,EAAA,UAAe,EAAa,EAAK,QAAQ,EAAM,GAAtC,CAAsC;GAC1D,KAAK,OACD,OAAO,kBAAC,OAAD,EAAA,UAAgB,EAAa,EAAK,QAAQ,EAAO,GAAvC,CAAuC;GAC5D,KAAK,QACD,OACI,kBAAC,QAAD;IAAgB,WAAW,EAAO;cAC7B,EAAK;GACJ,GAFK,CAEL;GAEd,KAAK,SACD,OAAO,kBAAC,OAAD;IAAe,WAAW,EAAO;IAAO,KAAK,EAAK;IAAK,KAAK,EAAK;GAAM,GAA7D,CAA6D;GAClF,KAAK,SACD,OAAO,kBAAC,MAAD,CAAe,GAAN,CAAM;GAC1B,SACI,OAAO;EACf;CACJ,CAAC;AACL;AAGA,SAAS,EAAQ,GAA8E;CAC3F,OAAO,IAAQ,EAAE,WAAW,EAAM,IAAI,KAAA;AAC1C;AAkBA,SAAgB,EAAS,EACrB,WACA,mBAAgB,GAChB,mBAAgB,IAChB,qBAAkB,IAClB,cACA,cACA,GAAG,KACW;CACd,IAAM,IAAS,QAAc,EAAc,CAAM,GAAG,CAAC,CAAM,CAAC;CAE5D,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,MAAM,CAAS;EAAG,GAAI;YAC3C,EAAO,KAAK,GAAO,MAChB,kBAAC,GAAD;GAEW;GACQ;GACA;GACE;GACN;EACd,GANQ,CAMR,CACJ;CACA,CAAA;AAEb;AAWA,SAAS,EAAM,EAAE,UAAO,kBAAe,kBAAe,oBAAiB,gBAAyB;CAC5F,IAAM,KAAY,MACd,EAAM,KAAK,GAAO,MACd,kBAAC,GAAD;EAEI,OAAO;EACQ;EACA;EACE;EACN;CACd,GANQ,CAMR,CACJ,GAEC,KAAU,MACZ,EAAM,KAAK,GAAM,MACb,EAAK,SAAS,SACV,kBAAC,KAAD;EAAe,WAAW,EAAO;EAAM,MAAM,EAAK;EAAM,GAAI;YACvD,EAAa,EAAK,QAAQ;CAC5B,GAFK,CAEL,IAEH,kBAAC,GAAD,EAAA,UAAuB,EAAa,CAAC,CAAI,CAAC,EAAY,GAAvC,CAAuC,CAE9D;CAEJ,QAAQ,EAAM,MAAd;EACI,KAAK,WAAW;GACZ,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAM,QAAQ,IAAgB,CAAC,CAAC;GAEtE,OACI,kBAAC,IAFW,KAEZ;IAAK,WAAW,EAAO,IAAI;cACtB,EAAO,EAAM,QAAQ;GACrB,CAAA;EAEb;EACA,KAAK,aACD,OAAO,kBAAC,KAAD,EAAA,UAAI,EAAO,EAAM,QAAQ,EAAK,CAAA;EACzC,KAAK,QACD,OAAO,IACH,kBAAC,GAAD;GACI,MAAM,EAAM;GACZ,UAAU,EAAM,YAAY,KAAA;GACX;EACpB,CAAA,IAED,kBAAC,OAAD,EAAA,UACI,kBAAC,QAAD,EAAA,UAAO,EAAM,MAAY,CAAA,EACxB,CAAA;EAEb,KAAK,SACD,OAAO,kBAAC,cAAD;GAAY,WAAW,EAAO;aAAQ,EAAS,EAAM,QAAQ;EAAc,CAAA;EACtF,KAAK,QACD,OAAO,kBAAC,MAAD,EAAI,WAAW,EAAO,KAAO,CAAA;EACxC,KAAK,QACD,OAAO,EAAM,UACT,kBAAC,MAAD;GAAI,WAAW,EAAO;GAAM,OAAO,EAAM,UAAU,IAAI,KAAA,IAAY,EAAM;aACpE,EAAM,MAAM,KAAK,GAAM,MACpB,kBAAC,MAAD;IAAgB,WAAW,EAAO;cAC7B,EAAS,CAAI;GACd,GAFK,CAEL,CACP;EACD,CAAA,IAEJ,kBAAC,MAAD;GAAI,WAAW,EAAO;aACjB,EAAM,MAAM,KAAK,GAAM,MACpB,kBAAC,MAAD;IAAgB,WAAW,EAAO;cAC7B,EAAS,CAAI;GACd,GAFK,CAEL,CACP;EACD,CAAA;EAEZ,KAAK,SACD,OAAO,kBAAC,GAAD;GAAc;GAAe;EAAS,CAAA;EACjD,SACI,OAAO;CACf;AACJ;AASA,SAAS,EAAM,EACX,UACA,aAID;CACC,IAAM,IAAS,EAA8B,IAAI,GAC3C,IAAc,EAAkB,GAAQ,YAAY;CAE1D,OACI,kBAAC,OAAD;EACI,KAAK;EACL,WAAW,EAAO;EAClB,UAAU,IAAc,IAAI,KAAA;EAC5B,MAAM,IAAc,WAAW,KAAA;EAC/B,cAAY,IAAc,mBAAmB,KAAA;YAE7C,kBAAC,SAAD;GAAO,WAAW,EAAO;aAAzB,CACI,kBAAC,SAAD,EAAA,UACI,kBAAC,MAAD,EAAA,UACK,EAAM,KAAK,KAAK,GAAM,MACnB,kBAAC,MAAD;IAAgB,OAAO,EAAQ,EAAM,MAAM,MAAU,IAAI;cACpD,EAAO,CAAI;GACZ,GAFK,CAEL,CACP,EACD,CAAA,EACD,CAAA,GACP,kBAAC,SAAD,EAAA,UACK,EAAM,KAAK,KAAK,GAAK,MAClB,kBAAC,MAAD,EAAA,UACK,EAAI,KAAK,GAAM,MACZ,kBAAC,MAAD;IAAoB,OAAO,EAAQ,EAAM,MAAM,MAAc,IAAI;cAC5D,EAAO,CAAI;GACZ,GAFK,CAEL,CACP,EACD,GANK,CAML,CACP,EACE,CAAA,CACJ;;CACN,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_root_L7b3z`,t=`tempest_h1_wpviN`,n=`tempest_h2_H0B9X`,r=`tempest_h3_zI9g3`,i=`tempest_h4_w1Khg`,a=`tempest_h5_MN-QR`,o=`tempest_h6_SFcgF`,s=`tempest_list_QopC7`,c=`tempest_item_0veAk`,l=`tempest_quote_QK3OF`,u=`tempest_rule_M019c`,d=`tempest_inlineCode_XsQik`,f=`tempest_link_prTnN`,p=`tempest_image_HjzdE`,m=`tempest_tableScroll_2u5gY`,h=`tempest_table_-CmP5`,g={root:e,h1:t,h2:n,h3:r,h4:i,h5:a,h6:o,list:s,item:c,quote:l,rule:u,inlineCode:d,link:f,image:p,tableScroll:m,table:h};exports.default=g,exports.h1=t,exports.h2=n,exports.h3=r,exports.h4=i,exports.h5=a,exports.h6=o,exports.image=p,exports.inlineCode=d,exports.item=c,exports.link=f,exports.list=s,exports.quote=l,exports.root=e,exports.rule=u,exports.table=h,exports.tableScroll=m;
2
+ //# sourceMappingURL=Markdown.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Markdown.module.cjs","names":[],"sources":["../../../src/components/Markdown/Markdown.module.css"],"sourcesContent":[".root {\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-relaxed);\n overflow-wrap: anywhere;\n}\n\n/*\n * Every block spaces itself with `margin-block-end` and the last child clears it,\n * so a Markdown body dropped into a card does not add a phantom gap at the bottom.\n */\n.root > * {\n margin-block: 0 var(--tempest-space-3);\n}\n\n.root > *:last-child {\n margin-block-end: 0;\n}\n\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n margin-block-start: var(--tempest-space-5);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-tight);\n}\n\n.root > .h1:first-child,\n.root > .h2:first-child,\n.root > .h3:first-child,\n.root > .h4:first-child,\n.root > .h5:first-child,\n.root > .h6:first-child {\n margin-block-start: 0;\n}\n\n.h1 {\n font-size: var(--tempest-text-2xl);\n}\n\n.h2 {\n font-size: var(--tempest-text-xl);\n}\n\n.h3 {\n font-size: var(--tempest-text-lg);\n}\n\n.h4,\n.h5,\n.h6 {\n font-size: var(--tempest-text-base);\n}\n\n.list {\n padding-inline-start: var(--tempest-space-6);\n}\n\n.list .list {\n margin-block: var(--tempest-space-1) 0;\n}\n\n.item {\n margin-block-end: var(--tempest-space-1);\n}\n\n.item > p {\n margin: 0;\n}\n\n.quote {\n padding-inline-start: var(--tempest-space-4);\n border-inline-start: 3px solid var(--tempest-border-strong);\n color: var(--tempest-text-muted);\n}\n\n.rule {\n height: 1px;\n border: 0;\n background-color: var(--tempest-border);\n}\n\n.inlineCode {\n padding: 0.1em 0.35em;\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface-2);\n color: var(--tempest-code-text, var(--tempest-text));\n font-family: var(--tempest-font-mono);\n font-size: 0.9em;\n}\n\n.link {\n color: var(--tempest-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n\n.image {\n max-width: 100%;\n height: auto;\n border-radius: var(--tempest-radius-md);\n}\n\n/*\n * A wide table scrolls inside its own box, and that box is focusable — a scroll\n * container with nothing focusable inside cannot be reached by keyboard, which is\n * the same defect the Table fix addressed.\n */\n.tableScroll {\n overflow-x: auto;\n}\n\n.tableScroll:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--tempest-text-sm);\n}\n\n.table th,\n.table td {\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border-bottom: 1px solid var(--tempest-border);\n text-align: left;\n}\n\n.table th {\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-semibold);\n}\n"],"mappings":""}
@@ -0,0 +1,23 @@
1
+ //#region src/components/Markdown/Markdown.module.css
2
+ var e = "tempest_root_L7b3z", t = "tempest_h1_wpviN", n = "tempest_h2_H0B9X", r = "tempest_h3_zI9g3", i = "tempest_h4_w1Khg", a = "tempest_h5_MN-QR", o = "tempest_h6_SFcgF", s = "tempest_list_QopC7", c = "tempest_item_0veAk", l = "tempest_quote_QK3OF", u = "tempest_rule_M019c", d = "tempest_inlineCode_XsQik", f = "tempest_link_prTnN", p = "tempest_image_HjzdE", m = "tempest_tableScroll_2u5gY", h = "tempest_table_-CmP5", g = {
3
+ root: e,
4
+ h1: t,
5
+ h2: n,
6
+ h3: r,
7
+ h4: i,
8
+ h5: a,
9
+ h6: o,
10
+ list: s,
11
+ item: c,
12
+ quote: l,
13
+ rule: u,
14
+ inlineCode: d,
15
+ link: f,
16
+ image: p,
17
+ tableScroll: m,
18
+ table: h
19
+ };
20
+ //#endregion
21
+ export { g as default, t as h1, n as h2, r as h3, i as h4, a as h5, o as h6, p as image, d as inlineCode, c as item, f as link, s as list, l as quote, e as root, u as rule, h as table, m as tableScroll };
22
+
23
+ //# sourceMappingURL=Markdown.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Markdown.module.js","names":[],"sources":["../../../src/components/Markdown/Markdown.module.css"],"sourcesContent":[".root {\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-relaxed);\n overflow-wrap: anywhere;\n}\n\n/*\n * Every block spaces itself with `margin-block-end` and the last child clears it,\n * so a Markdown body dropped into a card does not add a phantom gap at the bottom.\n */\n.root > * {\n margin-block: 0 var(--tempest-space-3);\n}\n\n.root > *:last-child {\n margin-block-end: 0;\n}\n\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n margin-block-start: var(--tempest-space-5);\n font-weight: var(--tempest-weight-semibold);\n line-height: var(--tempest-leading-tight);\n}\n\n.root > .h1:first-child,\n.root > .h2:first-child,\n.root > .h3:first-child,\n.root > .h4:first-child,\n.root > .h5:first-child,\n.root > .h6:first-child {\n margin-block-start: 0;\n}\n\n.h1 {\n font-size: var(--tempest-text-2xl);\n}\n\n.h2 {\n font-size: var(--tempest-text-xl);\n}\n\n.h3 {\n font-size: var(--tempest-text-lg);\n}\n\n.h4,\n.h5,\n.h6 {\n font-size: var(--tempest-text-base);\n}\n\n.list {\n padding-inline-start: var(--tempest-space-6);\n}\n\n.list .list {\n margin-block: var(--tempest-space-1) 0;\n}\n\n.item {\n margin-block-end: var(--tempest-space-1);\n}\n\n.item > p {\n margin: 0;\n}\n\n.quote {\n padding-inline-start: var(--tempest-space-4);\n border-inline-start: 3px solid var(--tempest-border-strong);\n color: var(--tempest-text-muted);\n}\n\n.rule {\n height: 1px;\n border: 0;\n background-color: var(--tempest-border);\n}\n\n.inlineCode {\n padding: 0.1em 0.35em;\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface-2);\n color: var(--tempest-code-text, var(--tempest-text));\n font-family: var(--tempest-font-mono);\n font-size: 0.9em;\n}\n\n.link {\n color: var(--tempest-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n\n.image {\n max-width: 100%;\n height: auto;\n border-radius: var(--tempest-radius-md);\n}\n\n/*\n * A wide table scrolls inside its own box, and that box is focusable — a scroll\n * container with nothing focusable inside cannot be reached by keyboard, which is\n * the same defect the Table fix addressed.\n */\n.tableScroll {\n overflow-x: auto;\n}\n\n.tableScroll:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--tempest-text-sm);\n}\n\n.table th,\n.table td {\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border-bottom: 1px solid var(--tempest-border);\n text-align: left;\n}\n\n.table th {\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-semibold);\n}\n"],"mappings":""}
@@ -0,0 +1,7 @@
1
+ const e=require("./markdown-url.cjs");var t=/^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)\s*$/,n=/^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/,r=/^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/,i=/^\s{0,3}>\s?(.*)$/,a=/^(\s*)([-*+])\s+(.*)$/,o=/^(\s*)(\d{1,9})[.)]\s+(.*)$/,s=/^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?\s*$/;function c(e){return l(e.replace(/\r\n?/g,`
2
+ `).split(`
3
+ `))}function l(e){let c=[],d=0;for(;d<e.length;){let p=e[d];if(p.trim()===``){d+=1;continue}let h=t.exec(p);if(h){let t=h[1][0],n=[];for(d+=1;d<e.length&&!RegExp(`^\\s{0,3}${t}{3,}\\s*$`).test(e[d]);)n.push(e[d]),d+=1;d+=1,c.push({type:`code`,language:h[2]||null,value:n.join(`
4
+ `)});continue}let g=n.exec(p);if(g){c.push({type:`heading`,level:g[1].length,children:m(g[2])}),d+=1;continue}if(r.test(p)){c.push({type:`rule`}),d+=1;continue}if(i.test(p)){let t=[];for(;d<e.length;){let n=i.exec(e[d]);if(n){t.push(n[1]),d+=1;continue}if(e[d].trim()===``)break;t.push(e[d]),d+=1}c.push({type:`quote`,children:l(t)});continue}if(a.test(p)||o.test(p)){let[t,n]=u(e,d);c.push(t),d=n;continue}if(d+1<e.length&&p.includes(`|`)&&s.test(e[d+1])){let[t,n]=f(e,d);c.push(t),d=n;continue}let _=[];for(;d<e.length&&e[d].trim()!==``;){let s=e[d];if(n.test(s)||r.test(s)||t.test(s)||i.test(s)||a.test(s)||o.test(s))break;_.push(s.trim()),d+=1}_.length>0&&c.push({type:`paragraph`,children:m(_.join(`
5
+ `))})}return c}function u(e,t){let n=a.exec(e[t]),r=o.exec(e[t]),i=r!==null,s=((r??n)?.[1]??``).length,c=i?Number(r?.[2]??1):1,u=[],d=[],f=t,p=()=>{d.length!==0&&(u.push(l(d)),d=[])};for(;f<e.length;){let t=e[f];if(t.trim()===``){if(f+1<e.length&&e[f+1].trim()!==``){d.push(``),f+=1;continue}break}let n=a.exec(t),r=o.exec(t),c=n??r;if(c&&c[1].length<=s){if(!(i?r:n))break;p(),d.push(c[3]),f+=1;continue}if(c){d.push(t.slice(s)),f+=1;continue}if(t.search(/\S/)>s){d.push(t.trim()),f+=1;continue}break}return p(),[{type:`list`,ordered:i,start:c,items:u},f]}function d(e){let t=e.trim().replace(/^\|/,``).replace(/\|$/,``),n=[],r=``;for(let e=0;e<t.length;e+=1){if(t[e]===`\\`&&t[e+1]===`|`){r+=`|`,e+=1;continue}if(t[e]===`|`){n.push(r),r=``;continue}r+=t[e]}return n.push(r),n.map(e=>e.trim())}function f(e,t){let n=d(e[t]),r=d(e[t+1]).map(e=>{let t=e.startsWith(`:`),n=e.endsWith(`:`);return t&&n?`center`:n?`right`:t?`left`:null}),i=[],a=t+2;for(;a<e.length&&e[a].trim()!==``&&e[a].includes(`|`);)i.push(d(e[a]).map(m)),a+=1;return[{type:`table`,align:r,head:n.map(m),rows:i},a]}var p=[{pattern:/^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeImageUrl(t[2]);return n?{type:`image`,src:n,alt:t[1]}:{type:`text`,value:t[1]}}},{pattern:/^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeLinkUrl(t[2]),r=m(t[1]);return n?{type:`link`,href:n,children:r}:{type:`text`,value:t[1]}}},{pattern:/^`([^`]+)`/,build:e=>({type:`code`,value:e[1]})},{pattern:/^\*\*([\s\S]+?)\*\*/,build:e=>({type:`strong`,children:m(e[1])})},{pattern:/^__([\s\S]+?)__/,build:e=>({type:`strong`,children:m(e[1])})},{pattern:/^~~([\s\S]+?)~~/,build:e=>({type:`del`,children:m(e[1])})},{pattern:/^\*([^*\n]+)\*/,build:e=>({type:`em`,children:m(e[1])})},{pattern:/^_([^_\n]+)_/,build:e=>({type:`em`,children:m(e[1])})},{pattern:/^<((?:https?:\/\/|mailto:)[^>\s]+)>/,build:t=>{let n=e.safeLinkUrl(t[1]);return n?{type:`link`,href:n,children:[{type:`text`,value:t[1]}]}:{type:`text`,value:t[1]}}},{pattern:/^ {2,}\n/,build:()=>({type:`break`})},{pattern:/^\\\n/,build:()=>({type:`break`})}];function m(e){let t=[],n=``,r=0,i=()=>{n&&t.push({type:`text`,value:n}),n=``};for(;r<e.length;){if(e[r]===`\\`&&r+1<e.length&&e[r+1]!==`
6
+ `){n+=e[r+1],r+=2;continue}let a=e.slice(r),o=!1;for(let e of p){let n=e.pattern.exec(a);if(!n)continue;let s=e.build(n);if(s){i(),t.push(s),r+=n[0].length,o=!0;break}}o||(n+=e[r],r+=1)}return i(),t}exports.parseInline=m,exports.parseMarkdown=c;
7
+ //# sourceMappingURL=markdown-parse.cjs.map