@k2b/ui 0.13.0 → 0.15.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 (74) hide show
  1. package/dist/browser/content/FileGrid.js +28 -8
  2. package/dist/browser/content/FileGrid.js.map +1 -1
  3. package/dist/browser/content/MarkdownView.js +21 -7
  4. package/dist/browser/content/MarkdownView.js.map +1 -1
  5. package/dist/browser/content/MessageRow.js +1250 -0
  6. package/dist/browser/content/MessageRow.js.map +1 -0
  7. package/dist/browser/content/VirtualFeed.js +1127 -0
  8. package/dist/browser/content/VirtualFeed.js.map +1 -0
  9. package/dist/browser/content/collection-selection.js +27 -11
  10. package/dist/browser/content/collection-selection.js.map +1 -1
  11. package/dist/browser/content/index.js +3 -0
  12. package/dist/browser/content/index.js.map +1 -1
  13. package/dist/browser/content/markdown-info-blocks.js +363 -0
  14. package/dist/browser/content/markdown-info-blocks.js.map +1 -0
  15. package/dist/browser/feedback/InstallGuide.js +199 -145
  16. package/dist/browser/feedback/InstallGuide.js.map +1 -1
  17. package/dist/browser/feedback/dialog-core.js +6 -1
  18. package/dist/browser/feedback/dialog-core.js.map +1 -1
  19. package/dist/browser/feedback/install.js +8 -4
  20. package/dist/browser/feedback/install.js.map +1 -1
  21. package/dist/browser/inputs/QrScanner.js +101 -21
  22. package/dist/browser/inputs/QrScanner.js.map +1 -1
  23. package/dist/browser/intl/messages.js +92 -0
  24. package/dist/browser/intl/messages.js.map +1 -1
  25. package/dist/browser/layout/MobileShell.js +48 -6
  26. package/dist/browser/layout/MobileShell.js.map +1 -1
  27. package/dist/browser/layout/TabBar.js +8 -4
  28. package/dist/browser/layout/TabBar.js.map +1 -1
  29. package/dist/browser/surfaces/Avatar.js +33 -17
  30. package/dist/browser/surfaces/Avatar.js.map +1 -1
  31. package/dist/ssr/content/FileGrid.js +17 -3
  32. package/dist/ssr/content/FileGrid.js.map +1 -1
  33. package/dist/ssr/content/MarkdownView.js +21 -7
  34. package/dist/ssr/content/MarkdownView.js.map +1 -1
  35. package/dist/ssr/content/MessageRow.js +796 -0
  36. package/dist/ssr/content/MessageRow.js.map +1 -0
  37. package/dist/ssr/content/VirtualFeed.js +989 -0
  38. package/dist/ssr/content/VirtualFeed.js.map +1 -0
  39. package/dist/ssr/content/collection-selection.js +27 -11
  40. package/dist/ssr/content/collection-selection.js.map +1 -1
  41. package/dist/ssr/content/index.js +3 -0
  42. package/dist/ssr/content/index.js.map +1 -1
  43. package/dist/ssr/content/markdown-info-blocks.js +363 -0
  44. package/dist/ssr/content/markdown-info-blocks.js.map +1 -0
  45. package/dist/ssr/feedback/InstallGuide.js +150 -98
  46. package/dist/ssr/feedback/InstallGuide.js.map +1 -1
  47. package/dist/ssr/feedback/dialog-core.js +6 -1
  48. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  49. package/dist/ssr/feedback/install.js +8 -4
  50. package/dist/ssr/feedback/install.js.map +1 -1
  51. package/dist/ssr/inputs/QrScanner.js +101 -21
  52. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  53. package/dist/ssr/intl/messages.js +92 -0
  54. package/dist/ssr/intl/messages.js.map +1 -1
  55. package/dist/ssr/layout/MobileShell.js +48 -6
  56. package/dist/ssr/layout/MobileShell.js.map +1 -1
  57. package/dist/ssr/layout/TabBar.js +3 -2
  58. package/dist/ssr/layout/TabBar.js.map +1 -1
  59. package/dist/ssr/surfaces/Avatar.js +14 -1
  60. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  61. package/dist/styles.css +1 -1
  62. package/dist/types/content/FileGrid.d.ts +6 -1
  63. package/dist/types/content/MarkdownView.d.ts +14 -2
  64. package/dist/types/content/MessageRow.d.ts +168 -0
  65. package/dist/types/content/VirtualFeed.d.ts +81 -0
  66. package/dist/types/content/collection-selection.d.ts +15 -1
  67. package/dist/types/content/index.d.ts +5 -0
  68. package/dist/types/content/markdown-info-blocks.d.ts +67 -0
  69. package/dist/types/feedback/InstallGuide.d.ts +5 -3
  70. package/dist/types/feedback/install.d.ts +4 -3
  71. package/dist/types/inputs/QrScanner.d.ts +10 -2
  72. package/dist/types/intl/messages.d.ts +46 -0
  73. package/dist/types/layout/TabBar.d.ts +4 -1
  74. package/package.json +1 -1
@@ -18,6 +18,19 @@ const initialsFor = name => {
18
18
  if (parts.length === 1) return parts[0]?.slice(0, 2).toUpperCase() ?? "?";
19
19
  return `${parts[0]?.[0] ?? ""}${parts.at(-1)?.[0] ?? ""}`.toUpperCase();
20
20
  };
21
+
22
+ /** Tints behind initials, as many as `.k2b-avatar[data-tint]` rules in the stylesheet. */
23
+ const TINTS = 10;
24
+
25
+ /**
26
+ * The tint behind initials and icons, from the name, so the same name has one color wherever an avatar shows it. The
27
+ * hash is the one of `@k2b/stdlib`'s generated avatar.
28
+ */
29
+ const tintFor = name => {
30
+ let hash = 0;
31
+ for (let index = 0; index < name.length; index++) hash = hash * 31 + name.charCodeAt(index) >>> 0;
32
+ return hash % TINTS;
33
+ };
21
34
  export function Avatar(props) {
22
35
  const messages = useUiMessages();
23
36
  const [failed, setFailed] = createSignal(false);
@@ -45,20 +58,23 @@ export function Avatar(props) {
45
58
  _$effect(_p$ => {
46
59
  var _v$ = className(),
47
60
  _v$2 = props.size ?? "md",
48
- _v$3 = props.style,
49
- _v$4 = props.alt ?? messages().avatarLabel({
61
+ _v$3 = tintFor(props.name.trim()),
62
+ _v$4 = props.style,
63
+ _v$5 = props.alt ?? messages().avatarLabel({
50
64
  name: label()
51
65
  });
52
66
  _v$ !== _p$.e && _$className(_el$2, _p$.e = _v$);
53
67
  _v$2 !== _p$.t && _$setAttribute(_el$2, "data-size", _p$.t = _v$2);
54
- _p$.a = _$style(_el$2, _v$3, _p$.a);
55
- _v$4 !== _p$.o && _$setAttribute(_el$2, "aria-label", _p$.o = _v$4);
68
+ _v$3 !== _p$.a && _$setAttribute(_el$2, "data-tint", _p$.a = _v$3);
69
+ _p$.o = _$style(_el$2, _v$4, _p$.o);
70
+ _v$5 !== _p$.i && _$setAttribute(_el$2, "aria-label", _p$.i = _v$5);
56
71
  return _p$;
57
72
  }, {
58
73
  e: undefined,
59
74
  t: undefined,
60
75
  a: undefined,
61
- o: undefined
76
+ o: undefined,
77
+ i: undefined
62
78
  });
63
79
  return _el$2;
64
80
  })();
@@ -67,20 +83,20 @@ export function Avatar(props) {
67
83
  var _el$3 = _$getNextElement(_tmpl$3);
68
84
  _el$3.addEventListener("error", () => setFailed(true));
69
85
  _$effect(_p$ => {
70
- var _v$5 = src(),
71
- _v$6 = props.alt ?? messages().avatarLabel({
86
+ var _v$6 = src(),
87
+ _v$7 = props.alt ?? messages().avatarLabel({
72
88
  name: label()
73
89
  }),
74
- _v$7 = className(),
75
- _v$8 = props.size ?? "md",
76
- _v$9 = props.style,
77
- _v$0 = props.loading ?? "lazy";
78
- _v$5 !== _p$.e && _$setAttribute(_el$3, "src", _p$.e = _v$5);
79
- _v$6 !== _p$.t && _$setAttribute(_el$3, "alt", _p$.t = _v$6);
80
- _v$7 !== _p$.a && _$className(_el$3, _p$.a = _v$7);
81
- _v$8 !== _p$.o && _$setAttribute(_el$3, "data-size", _p$.o = _v$8);
82
- _p$.i = _$style(_el$3, _v$9, _p$.i);
83
- _v$0 !== _p$.n && _$setAttribute(_el$3, "loading", _p$.n = _v$0);
90
+ _v$8 = className(),
91
+ _v$9 = props.size ?? "md",
92
+ _v$0 = props.style,
93
+ _v$1 = props.loading ?? "lazy";
94
+ _v$6 !== _p$.e && _$setAttribute(_el$3, "src", _p$.e = _v$6);
95
+ _v$7 !== _p$.t && _$setAttribute(_el$3, "alt", _p$.t = _v$7);
96
+ _v$8 !== _p$.a && _$className(_el$3, _p$.a = _v$8);
97
+ _v$9 !== _p$.o && _$setAttribute(_el$3, "data-size", _p$.o = _v$9);
98
+ _p$.i = _$style(_el$3, _v$0, _p$.i);
99
+ _v$1 !== _p$.n && _$setAttribute(_el$3, "loading", _p$.n = _v$1);
84
100
  return _p$;
85
101
  }, {
86
102
  e: undefined,
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["surfaces/Avatar.tsx"],"sourcesContent":["import { createEffect, createSignal, type JSX, on, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\n\nexport type AvatarSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport type AvatarProps = {\n name: string;\n src?: string | null;\n icon?: string;\n alt?: string;\n fallback?: string;\n size?: AvatarSize;\n loading?: \"eager\" | \"lazy\";\n class?: string;\n style?: JSX.CSSProperties | string;\n};\n\nconst initialsFor = (name: string): string => {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"?\";\n if (parts.length === 1) return parts[0]?.slice(0, 2).toUpperCase() ?? \"?\";\n return `${parts[0]?.[0] ?? \"\"}${parts.at(-1)?.[0] ?? \"\"}`.toUpperCase();\n};\n\nexport function Avatar(props: AvatarProps): JSX.Element {\n const messages = useUiMessages();\n const [failed, setFailed] = createSignal(false);\n const label = () => props.name.trim() || messages().unknownUser;\n // Derive initials from the given name, not from the accessible-name fallback:\n // an empty `name` must render \"?\" the way Cloud's avatar does, not \"UU\".\n const fallback = () => props.fallback ?? initialsFor(props.name);\n const fallbackContent = () => (props.icon ? <i class={props.icon} aria-hidden=\"true\" /> : fallback());\n const className = () => `k2b-avatar ${props.class ?? \"\"}`;\n\n createEffect(\n on(\n () => props.src,\n () => setFailed(false),\n { defer: true },\n ),\n );\n\n return (\n <Show\n when={!failed() ? props.src || undefined : undefined}\n fallback={\n <span\n class={className()}\n data-size={props.size ?? \"md\"}\n style={props.style}\n role=\"img\"\n aria-label={props.alt ?? messages().avatarLabel({ name: label() })}\n >\n {fallbackContent()}\n </span>\n }\n >\n {(src) => (\n <img\n src={src()}\n alt={props.alt ?? messages().avatarLabel({ name: label() })}\n class={className()}\n data-size={props.size ?? \"md\"}\n style={props.style}\n loading={props.loading ?? \"lazy\"}\n decoding=\"async\"\n onError={() => setFailed(true)}\n />\n )}\n </Show>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,YAAY,EAAE,YAAY,EAAY,EAAE,EAAE,IAAI,QAAQ,UAAU;AACzE,SAAS,aAAa,QAAQ,kBAAkB;AAgBhD,MAAM,WAAW,GAAI,IAAY,IAAa;EAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;EACtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,GAAG;EAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,GAAG;EACzE,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC;AACzE,CAAC;AAED,OAAO,SAAS,MAAM,CAAC,KAAkB,EAAe;EACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;EAChC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC;EAC/C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW;EAC/D;EACA;EACA,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC;EAChE,MAAM,eAAe,GAAG,MAAO,KAAK,CAAC,IAAI;IAAA;IAAA,iCAAa,KAAK,CAAC,IAAI;IAAA;EAAA,OAA0B,QAAQ,CAAC,CAAE;EACrG,MAAM,SAAS,GAAG,MAAM,cAAc,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE;EAEzD,YAAY,CACV,EAAE,CACA,MAAM,KAAK,CAAC,GAAG,EACf,MAAM,SAAS,CAAC,KAAK,CAAC,EACtB;IAAE,KAAK,EAAE;EAAK,CAChB,CACF,CAAC;EAED,yBACG,IAAI;IAAA,IACH,IAAI;MAAA,OAAE,gBAAC,MAAM,CAAC,CAAC,MAAG,KAAK,CAAC,GAAG,IAAI,SAAS,GAAG,SAAS;IAAA;IAAA,IACpD,QAAQ;MAAA;QAAA;QAAA,gBAQH,eAAe;QAAA;UAAA,UANT,SAAS,CAAC,CAAC;YAAA,OACP,KAAK,CAAC,IAAI,IAAI,IAAI;YAAA,OACtB,KAAK,CAAC,KAAK;YAAA,OAEN,KAAK,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;cAAE,IAAI,EAAE,KAAK,CAAC;YAAE,CAAC,CAAC;UAAA;UAAA;UAAA;UAAA;UAAA;QAAA;UAAA;UAAA;UAAA;UAAA;QAAA;QAAA;MAAA;IAAA;IAAA,UAMpE,GAAG;MAAA;MAAA,gCASQ,MAAM,SAAS,CAAC,IAAI,CAAC;MAAA;QAAA,WAPzB,GAAG,CAAC,CAAC;UAAA,OACL,KAAK,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;YAAE,IAAI,EAAE,KAAK,CAAC;UAAE,CAAC,CAAC;UAAA,OACpD,SAAS,CAAC,CAAC;UAAA,OACP,KAAK,CAAC,IAAI,IAAI,IAAI;UAAA,OACtB,KAAK,CAAC,KAAK;UAAA,OACT,KAAK,CAAC,OAAO,IAAI,MAAM;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;MAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;MAAA;MAAA;IAAA;EAInC;AAGP","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["surfaces/Avatar.tsx"],"sourcesContent":["import { createEffect, createSignal, type JSX, on, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\n\nexport type AvatarSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport type AvatarProps = {\n name: string;\n src?: string | null;\n icon?: string;\n alt?: string;\n fallback?: string;\n size?: AvatarSize;\n loading?: \"eager\" | \"lazy\";\n class?: string;\n style?: JSX.CSSProperties | string;\n};\n\nconst initialsFor = (name: string): string => {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"?\";\n if (parts.length === 1) return parts[0]?.slice(0, 2).toUpperCase() ?? \"?\";\n return `${parts[0]?.[0] ?? \"\"}${parts.at(-1)?.[0] ?? \"\"}`.toUpperCase();\n};\n\n/** Tints behind initials, as many as `.k2b-avatar[data-tint]` rules in the stylesheet. */\nconst TINTS = 10;\n\n/**\n * The tint behind initials and icons, from the name, so the same name has one color wherever an avatar shows it. The\n * hash is the one of `@k2b/stdlib`'s generated avatar.\n */\nconst tintFor = (name: string): number => {\n let hash = 0;\n for (let index = 0; index < name.length; index++) hash = (hash * 31 + name.charCodeAt(index)) >>> 0;\n return hash % TINTS;\n};\n\nexport function Avatar(props: AvatarProps): JSX.Element {\n const messages = useUiMessages();\n const [failed, setFailed] = createSignal(false);\n const label = () => props.name.trim() || messages().unknownUser;\n // Derive initials from the given name, not from the accessible-name fallback:\n // an empty `name` must render \"?\" the way Cloud's avatar does, not \"UU\".\n const fallback = () => props.fallback ?? initialsFor(props.name);\n const fallbackContent = () => (props.icon ? <i class={props.icon} aria-hidden=\"true\" /> : fallback());\n const className = () => `k2b-avatar ${props.class ?? \"\"}`;\n\n createEffect(\n on(\n () => props.src,\n () => setFailed(false),\n { defer: true },\n ),\n );\n\n return (\n <Show\n when={!failed() ? props.src || undefined : undefined}\n fallback={\n <span\n class={className()}\n data-size={props.size ?? \"md\"}\n data-tint={tintFor(props.name.trim())}\n style={props.style}\n role=\"img\"\n aria-label={props.alt ?? messages().avatarLabel({ name: label() })}\n >\n {fallbackContent()}\n </span>\n }\n >\n {(src) => (\n <img\n src={src()}\n alt={props.alt ?? messages().avatarLabel({ name: label() })}\n class={className()}\n data-size={props.size ?? \"md\"}\n style={props.style}\n loading={props.loading ?? \"lazy\"}\n decoding=\"async\"\n onError={() => setFailed(true)}\n />\n )}\n </Show>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,30 +4,44 @@ import { ssr as _$ssr } from "solid-js/web";
4
4
  import { createComponent as _$createComponent } from "solid-js/web";
5
5
  import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
6
6
  import { escape as _$escape } from "solid-js/web";
7
- var _tmpl$ = ["<div class=\"", "\"", " role=\"grid\"", " aria-multiselectable=\"true\">", "</div>"],
7
+ var _tmpl$ = ["<div class=\"", "\"", " role=\"grid\"", ">", "</div>"],
8
8
  _tmpl$2 = ["<div class=\"k2b-file-grid__preview\">", "</div>"],
9
9
  _tmpl$3 = ["<div class=\"k2b-file-grid__name\">", "</div>"],
10
10
  _tmpl$4 = ["<div class=\"k2b-file-grid__meta\">", "</div>"],
11
11
  _tmpl$5 = ["<div class=\"k2b-file-grid__actions\">", "</div>"],
12
12
  _tmpl$6 = ["<div role=\"row\" class=\"k2b-file-grid__row\">", "</div>"];
13
13
  import { For } from "solid-js";
14
- /** File presentation only; the host owns data, menus, leases and permissions. */
14
+ /**
15
+ * File presentation only; the host owns data, menus, leases and permissions. Items the selection marks with
16
+ * `isDisabled` stay reachable by keyboard but cannot be selected or opened; say why in `renderMeta`.
17
+ */
15
18
  export function FileGrid(props) {
16
19
  let grid;
20
+ // Both clicks of a double-click belong to the item that took the first one. When that click replaced the rows,
21
+ // for example by opening a folder, the item now under the pointer ignores the rest of the gesture.
22
+ let pressed;
23
+ const ownsClick = event => {
24
+ if (event.detail > 1 && pressed !== event.currentTarget) pressed = null;else pressed = event.currentTarget;
25
+ return pressed !== null;
26
+ };
17
27
  const columns = () => grid ? Math.max(1, getComputedStyle(grid).gridTemplateColumns.split(" ").length) : 1;
18
28
  const nested = event => event.target instanceof Element && !!event.target.closest("button,a,input,label,select,textarea,summary,[contenteditable],[role=button],[role=menu]");
19
- return _$ssr(_tmpl$, `k2b-file-grid ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-size", _$escape(props.size ?? "md", true), false), _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(For, {
29
+ return _$ssr(_tmpl$, `k2b-file-grid ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-size", _$escape(props.size ?? "md", true), false) + _$ssrAttribute("data-layout", _$escape(props.layout ?? "grid", true), false), _$ssrAttribute("aria-label", _$escape(props.label, true), false) + _$ssrAttribute("aria-multiselectable", _$escape(props.selection.multiple, true), false), _$escape(_$createComponent(For, {
20
30
  get each() {
21
31
  return props.rows;
22
32
  },
23
33
  children: row => {
24
34
  const id = () => props.getRowId(row);
35
+ const disabled = () => props.selection.isDisabled(id());
25
36
  return _$ssr(_tmpl$6, _$ssrElement("div", _$mergeProps(() => props.itemProps?.(row), {
26
37
  role: "gridcell",
27
38
  "class": "k2b-file-grid__item",
28
39
  get ["aria-selected"]() {
29
40
  return props.selection.selected().has(id());
30
41
  },
42
+ get ["aria-disabled"]() {
43
+ return disabled() || undefined;
44
+ },
31
45
  get tabIndex() {
32
46
  return props.selection.focused() === id() ? 0 : -1;
33
47
  }
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["content/FileGrid.tsx"],"sourcesContent":["import { For, type JSX } from \"solid-js\";\nimport type { CollectionSelection } from \"./collection-selection\";\n\nexport type FileGridProps<T> = {\n rows: readonly T[];\n getRowId: (row: T) => string;\n selection: CollectionSelection;\n label: string;\n size?: \"sm\" | \"md\" | \"lg\";\n renderPreview: (row: T) => JSX.Element;\n renderLabel: (row: T) => JSX.Element;\n renderMeta?: (row: T) => JSX.Element;\n renderActions?: (row: T) => JSX.Element;\n onRowClick?: (row: T) => void;\n onOpen: (row: T) => void;\n onContextMenu?: (row: T) => void;\n /** Extra attributes for one tile, for example drag-and-drop handlers or data attributes the host styles. */\n itemProps?: (row: T) => JSX.HTMLAttributes<HTMLDivElement>;\n class?: string;\n};\n\n/** File presentation only; the host owns data, menus, leases and permissions. */\nexport function FileGrid<T>(props: FileGridProps<T>) {\n let grid: HTMLDivElement | undefined;\n const columns = () => (grid ? Math.max(1, getComputedStyle(grid).gridTemplateColumns.split(\" \").length) : 1);\n const nested = (event: Event) =>\n event.target instanceof Element &&\n !!event.target.closest(\"button,a,input,label,select,textarea,summary,[contenteditable],[role=button],[role=menu]\");\n return (\n <div\n ref={grid}\n class={`k2b-file-grid ${props.class ?? \"\"}`}\n data-size={props.size ?? \"md\"}\n role=\"grid\"\n aria-label={props.label}\n aria-multiselectable=\"true\"\n >\n <For each={props.rows}>\n {(row) => {\n const id = () => props.getRowId(row);\n return (\n <div role=\"row\" class=\"k2b-file-grid__row\">\n <div\n {...props.itemProps?.(row)}\n role=\"gridcell\"\n class=\"k2b-file-grid__item\"\n aria-selected={props.selection.selected().has(id())}\n ref={(element) => props.selection.register(id(), element)}\n tabIndex={props.selection.focused() === id() ? 0 : -1}\n onFocus={() => props.selection.markFocused(id())}\n onClick={(event) => {\n if (!nested(event)) {\n props.selection.select(id(), event);\n props.onRowClick?.(row);\n }\n }}\n onDblClick={(event) => {\n if (!nested(event)) props.onOpen(row);\n }}\n onContextMenu={() => props.onContextMenu?.(row)}\n onKeyDown={(event) => {\n if (nested(event)) return;\n if (props.selection.keyDown(event, id(), columns())) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onOpen(row);\n }\n }}\n >\n <div class=\"k2b-file-grid__preview\">{props.renderPreview(row)}</div>\n <div class=\"k2b-file-grid__name\">{props.renderLabel(row)}</div>\n <div class=\"k2b-file-grid__meta\">{props.renderMeta?.(row)}</div>\n <div class=\"k2b-file-grid__actions\">{props.renderActions?.(row)}</div>\n </div>\n </div>\n );\n }}\n </For>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,GAAG,QAAkB,UAAU;AAqBxC;AACA,OAAO,SAAS,QAAQ,CAAI,KAAuB,EAAE;EACnD,IAAI,IAAgC;EACpC,MAAM,OAAO,GAAG,MAAO,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAE;EAC5G,MAAM,MAAM,GAAI,KAAY,IAC1B,KAAK,CAAC,MAAM,YAAY,OAAO,IAC/B,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,0FAA0F,CAAC;EACpH,qBAGW,0BAAiB,KAAK,CAAC,KAAK,IAAI,EAAE,SAAE,uCAChC,KAAK,CAAC,IAAI,IAAI,IAAI,uDAEjB,KAAK,CAAC,KAAK,4CAGtB,GAAG;IAAA,IAAC,IAAI;MAAA,OAAE,KAAK,CAAC,IAAI;IAAA;IAAA,UACjB,GAAG,IAAK;MACR,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;MACpC,6DAGU,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;QAC1B,IAAI,EAAC,UAAU;QAAA,SACT,qBAAqB;QAAA;UAAA,OACZ,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA;QAAA,IAEnD,QAAQ;UAAA,OAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAA;MAAA,6BAqBhB,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,4BAC3B,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,4BACtB,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,4BACpB,KAAK,CAAC,aAAa,GAAG,GAAG,CAAC;IAIvE;EAAC;AAIT","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["content/FileGrid.tsx"],"sourcesContent":["import { For, type JSX } from \"solid-js\";\nimport type { CollectionSelection } from \"./collection-selection\";\n\nexport type FileGridProps<T> = {\n rows: readonly T[];\n getRowId: (row: T) => string;\n selection: CollectionSelection;\n label: string;\n size?: \"sm\" | \"md\" | \"lg\";\n /** `list` shows one compact row per item: preview, name, and meta beside each other. Defaults to `grid`. */\n layout?: \"grid\" | \"list\";\n renderPreview: (row: T) => JSX.Element;\n renderLabel: (row: T) => JSX.Element;\n renderMeta?: (row: T) => JSX.Element;\n renderActions?: (row: T) => JSX.Element;\n onRowClick?: (row: T) => void;\n onOpen: (row: T) => void;\n onContextMenu?: (row: T) => void;\n /** Extra attributes for one tile, for example drag-and-drop handlers or data attributes the host styles. */\n itemProps?: (row: T) => JSX.HTMLAttributes<HTMLDivElement>;\n class?: string;\n};\n\n/**\n * File presentation only; the host owns data, menus, leases and permissions. Items the selection marks with\n * `isDisabled` stay reachable by keyboard but cannot be selected or opened; say why in `renderMeta`.\n */\nexport function FileGrid<T>(props: FileGridProps<T>) {\n let grid: HTMLDivElement | undefined;\n // Both clicks of a double-click belong to the item that took the first one. When that click replaced the rows,\n // for example by opening a folder, the item now under the pointer ignores the rest of the gesture.\n let pressed: EventTarget | null | undefined;\n const ownsClick = (event: MouseEvent) => {\n if (event.detail > 1 && pressed !== event.currentTarget) pressed = null;\n else pressed = event.currentTarget;\n return pressed !== null;\n };\n const columns = () => (grid ? Math.max(1, getComputedStyle(grid).gridTemplateColumns.split(\" \").length) : 1);\n const nested = (event: Event) =>\n event.target instanceof Element &&\n !!event.target.closest(\"button,a,input,label,select,textarea,summary,[contenteditable],[role=button],[role=menu]\");\n return (\n <div\n ref={grid}\n class={`k2b-file-grid ${props.class ?? \"\"}`}\n data-size={props.size ?? \"md\"}\n data-layout={props.layout ?? \"grid\"}\n role=\"grid\"\n aria-label={props.label}\n aria-multiselectable={props.selection.multiple}\n >\n <For each={props.rows}>\n {(row) => {\n const id = () => props.getRowId(row);\n const disabled = () => props.selection.isDisabled(id());\n return (\n <div role=\"row\" class=\"k2b-file-grid__row\">\n <div\n {...props.itemProps?.(row)}\n role=\"gridcell\"\n class=\"k2b-file-grid__item\"\n aria-selected={props.selection.selected().has(id())}\n aria-disabled={disabled() || undefined}\n ref={(element) => props.selection.register(id(), element)}\n tabIndex={props.selection.focused() === id() ? 0 : -1}\n onFocus={() => props.selection.markFocused(id())}\n onClick={(event) => {\n if (!nested(event) && ownsClick(event) && !disabled()) {\n props.selection.select(id(), event);\n props.onRowClick?.(row);\n }\n }}\n onDblClick={(event) => {\n if (!nested(event) && pressed !== null && !disabled()) props.onOpen(row);\n }}\n onContextMenu={() => props.onContextMenu?.(row)}\n onKeyDown={(event) => {\n if (nested(event)) return;\n if (props.selection.keyDown(event, id(), columns())) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n if (!disabled()) props.onOpen(row);\n }\n }}\n >\n <div class=\"k2b-file-grid__preview\">{props.renderPreview(row)}</div>\n <div class=\"k2b-file-grid__name\">{props.renderLabel(row)}</div>\n <div class=\"k2b-file-grid__meta\">{props.renderMeta?.(row)}</div>\n <div class=\"k2b-file-grid__actions\">{props.renderActions?.(row)}</div>\n </div>\n </div>\n );\n }}\n </For>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,GAAG,QAAkB,UAAU;AAuBxC;AACA;AACA;AACA;AACA,OAAO,SAAS,QAAQ,CAAI,KAAuB,EAAE;EACnD,IAAI,IAAgC;EACpC;EACA;EACA,IAAI,OAAuC;EAC3C,MAAM,SAAS,GAAI,KAAiB,IAAK;IACvC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,CAAC,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC,KACnE,OAAO,GAAG,KAAK,CAAC,aAAa;IAClC,OAAO,OAAO,KAAK,IAAI;EACzB,CAAC;EACD,MAAM,OAAO,GAAG,MAAO,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAE;EAC5G,MAAM,MAAM,GAAI,KAAY,IAC1B,KAAK,CAAC,MAAM,YAAY,OAAO,IAC/B,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,0FAA0F,CAAC;EACpH,qBAGW,0BAAiB,KAAK,CAAC,KAAK,IAAI,EAAE,SAAE,uCAChC,KAAK,CAAC,IAAI,IAAI,IAAI,yDAChB,KAAK,CAAC,MAAM,IAAI,MAAM,uDAEvB,KAAK,CAAC,KAAK,kEACD,KAAK,CAAC,SAAS,CAAC,QAAQ,4CAE7C,GAAG;IAAA,IAAC,IAAI;MAAA,OAAE,KAAK,CAAC,IAAI;IAAA;IAAA,UACjB,GAAG,IAAK;MACR,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;MACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;MACvD,6DAGU,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;QAC1B,IAAI,EAAC,UAAU;QAAA,SACT,qBAAqB;QAAA;UAAA,OACZ,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA;QAAA;UAAA,OACpC,QAAQ,CAAC,CAAC,IAAI,SAAS;QAAA;QAAA,IAEtC,QAAQ;UAAA,OAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAA;MAAA,6BAqBhB,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,4BAC3B,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,4BACtB,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,4BACpB,KAAK,CAAC,aAAa,GAAG,GAAG,CAAC;IAIvE;EAAC;AAIT","ignoreList":[]}
@@ -2,8 +2,10 @@ import { ssr as _$ssr } from "solid-js/web";
2
2
  import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
3
3
  import { escape as _$escape } from "solid-js/web";
4
4
  var _tmpl$ = ["<div class=\"", "\"", ">", "</div>"];
5
- import { marked, Renderer } from "marked";
6
- const escapeHtml = value => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5
+ import { Marked, Renderer } from "marked";
6
+ import { useLocale } from "../intl/locale";
7
+ import { markdownInfoBlocks } from "./markdown-info-blocks";
8
+ export const escapeHtml = value => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7
9
  const safeUrl = value => {
8
10
  const normalized = value.trim().replace(/[\u0000-\u001f\u007f-\u009f\s]+/g, "");
9
11
  if (/^(?:javascript|vbscript|data):/i.test(normalized)) return null;
@@ -18,7 +20,7 @@ const isStandaloneInlineToken = (text, start, value) => {
18
20
  if (after && /[a-zA-Z0-9_@]/.test(after)) return false;
19
21
  return !(after === "." && /[a-zA-Z0-9_]/.test(text[end + 1] ?? ""));
20
22
  };
21
- const createSafeRenderer = (options = {}) => {
23
+ export const createSafeRenderer = (options = {}) => {
22
24
  const renderer = new Renderer();
23
25
  const inlineTokens = normalizeInlineTokens(options.inlineTokens);
24
26
  const baseText = renderer.text.bind(renderer);
@@ -107,17 +109,27 @@ const createSafeRenderer = (options = {}) => {
107
109
  };
108
110
  return renderer;
109
111
  };
110
- export const renderSafeMarkdown = (markdown, options = {}) => marked.parse(markdown, {
112
+ export const renderSafeMarkdown = (markdown, options = {}) => createSafeMarked(options.locale).parse(markdown, {
111
113
  async: false,
112
114
  renderer: createSafeRenderer(options)
113
115
  });
114
116
 
117
+ /**
118
+ * Internal to the package: the Markdown grammar of `renderSafeMarkdown`, for a component that renders with an adjusted
119
+ * safe renderer or reads the tokens, as `MessageRow` does.
120
+ */
121
+ export const createSafeMarked = locale => new Marked(markdownInfoBlocks({
122
+ locale
123
+ }));
124
+
115
125
  /**
116
126
  * Markdown View Component (SSR)
117
127
  *
118
128
  * Renders untrusted Markdown safely by default. Raw HTML and unsafe link
119
- * protocols are escaped. Already-rendered HTML requires the deliberately named
120
- * `trustedHtml` boundary; the caller owns sanitization in that mode.
129
+ * protocols are escaped. Info blocks (`:::note`, `:::info`, `:::success`,
130
+ * `:::warning`, `:::danger`) render as the calm `NoticeCard`. Already-rendered
131
+ * HTML requires the deliberately named `trustedHtml` boundary; the caller owns
132
+ * sanitization in that mode.
121
133
  *
122
134
  * @example
123
135
  * ```tsx
@@ -132,11 +144,13 @@ export const renderSafeMarkdown = (markdown, options = {}) => marked.parse(markd
132
144
  * ```
133
145
  */
134
146
  export default function MarkdownView(props) {
147
+ const locale = useLocale();
135
148
  const html = () => props.markdown !== undefined ? renderSafeMarkdown(props.markdown, {
136
149
  inlineTokens: props.inlineTokens,
137
150
  allowImages: props.allowImages,
138
151
  linkProtocols: props.linkProtocols,
139
- linkTarget: props.linkTarget
152
+ linkTarget: props.linkTarget,
153
+ locale: locale()
140
154
  }) : props.trustedHtml;
141
155
  return _$ssr(_tmpl$, `k2b-content-markdown ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-heading-scale", props.headingScale && props.headingScale !== "normal" ? _$escape(props.headingScale, true) : _$escape(undefined, true), false), html());
142
156
  }
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["content/MarkdownView.tsx"],"sourcesContent":["import { marked, Renderer, type Tokens } from \"marked\";\n\ntype CommonProps = MarkdownRenderOptions & {\n /** Optional additional CSS classes */\n class?: string;\n /**\n * Visual heading scale. Markdown heading levels and document structure stay\n * unchanged.\n */\n headingScale?: \"compact\" | \"normal\" | \"large\";\n};\n\nexport type MarkdownViewProps = CommonProps &\n (\n | {\n /** Untrusted Markdown. Raw HTML and unsafe link protocols are escaped. */\n markdown: string;\n /** Exact standalone text tokens to emphasize without changing Markdown parsing or trusted-HTML boundaries. */\n inlineTokens?: readonly string[];\n trustedHtml?: never;\n }\n | {\n /** Explicitly trusted, already-rendered HTML. The caller owns sanitization. */\n trustedHtml: string;\n markdown?: never;\n inlineTokens?: never;\n }\n );\n\nexport type MarkdownRenderOptions = {\n inlineTokens?: readonly string[];\n /** Whether to render images. False renders alt text without resource requests. Defaults to true. */\n allowImages?: boolean;\n /** Optional protocol allowlist, including trailing colons. Relative URLs resolve as HTTPS. */\n linkProtocols?: readonly string[];\n linkTarget?: \"_blank\";\n};\n\nconst escapeHtml = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\").replaceAll('\"', \"&quot;\").replaceAll(\"'\", \"&#39;\");\n\nconst safeUrl = (value: string): string | null => {\n const normalized = value.trim().replace(/[\\u0000-\\u001f\\u007f-\\u009f\\s]+/g, \"\");\n if (/^(?:javascript|vbscript|data):/i.test(normalized)) return null;\n return value;\n};\n\nconst normalizeInlineTokens = (tokens: readonly string[] | undefined): string[] =>\n [...new Set(tokens?.filter(Boolean) ?? [])].sort((left, right) => right.length - left.length);\n\nconst isStandaloneInlineToken = (text: string, start: number, value: string): boolean => {\n const before = text[start - 1];\n if (before && /[a-zA-Z0-9_.%+\\-]/.test(before)) return false;\n const end = start + value.length;\n const after = text[end];\n if (after && /[a-zA-Z0-9_@]/.test(after)) return false;\n return !(after === \".\" && /[a-zA-Z0-9_]/.test(text[end + 1] ?? \"\"));\n};\n\nconst createSafeRenderer = (options: MarkdownRenderOptions = {}): Renderer => {\n const renderer = new Renderer();\n const inlineTokens = normalizeInlineTokens(options.inlineTokens);\n const baseText = renderer.text.bind(renderer);\n // marked flags text that follows an inline <pre>, <code>, <kbd> or <script>\n // tag as already escaped, because it expects that tag to reach the output as\n // HTML. The tag is escaped here, so the text after it is escaped as well.\n const renderText: Renderer[\"text\"] = (token) => baseText(token.type === \"text\" && token.escaped ? { ...token, escaped: false } : token);\n const renderTable = renderer.table.bind(renderer);\n renderer.table = (token: Tokens.Table) => {\n const table = renderTable(token);\n const content = token.header.every((cell) => !cell.text.trim()) ? table.replace(/<thead>[\\s\\S]*?<\\/thead>\\n?/, \"\") : table;\n return `<div class=\"k2b-content-markdown__table\" tabindex=\"0\">${content}</div>`;\n };\n renderer.html = ({ text }: Tokens.HTML | Tokens.Tag) => escapeHtml(text);\n renderer.link = function ({ href, title, tokens }: Tokens.Link) {\n const body = this.parser.parseInline(tokens);\n const url = safeUrl(href);\n if (!url) return body;\n if (options.linkProtocols) {\n try {\n if (!options.linkProtocols.includes(new URL(url, \"https://markdown.invalid/\").protocol)) return body;\n } catch {\n return body;\n }\n }\n const titleAttribute = title ? ` title=\"${escapeHtml(title)}\"` : \"\";\n const target = options.linkTarget === \"_blank\" ? ' target=\"_blank\" rel=\"noopener noreferrer\"' : \"\";\n return `<a href=\"${escapeHtml(url)}\"${titleAttribute}${target}>${body}</a>`;\n };\n renderer.image = ({ href, title, text }: Tokens.Image) => {\n const url = safeUrl(href);\n if (!url || options.allowImages === false) return escapeHtml(text);\n const titleAttribute = title ? ` title=\"${escapeHtml(title)}\"` : \"\";\n return `<img src=\"${escapeHtml(url)}\" alt=\"${escapeHtml(text)}\"${titleAttribute}>`;\n };\n renderer.text = (token) => {\n if (inlineTokens.length === 0 || token.type === \"escape\" || (\"tokens\" in token && token.tokens)) return renderText(token);\n const parts: string[] = [];\n let plainStart = 0;\n let cursor = 0;\n while (cursor < token.text.length) {\n const inlineToken = inlineTokens.find(\n (candidate) => token.text.startsWith(candidate, cursor) && isStandaloneInlineToken(token.text, cursor, candidate),\n );\n if (!inlineToken) {\n cursor += 1;\n continue;\n }\n if (plainStart < cursor) {\n const text = token.text.slice(plainStart, cursor);\n parts.push(renderText({ ...token, raw: text, text }));\n }\n parts.push(\n `<span class=\"k2b-content-markdown__inline-token\">${renderText({ ...token, raw: inlineToken, text: inlineToken })}</span>`,\n );\n cursor += inlineToken.length;\n plainStart = cursor;\n }\n if (plainStart === 0) return renderText(token);\n if (plainStart < token.text.length) {\n const text = token.text.slice(plainStart);\n parts.push(renderText({ ...token, raw: text, text }));\n }\n return parts.join(\"\");\n };\n return renderer;\n};\n\nexport const renderSafeMarkdown = (markdown: string, options: MarkdownRenderOptions = {}): string =>\n marked.parse(markdown, {\n async: false,\n renderer: createSafeRenderer(options),\n }) as string;\n\n/**\n * Markdown View Component (SSR)\n *\n * Renders untrusted Markdown safely by default. Raw HTML and unsafe link\n * protocols are escaped. Already-rendered HTML requires the deliberately named\n * `trustedHtml` boundary; the caller owns sanitization in that mode.\n *\n * @example\n * ```tsx\n * import { MarkdownView } from \"@k2b/ui\";\n *\n * <MarkdownView markdown={source} />;\n * ```\n *\n * @example Compact headings, e.g. inside comments or file previews:\n * ```tsx\n * <MarkdownView markdown={source} headingScale=\"compact\" />;\n * ```\n */\nexport default function MarkdownView(props: MarkdownViewProps) {\n const html = () =>\n props.markdown !== undefined\n ? renderSafeMarkdown(props.markdown, {\n inlineTokens: props.inlineTokens,\n allowImages: props.allowImages,\n linkProtocols: props.linkProtocols,\n linkTarget: props.linkTarget,\n })\n : props.trustedHtml;\n return (\n <div\n class={`k2b-content-markdown ${props.class ?? \"\"}`}\n data-heading-scale={props.headingScale && props.headingScale !== \"normal\" ? props.headingScale : undefined}\n innerHTML={html()}\n />\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["content/MarkdownView.tsx"],"sourcesContent":["import { Marked, Renderer, type Tokens } from \"marked\";\nimport { useLocale } from \"../intl/locale\";\nimport { markdownInfoBlocks } from \"./markdown-info-blocks\";\n\ntype CommonProps = MarkdownRenderOptions & {\n /** Optional additional CSS classes */\n class?: string;\n /**\n * Visual heading scale. Markdown heading levels and document structure stay\n * unchanged.\n */\n headingScale?: \"compact\" | \"normal\" | \"large\";\n};\n\nexport type MarkdownViewProps = CommonProps &\n (\n | {\n /** Untrusted Markdown. Raw HTML and unsafe link protocols are escaped. */\n markdown: string;\n /** Exact standalone text tokens to emphasize without changing Markdown parsing or trusted-HTML boundaries. */\n inlineTokens?: readonly string[];\n trustedHtml?: never;\n }\n | {\n /** Explicitly trusted, already-rendered HTML. The caller owns sanitization. */\n trustedHtml: string;\n markdown?: never;\n inlineTokens?: never;\n }\n );\n\nexport type MarkdownRenderOptions = {\n inlineTokens?: readonly string[];\n /** Whether to render images. False renders alt text without resource requests. Defaults to true. */\n allowImages?: boolean;\n /** Optional protocol allowlist, including trailing colons. Relative URLs resolve as HTTPS. */\n linkProtocols?: readonly string[];\n linkTarget?: \"_blank\";\n /** Locale for the screen-reader names of untitled info blocks. `MarkdownView` passes the inherited render locale. */\n locale?: string;\n};\n\nexport const escapeHtml = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\").replaceAll('\"', \"&quot;\").replaceAll(\"'\", \"&#39;\");\n\nconst safeUrl = (value: string): string | null => {\n const normalized = value.trim().replace(/[\\u0000-\\u001f\\u007f-\\u009f\\s]+/g, \"\");\n if (/^(?:javascript|vbscript|data):/i.test(normalized)) return null;\n return value;\n};\n\nconst normalizeInlineTokens = (tokens: readonly string[] | undefined): string[] =>\n [...new Set(tokens?.filter(Boolean) ?? [])].sort((left, right) => right.length - left.length);\n\nconst isStandaloneInlineToken = (text: string, start: number, value: string): boolean => {\n const before = text[start - 1];\n if (before && /[a-zA-Z0-9_.%+\\-]/.test(before)) return false;\n const end = start + value.length;\n const after = text[end];\n if (after && /[a-zA-Z0-9_@]/.test(after)) return false;\n return !(after === \".\" && /[a-zA-Z0-9_]/.test(text[end + 1] ?? \"\"));\n};\n\nexport const createSafeRenderer = (options: MarkdownRenderOptions = {}): Renderer => {\n const renderer = new Renderer();\n const inlineTokens = normalizeInlineTokens(options.inlineTokens);\n const baseText = renderer.text.bind(renderer);\n // marked flags text that follows an inline <pre>, <code>, <kbd> or <script>\n // tag as already escaped, because it expects that tag to reach the output as\n // HTML. The tag is escaped here, so the text after it is escaped as well.\n const renderText: Renderer[\"text\"] = (token) => baseText(token.type === \"text\" && token.escaped ? { ...token, escaped: false } : token);\n const renderTable = renderer.table.bind(renderer);\n renderer.table = (token: Tokens.Table) => {\n const table = renderTable(token);\n const content = token.header.every((cell) => !cell.text.trim()) ? table.replace(/<thead>[\\s\\S]*?<\\/thead>\\n?/, \"\") : table;\n return `<div class=\"k2b-content-markdown__table\" tabindex=\"0\">${content}</div>`;\n };\n renderer.html = ({ text }: Tokens.HTML | Tokens.Tag) => escapeHtml(text);\n renderer.link = function ({ href, title, tokens }: Tokens.Link) {\n const body = this.parser.parseInline(tokens);\n const url = safeUrl(href);\n if (!url) return body;\n if (options.linkProtocols) {\n try {\n if (!options.linkProtocols.includes(new URL(url, \"https://markdown.invalid/\").protocol)) return body;\n } catch {\n return body;\n }\n }\n const titleAttribute = title ? ` title=\"${escapeHtml(title)}\"` : \"\";\n const target = options.linkTarget === \"_blank\" ? ' target=\"_blank\" rel=\"noopener noreferrer\"' : \"\";\n return `<a href=\"${escapeHtml(url)}\"${titleAttribute}${target}>${body}</a>`;\n };\n renderer.image = ({ href, title, text }: Tokens.Image) => {\n const url = safeUrl(href);\n if (!url || options.allowImages === false) return escapeHtml(text);\n const titleAttribute = title ? ` title=\"${escapeHtml(title)}\"` : \"\";\n return `<img src=\"${escapeHtml(url)}\" alt=\"${escapeHtml(text)}\"${titleAttribute}>`;\n };\n renderer.text = (token) => {\n if (inlineTokens.length === 0 || token.type === \"escape\" || (\"tokens\" in token && token.tokens)) return renderText(token);\n const parts: string[] = [];\n let plainStart = 0;\n let cursor = 0;\n while (cursor < token.text.length) {\n const inlineToken = inlineTokens.find(\n (candidate) => token.text.startsWith(candidate, cursor) && isStandaloneInlineToken(token.text, cursor, candidate),\n );\n if (!inlineToken) {\n cursor += 1;\n continue;\n }\n if (plainStart < cursor) {\n const text = token.text.slice(plainStart, cursor);\n parts.push(renderText({ ...token, raw: text, text }));\n }\n parts.push(\n `<span class=\"k2b-content-markdown__inline-token\">${renderText({ ...token, raw: inlineToken, text: inlineToken })}</span>`,\n );\n cursor += inlineToken.length;\n plainStart = cursor;\n }\n if (plainStart === 0) return renderText(token);\n if (plainStart < token.text.length) {\n const text = token.text.slice(plainStart);\n parts.push(renderText({ ...token, raw: text, text }));\n }\n return parts.join(\"\");\n };\n return renderer;\n};\n\nexport const renderSafeMarkdown = (markdown: string, options: MarkdownRenderOptions = {}): string =>\n createSafeMarked(options.locale).parse(markdown, { async: false, renderer: createSafeRenderer(options) }) as string;\n\n/**\n * Internal to the package: the Markdown grammar of `renderSafeMarkdown`, for a component that renders with an adjusted\n * safe renderer or reads the tokens, as `MessageRow` does.\n */\nexport const createSafeMarked = (locale?: string): Marked => new Marked(markdownInfoBlocks({ locale }));\n\n/**\n * Markdown View Component (SSR)\n *\n * Renders untrusted Markdown safely by default. Raw HTML and unsafe link\n * protocols are escaped. Info blocks (`:::note`, `:::info`, `:::success`,\n * `:::warning`, `:::danger`) render as the calm `NoticeCard`. Already-rendered\n * HTML requires the deliberately named `trustedHtml` boundary; the caller owns\n * sanitization in that mode.\n *\n * @example\n * ```tsx\n * import { MarkdownView } from \"@k2b/ui\";\n *\n * <MarkdownView markdown={source} />;\n * ```\n *\n * @example Compact headings, e.g. inside comments or file previews:\n * ```tsx\n * <MarkdownView markdown={source} headingScale=\"compact\" />;\n * ```\n */\nexport default function MarkdownView(props: MarkdownViewProps) {\n const locale = useLocale();\n const html = () =>\n props.markdown !== undefined\n ? renderSafeMarkdown(props.markdown, {\n inlineTokens: props.inlineTokens,\n allowImages: props.allowImages,\n linkProtocols: props.linkProtocols,\n linkTarget: props.linkTarget,\n locale: locale(),\n })\n : props.trustedHtml;\n return (\n <div\n class={`k2b-content-markdown ${props.class ?? \"\"}`}\n data-heading-scale={props.headingScale && props.headingScale !== \"normal\" ? props.headingScale : undefined}\n innerHTML={html()}\n />\n );\n}\n"],"mappings":"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","ignoreList":[]}