@astronox/core 0.2.4 → 0.3.1

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 (114) hide show
  1. package/README.md +16 -1
  2. package/package.json +18 -4
  3. package/src/admin/authController.ts +166 -0
  4. package/src/admin/cache.ts +44 -17
  5. package/src/admin/index.ts +3 -0
  6. package/src/admin/preparedDestination.ts +114 -0
  7. package/src/admin/resourceClient.ts +107 -0
  8. package/src/ads/adsense/index.ts +1 -0
  9. package/src/ads/adsense.ts +112 -0
  10. package/src/ads/contracts.ts +11 -0
  11. package/src/ads/index.ts +2 -0
  12. package/src/ads/slot.ts +16 -0
  13. package/src/auth/contracts.ts +1 -4
  14. package/src/auth/firebase/client.ts +19 -6
  15. package/src/auth/firebase/index.ts +1 -0
  16. package/src/client/formSubmission.ts +6 -1
  17. package/src/client/preferences.ts +8 -4
  18. package/src/components/actions/Button/Button.astro +30 -42
  19. package/src/components/actions/CardAction/CardAction.astro +7 -0
  20. package/src/components/actions/CardAction/CardAction.css +31 -10
  21. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +6 -8
  22. package/src/components/actions/MediaTrigger/MediaTrigger.astro +1 -1
  23. package/src/components/actions/ScrollToTop/ScrollToTop.astro +2 -1
  24. package/src/components/actions/ScrollToTop/ScrollToTop.css +2 -6
  25. package/src/components/actions/ThemeToggle/ThemeToggle.astro +3 -3
  26. package/src/components/actions/ThemeToggle/themeToggleController.ts +7 -4
  27. package/src/components/content/CardFooter/CardFooter.css +2 -4
  28. package/src/components/content/CardHeader/CardHeader.astro +1 -4
  29. package/src/components/content/CardLink/CardLink.astro +3 -15
  30. package/src/components/content/CardLink/CardLink.css +4 -15
  31. package/src/components/content/CodeBlock/CodeBlock.astro +7 -6
  32. package/src/components/content/CodeBlock/CodeBlock.css +2 -5
  33. package/src/components/content/CodeBlock/codeBlockController.ts +1 -2
  34. package/src/components/content/MarkdownContent/MarkdownContent.astro +1 -3
  35. package/src/components/content/MarkdownContent/MarkdownContent.css +7 -2
  36. package/src/components/content/MarkdownContent/MarkdownInline.astro +11 -3
  37. package/src/components/content/MarkdownContent/MarkdownNode.astro +28 -33
  38. package/src/components/content/MarkdownContent/MarkdownSymbol.astro +30 -19
  39. package/src/components/content/MarkdownContent/contracts.ts +15 -15
  40. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +27 -83
  41. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +19 -22
  42. package/src/components/feedback/Spinner/Spinner.astro +1 -2
  43. package/src/components/feedback/Tooltip/tooltipController.ts +1 -4
  44. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -4
  45. package/src/components/forms/DateTimeInput/DateTimeInput.astro +2 -1
  46. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +10 -8
  47. package/src/components/forms/PreparedFormBootstrap/parsePreparedFormState.ts +48 -0
  48. package/src/components/forms/Textarea/Textarea.astro +2 -2
  49. package/src/components/icons/BurnIcon.astro +2 -1
  50. package/src/components/icons/FieldIcon.astro +10 -4
  51. package/src/components/icons/GrassIcon.astro +3 -2
  52. package/src/components/icons/SlowIcon.astro +1 -2
  53. package/src/components/icons/StunIcon.astro +2 -1
  54. package/src/components/icons/WaterIcon.astro +3 -2
  55. package/src/components/index.ts +2 -0
  56. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -2
  57. package/src/components/media/CanvasFrame/canvasFrameController.ts +1 -4
  58. package/src/components/media/EmbedFrame/EmbedFrame.astro +5 -12
  59. package/src/components/media/EmbedFrame/embedFrameController.ts +4 -4
  60. package/src/components/monetization/AdSlot/AdSlot.astro +33 -0
  61. package/src/components/monetization/AdSlot/AdSlot.css +53 -0
  62. package/src/components/navigation/DesktopNavigation.css +1 -2
  63. package/src/components/navigation/Footer/Footer.css +3 -9
  64. package/src/components/navigation/MobileNavigation.astro +10 -2
  65. package/src/components/navigation/MobileNavigation.css +8 -22
  66. package/src/components/navigation/navigationTypes.ts +1 -4
  67. package/src/components/seo/SeoHead.astro +21 -6
  68. package/src/composition/SurfaceGroup/SurfaceGroup.css +4 -14
  69. package/src/config/environment.ts +52 -0
  70. package/src/config/index.ts +3 -0
  71. package/src/config/integrations.ts +9 -0
  72. package/src/config/siteConfig.ts +7 -0
  73. package/src/config/storage.ts +16 -0
  74. package/src/content/admin.ts +354 -0
  75. package/src/content/cache.ts +177 -0
  76. package/src/content/catalog.ts +72 -0
  77. package/src/content/contracts.ts +77 -0
  78. package/src/content/drafts.ts +49 -0
  79. package/src/content/index.ts +7 -0
  80. package/src/content/markdown.ts +146 -0
  81. package/src/content/snapshot.ts +139 -0
  82. package/src/data/firestore/client/contracts.ts +10 -2
  83. package/src/data/index.ts +1 -0
  84. package/src/data/memoizedLoader.ts +13 -0
  85. package/src/display/contracts.ts +1 -5
  86. package/src/i18n/config.ts +2 -8
  87. package/src/i18n/coreMessages.ts +2 -9
  88. package/src/i18n/preference.ts +2 -6
  89. package/src/patterns/Combobox/Combobox.astro +9 -12
  90. package/src/patterns/Combobox/comboboxController.ts +14 -8
  91. package/src/patterns/Fullscreen/FullscreenControl.astro +5 -1
  92. package/src/patterns/Fullscreen/fullscreenController.ts +1 -3
  93. package/src/patterns/MapViewport/geometry.ts +1 -4
  94. package/src/patterns/Modal/Modal.css +1 -4
  95. package/src/patterns/Modal/ModalOpen.astro +5 -1
  96. package/src/patterns/Table/Table.astro +10 -10
  97. package/src/patterns/Table/TableBody.astro +1 -4
  98. package/src/patterns/Table/TableBody.css +2 -4
  99. package/src/patterns/Table/TablePagination.astro +23 -30
  100. package/src/patterns/Table/TableSkeleton.astro +1 -5
  101. package/src/patterns/Table/tableClientController.ts +2 -2
  102. package/src/patterns/Table/tableFilterController.ts +1 -3
  103. package/src/patterns/Table/tableHeaderOverflowController.ts +1 -4
  104. package/src/patterns/Table/tableProgressiveController.ts +62 -60
  105. package/src/patterns/Table/tableScrollController.ts +3 -2
  106. package/src/primitives/text/TextBlock/TextBlock.astro +1 -3
  107. package/src/publication/contracts.ts +11 -0
  108. package/src/publication/http.ts +31 -0
  109. package/src/publication/index.ts +2 -0
  110. package/src/routing/localized.ts +9 -12
  111. package/src/seo/localizedMeta.ts +2 -6
  112. package/src/styles/interaction.css +14 -7
  113. package/src/styles/style.css +11 -20
  114. package/src/theme/contracts.ts +1 -6
@@ -154,26 +154,17 @@
154
154
  );
155
155
  }
156
156
 
157
- .card-link:is(
158
- [data-media-size='compact'],
159
- [data-media-size='small']
160
- )
157
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
161
158
  .card-link__layout {
162
159
  --card-link-artwork-max-size: var(--card-link-resolved-media-size);
163
160
  }
164
161
 
165
- .card-link:is(
166
- [data-media-size='compact'],
167
- [data-media-size='small']
168
- )
162
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
169
163
  .card-link__content {
170
164
  --card-link-content-media-size: var(--card-link-resolved-media-size);
171
165
  }
172
166
 
173
- .card-link:is(
174
- [data-media-size='compact'],
175
- [data-media-size='small']
176
- )
167
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
177
168
  :is(.card-link__media, .card-link__content-media)
178
169
  [data-widget='media'] {
179
170
  --media-object-inline-size: 100%;
@@ -203,9 +194,7 @@
203
194
  }
204
195
 
205
196
  .card-link[data-density='relaxed'] .card-link__layout {
206
- --card-link-size: calc(
207
- var(--nox-navigation-block-size) + var(--nox-space-5)
208
- );
197
+ --card-link-size: calc(var(--nox-navigation-block-size) + var(--nox-space-5));
209
198
  }
210
199
 
211
200
  .card-link[data-density='relaxed'] .card-link__content {
@@ -3,10 +3,7 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import Button from '../../actions/Button/Button.astro';
4
4
  import CopyIcon from '../../icons/CopyIcon.astro';
5
5
  import type { Tone } from '../../../theme/contracts';
6
- import type {
7
- CodeBlockCopy,
8
- CodeBlockPresentation,
9
- } from './contracts';
6
+ import type { CodeBlockCopy, CodeBlockPresentation } from './contracts';
10
7
 
11
8
  type Props = HTMLAttributes<'pre'> & {
12
9
  copy?: CodeBlockCopy;
@@ -42,7 +39,9 @@ const resolvedTone = tone ?? dataTone;
42
39
  data-code-block-content
43
40
  data-code-block-presentation={presentation}
44
41
  data-wrap={wrap ? true : undefined}
45
- >{text}</pre>
42
+ >
43
+ {text}
44
+ </pre>
46
45
  <Button
47
46
  class="code-block__copy"
48
47
  type="button"
@@ -75,7 +74,9 @@ const resolvedTone = tone ?? dataTone;
75
74
  data-code-block-presentation={presentation}
76
75
  data-tone={resolvedTone}
77
76
  data-wrap={wrap ? true : undefined}
78
- >{text}</pre>
77
+ >
78
+ {text}
79
+ </pre>
79
80
  )
80
81
  }
81
82
 
@@ -18,8 +18,7 @@
18
18
  --code-block-light-line-height: 1.125rem;
19
19
  --code-block-copy-size: calc(
20
20
  var(--code-block-light-line-height) + var(--nox-space-3) +
21
- var(--nox-space-3) + var(--nox-border-width) +
22
- var(--nox-border-width)
21
+ var(--nox-space-3) + var(--nox-border-width) + var(--nox-border-width)
23
22
  );
24
23
 
25
24
  position: relative;
@@ -30,9 +29,7 @@
30
29
  .code-block-shell .code-block {
31
30
  box-sizing: border-box;
32
31
  inline-size: 100%;
33
- padding-inline-end: calc(
34
- var(--code-block-copy-size) + var(--nox-space-3)
35
- );
32
+ padding-inline-end: calc(var(--code-block-copy-size) + var(--nox-space-3));
36
33
  }
37
34
 
38
35
  .code-block[data-code-block-presentation='light'] {
@@ -17,8 +17,7 @@ export function initCodeBlock(root: HTMLElement): () => void {
17
17
  root.setAttribute(initializedAttribute, 'true');
18
18
  const copyLabel = copyButton.dataset.copyLabel ?? '';
19
19
  const copiedLabel = copyButton.dataset.copiedLabel ?? copyLabel;
20
- const unavailableLabel =
21
- copyButton.dataset.copyUnavailableLabel ?? copyLabel;
20
+ const unavailableLabel = copyButton.dataset.copyUnavailableLabel ?? copyLabel;
22
21
  let feedbackTimeout: number | undefined;
23
22
 
24
23
  const setButtonLabel = (label: string): void => {
@@ -1,8 +1,6 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types';
3
- import type {
4
- CodeBlockCopy,
5
- } from '../CodeBlock/contracts';
3
+ import type { CodeBlockCopy } from '../CodeBlock/contracts';
6
4
  import MarkdownNode from './MarkdownNode.astro';
7
5
  import type { MarkdownDocument } from './contracts';
8
6
 
@@ -163,8 +163,7 @@
163
163
  gap: 0;
164
164
  }
165
165
 
166
- .markdown-content__list-item[data-markdown-resource]
167
- .markdown-content__list {
166
+ .markdown-content__list-item[data-markdown-resource] .markdown-content__list {
168
167
  padding-inline-start: 0;
169
168
  list-style: none;
170
169
  }
@@ -268,6 +267,12 @@
268
267
  block-size: 1em;
269
268
  }
270
269
 
270
+ .markdown-content__symbol-media {
271
+ block-size: 1.5em;
272
+ inline-size: 1.5em;
273
+ object-fit: contain;
274
+ }
275
+
271
276
  .markdown-content__symbol[data-markdown-symbol^='terrain-']
272
277
  .markdown-content__symbol-icon {
273
278
  inline-size: 1.25em;
@@ -20,10 +20,16 @@ const { node }: Props = Astro.props;
20
20
  <MarkdownSymbol node={node} />
21
21
  ) : node.type === 'strong' ? (
22
22
  <strong class="markdown-content__strong">
23
- {node.children.map((child) => <Astro.self node={child} />)}
23
+ {node.children.map((child) => (
24
+ <Astro.self node={child} />
25
+ ))}
24
26
  </strong>
25
27
  ) : node.type === 'emphasis' ? (
26
- <em>{node.children.map((child) => <Astro.self node={child} />)}</em>
28
+ <em>
29
+ {node.children.map((child) => (
30
+ <Astro.self node={child} />
31
+ ))}
32
+ </em>
27
33
  ) : node.type === 'code' ? (
28
34
  <code class="markdown-content__inline-code">{node.value}</code>
29
35
  ) : node.type === 'link' ? (
@@ -36,7 +42,9 @@ const { node }: Props = Astro.props;
36
42
  rel={node.external ? 'noopener noreferrer' : undefined}
37
43
  target={node.external ? '_blank' : undefined}
38
44
  >
39
- {node.children.map((child) => <Astro.self node={child} />)}
45
+ {node.children.map((child) => (
46
+ <Astro.self node={child} />
47
+ ))}
40
48
  </a>
41
49
  ) : (
42
50
  <br />
@@ -1,8 +1,6 @@
1
1
  ---
2
2
  import CodeBlock from '../CodeBlock/CodeBlock.astro';
3
- import type {
4
- CodeBlockCopy,
5
- } from '../CodeBlock/contracts';
3
+ import type { CodeBlockCopy } from '../CodeBlock/contracts';
6
4
  import Media from '../../../primitives/media/Media/Media.astro';
7
5
  import Separator from '../../../primitives/layout/Separator/Separator.astro';
8
6
  import type { Tone } from '../../../theme/contracts';
@@ -26,14 +24,14 @@ function toHeadingTag(
26
24
  return `h${level}` as 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
27
25
  }
28
26
 
29
- const defaultListMarker = {
27
+ const defaultListMarker: MarkdownListMarker = {
30
28
  symbol: 'bullet',
31
29
  tone: 'neutral',
32
- } as const satisfies MarkdownListMarker;
33
- const nestedListMarker = {
30
+ };
31
+ const nestedListMarker: MarkdownListMarker = {
34
32
  symbol: 'arrow-right',
35
33
  tone: 'neutral',
36
- } as const satisfies MarkdownListMarker;
34
+ };
37
35
 
38
36
  const codeBlockTones = new Set<Tone>([
39
37
  'neutral',
@@ -77,10 +75,7 @@ const listItems =
77
75
 
78
76
  {
79
77
  node.type === 'section' ? (
80
- <section
81
- class="markdown-content__section"
82
- aria-labelledby={node.id}
83
- >
78
+ <section class="markdown-content__section" aria-labelledby={node.id}>
84
79
  <HeadingTag
85
80
  class="markdown-content__section-title"
86
81
  data-markdown-heading-level={node.level}
@@ -89,16 +84,16 @@ const listItems =
89
84
  {node.title}
90
85
  </HeadingTag>
91
86
  <div class="markdown-content__section-body">
92
- {
93
- node.blocks.map((block) => (
94
- <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
95
- ))
96
- }
87
+ {node.blocks.map((block) => (
88
+ <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
89
+ ))}
97
90
  </div>
98
91
  </section>
99
92
  ) : node.type === 'paragraph' ? (
100
93
  <p class="markdown-content__paragraph">
101
- {node.children.map((child) => <MarkdownInline node={child} />)}
94
+ {node.children.map((child) => (
95
+ <MarkdownInline node={child} />
96
+ ))}
102
97
  </p>
103
98
  ) : node.type === 'heading' ? (
104
99
  <HeadingTag
@@ -106,17 +101,23 @@ const listItems =
106
101
  data-markdown-heading-level={node.level}
107
102
  id={node.id}
108
103
  >
109
- {node.children.map((child) => <MarkdownInline node={child} />)}
104
+ {node.children.map((child) => (
105
+ <MarkdownInline node={child} />
106
+ ))}
110
107
  </HeadingTag>
111
108
  ) : node.type === 'change' ? (
112
109
  <div class="markdown-content__change">
113
110
  <span class="markdown-content__change-label">
114
- {node.label.map((child) => <MarkdownInline node={child} />)}
111
+ {node.label.map((child) => (
112
+ <MarkdownInline node={child} />
113
+ ))}
115
114
  <span>:</span>
116
115
  </span>
117
116
  {node.presentation === 'statement' ? (
118
117
  <span class="markdown-content__change-values">
119
- {node.value.map((child) => <MarkdownInline node={child} />)}
118
+ {node.value.map((child) => (
119
+ <MarkdownInline node={child} />
120
+ ))}
120
121
  </span>
121
122
  ) : (
122
123
  <span class="markdown-content__change-values" lang={node.valueLang}>
@@ -187,9 +188,7 @@ const listItems =
187
188
  media={item.resource.media}
188
189
  size="fill"
189
190
  upscale={
190
- item.resource.mediaScale === 'compact'
191
- ? 'prevent'
192
- : undefined
191
+ item.resource.mediaScale === 'compact' ? 'prevent' : undefined
193
192
  }
194
193
  />
195
194
  ) : null}
@@ -242,11 +241,9 @@ const listItems =
242
241
  )}
243
242
  </div>
244
243
  ) : null}
245
- {
246
- item.children.map((block) => (
247
- <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
248
- ))
249
- }
244
+ {item.children.map((block) => (
245
+ <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
246
+ ))}
250
247
  </div>
251
248
  </li>
252
249
  ))}
@@ -256,11 +253,9 @@ const listItems =
256
253
  class="markdown-content__quote"
257
254
  data-tone={node.tone ?? 'neutral'}
258
255
  >
259
- {
260
- node.children.map((block) => (
261
- <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
262
- ))
263
- }
256
+ {node.children.map((block) => (
257
+ <Astro.self node={block} codeBlockCopy={codeBlockCopy} />
258
+ ))}
264
259
  </blockquote>
265
260
  ) : node.type === 'separator' ? (
266
261
  <Separator bleed emphasis={node.emphasis} />
@@ -5,29 +5,24 @@ import ArrowRightIcon from '../../icons/ArrowRightIcon.astro';
5
5
  import ArrowUpIcon from '../../icons/ArrowUpIcon.astro';
6
6
  import BulletIcon from '../../icons/BulletIcon.astro';
7
7
  import EqualIcon from '../../icons/EqualIcon.astro';
8
- import FieldIcon from '../../icons/FieldIcon.astro';
9
- import GrassIcon from '../../icons/GrassIcon.astro';
10
- import MountainIcon from '../../icons/MountainIcon.astro';
11
- import WaterIcon from '../../icons/WaterIcon.astro';
12
- import type { MarkdownSymbol } from './contracts';
8
+ import type { MarkdownBuiltinSymbolName, MarkdownSymbol } from './contracts';
13
9
 
14
10
  type Props = {
15
11
  node: MarkdownSymbol;
16
12
  };
17
13
 
18
14
  const { node }: Props = Astro.props;
19
- const Icon = {
15
+ const icons = {
20
16
  adjustment: ArrowUpDownIcon,
21
17
  'arrow-right': ArrowRightIcon,
22
18
  bullet: BulletIcon,
23
19
  decrease: ArrowDownIcon,
24
20
  increase: ArrowUpIcon,
25
21
  neutral: EqualIcon,
26
- 'terrain-field': FieldIcon,
27
- 'terrain-grass': GrassIcon,
28
- 'terrain-mountain': MountainIcon,
29
- 'terrain-water': WaterIcon,
30
- }[node.name];
22
+ } satisfies Record<MarkdownBuiltinSymbolName, typeof ArrowUpDownIcon>;
23
+ const Icon = node.media ? null : icons[node.name as MarkdownBuiltinSymbolName];
24
+ if (!node.media && !Icon)
25
+ throw new Error(`Unknown Markdown symbol: ${node.name}`);
31
26
  ---
32
27
 
33
28
  <span
@@ -36,12 +31,28 @@ const Icon = {
36
31
  data-tone={node.tone}
37
32
  lang={node.lang}
38
33
  >
39
- <Icon class="markdown-content__symbol-icon" />
40
- {node.label ? (
41
- <span class="markdown-content__symbol-label">{node.label}</span>
42
- ) : node.accessibleLabel ? (
43
- <span class="markdown-content__visually-hidden">
44
- {node.accessibleLabel}
45
- </span>
46
- ) : null}
34
+ {
35
+ node.media ? (
36
+ <img
37
+ class="markdown-content__symbol-media"
38
+ src={node.media.src}
39
+ alt={node.media.alt}
40
+ width={node.media.width}
41
+ height={node.media.height}
42
+ loading={node.media.loading ?? 'lazy'}
43
+ decoding={node.media.decoding ?? 'async'}
44
+ />
45
+ ) : Icon ? (
46
+ <Icon class="markdown-content__symbol-icon" />
47
+ ) : null
48
+ }
49
+ {
50
+ node.label ? (
51
+ <span class="markdown-content__symbol-label">{node.label}</span>
52
+ ) : node.accessibleLabel ? (
53
+ <span class="markdown-content__visually-hidden">
54
+ {node.accessibleLabel}
55
+ </span>
56
+ ) : null
57
+ }
47
58
  </span>
@@ -7,27 +7,27 @@ export type MarkdownText = {
7
7
  value: string;
8
8
  };
9
9
 
10
- export type MarkdownSymbolName =
11
- | 'adjustment'
12
- | 'arrow-right'
13
- | 'bullet'
14
- | 'decrease'
15
- | 'increase'
16
- | 'neutral'
17
- | 'terrain-field'
18
- | 'terrain-grass'
19
- | 'terrain-mountain'
20
- | 'terrain-water';
21
-
22
- export type MarkdownSymbol = {
10
+ export type MarkdownBuiltinSymbolName =
11
+ 'adjustment' | 'arrow-right' | 'bullet' | 'decrease' | 'increase' | 'neutral';
12
+
13
+ type MarkdownSymbolBase = {
23
14
  type: 'symbol';
24
15
  accessibleLabel?: string;
25
16
  lang?: string;
26
17
  label?: string;
27
- name: MarkdownSymbolName;
28
18
  tone?: Tone;
29
19
  };
30
20
 
21
+ export type MarkdownSymbol =
22
+ | (MarkdownSymbolBase & {
23
+ media?: never;
24
+ name: MarkdownBuiltinSymbolName;
25
+ })
26
+ | (MarkdownSymbolBase & {
27
+ media: DisplayMedia;
28
+ name: string;
29
+ });
30
+
31
31
  export type MarkdownStrong = {
32
32
  type: 'strong';
33
33
  children: readonly MarkdownInline[];
@@ -91,7 +91,7 @@ export type MarkdownListMarker = {
91
91
  lang?: string;
92
92
  label?: string;
93
93
  symbol: Extract<
94
- MarkdownSymbolName,
94
+ MarkdownBuiltinSymbolName,
95
95
  | 'adjustment'
96
96
  | 'arrow-right'
97
97
  | 'bullet'
@@ -24,14 +24,14 @@ const codeBlockTones = new Set<Tone>([
24
24
  'warning',
25
25
  'danger',
26
26
  ]);
27
- const defaultListMarker = {
27
+ const defaultListMarker: MarkdownListMarker = {
28
28
  symbol: 'bullet',
29
29
  tone: 'neutral',
30
- } as const satisfies MarkdownListMarker;
31
- const nestedListMarker = {
30
+ };
31
+ const nestedListMarker: MarkdownListMarker = {
32
32
  symbol: 'arrow-right',
33
33
  tone: 'neutral',
34
- } as const satisfies MarkdownListMarker;
34
+ };
35
35
 
36
36
  export function renderMarkdownDocument(
37
37
  root: HTMLElement,
@@ -103,10 +103,7 @@ function renderNode(
103
103
  document.createTextNode(node.after),
104
104
  );
105
105
  if (node.delta) {
106
- const delta = createElement(
107
- 'span',
108
- 'markdown-content__change-delta',
109
- );
106
+ const delta = createElement('span', 'markdown-content__change-delta');
110
107
  delta.dataset.tone = node.delta.tone;
111
108
  delta.append('(');
112
109
  if (node.delta.sign !== 'neutral') {
@@ -125,10 +122,7 @@ function renderNode(
125
122
  }
126
123
  values.append(transition);
127
124
  if (node.suffix?.length) {
128
- const suffix = createElement(
129
- 'span',
130
- 'markdown-content__change-suffix',
131
- );
125
+ const suffix = createElement('span', 'markdown-content__change-suffix');
132
126
  appendInline(suffix, node.suffix);
133
127
  values.append(suffix);
134
128
  }
@@ -216,10 +210,7 @@ function renderListItem(
216
210
  appendInline(heading, item.heading);
217
211
  body.append(heading);
218
212
  } else if (item.resource) {
219
- const heading = createElement(
220
- 'div',
221
- 'markdown-content__resource-heading',
222
- );
213
+ const heading = createElement('div', 'markdown-content__resource-heading');
223
214
  if (item.resource.mediaPlacement === 'inline') {
224
215
  heading.append(
225
216
  renderMedia(
@@ -295,24 +286,35 @@ function renderSymbol(node: MarkdownSymbol): HTMLSpanElement {
295
286
  symbol.dataset.markdownSymbol = node.name;
296
287
  if (node.tone) symbol.dataset.tone = node.tone;
297
288
  if (node.lang) symbol.lang = node.lang;
298
- symbol.append(createSymbolIcon(node.name));
289
+ symbol.append(createSymbolIcon(node));
299
290
 
300
291
  if (node.label) {
301
292
  const label = createElement('span', 'markdown-content__symbol-label');
302
293
  label.textContent = node.label;
303
294
  symbol.append(label);
304
295
  } else if (node.accessibleLabel) {
305
- const label = createElement(
306
- 'span',
307
- 'markdown-content__visually-hidden',
308
- );
296
+ const label = createElement('span', 'markdown-content__visually-hidden');
309
297
  label.textContent = node.accessibleLabel;
310
298
  symbol.append(label);
311
299
  }
312
300
  return symbol;
313
301
  }
314
302
 
315
- function createSymbolIcon(name: MarkdownSymbol['name']): SVGSVGElement {
303
+ function createSymbolIcon(
304
+ node: MarkdownSymbol,
305
+ ): SVGSVGElement | HTMLImageElement {
306
+ if (node.media) {
307
+ const image = document.createElement('img');
308
+ image.classList.add('markdown-content__symbol-media');
309
+ image.src = node.media.src;
310
+ image.alt = node.media.alt;
311
+ image.width = node.media.width;
312
+ image.height = node.media.height;
313
+ image.loading = node.media.loading ?? 'lazy';
314
+ image.decoding = node.media.decoding ?? 'async';
315
+ return image;
316
+ }
317
+ const name = node.name;
316
318
  const svg = document.createElementNS(svgNamespace, 'svg');
317
319
  svg.setAttribute('viewBox', '0 0 32 32');
318
320
  svg.setAttribute('fill', 'none');
@@ -320,13 +322,6 @@ function createSymbolIcon(name: MarkdownSymbol['name']): SVGSVGElement {
320
322
  svg.setAttribute('focusable', 'false');
321
323
  svg.classList.add('markdown-content__symbol-icon');
322
324
 
323
- if (name.startsWith('terrain-')) {
324
- svg.classList.add('circle-symbol-icon');
325
- svg.dataset.terrainIcon = name.slice('terrain-'.length);
326
- appendTerrainIcon(svg, name);
327
- return svg;
328
- }
329
-
330
325
  if (name === 'bullet') {
331
326
  const circle = document.createElementNS(svgNamespace, 'circle');
332
327
  circle.setAttribute('cx', '16');
@@ -358,55 +353,6 @@ function createSymbolIcon(name: MarkdownSymbol['name']): SVGSVGElement {
358
353
  return svg;
359
354
  }
360
355
 
361
- function appendTerrainIcon(
362
- svg: SVGSVGElement,
363
- name: Extract<MarkdownSymbol['name'], `terrain-${string}`>,
364
- ): void {
365
- const surface = document.createElementNS(svgNamespace, 'circle');
366
- surface.setAttribute('cx', '16');
367
- surface.setAttribute('cy', '16');
368
- surface.setAttribute('r', '13');
369
- surface.classList.add('circle-symbol-icon__surface');
370
- svg.append(surface);
371
-
372
- if (name === 'terrain-field') {
373
- const rectangle = document.createElementNS(svgNamespace, 'rect');
374
- rectangle.setAttribute('x', '9');
375
- rectangle.setAttribute('y', '9');
376
- rectangle.setAttribute('width', '14');
377
- rectangle.setAttribute('height', '14');
378
- rectangle.setAttribute('rx', '1');
379
- rectangle.classList.add('circle-symbol-icon__mark');
380
- const grid = document.createElementNS(svgNamespace, 'path');
381
- grid.setAttribute('d', 'M13.7 9v14m4.6-14v14M9 13.7h14M9 18.3h14');
382
- grid.classList.add('circle-symbol-icon__mark');
383
- svg.append(rectangle, grid);
384
- return;
385
- }
386
-
387
- const fill = document.createElementNS(svgNamespace, 'path');
388
- fill.classList.add('circle-symbol-icon__fill');
389
- fill.setAttribute(
390
- 'd',
391
- name === 'terrain-grass'
392
- ? 'M23.5 8.5c.2 8.4-3.7 14-11.7 15.2-3.1-6.8.2-13.5 11.7-15.2Z'
393
- : name === 'terrain-water'
394
- ? 'M16 5.5c3.1 4.2 7.5 9 7.5 13.3A7.5 7.5 0 1 1 8.5 18.8C8.5 14.5 12.9 9.7 16 5.5Z'
395
- : 'm5.5 24 7.3-13 3.1 5.1 3.4-7.1 7.2 15H5.5Z',
396
- );
397
- const highlight = document.createElementNS(svgNamespace, 'path');
398
- highlight.classList.add('circle-symbol-icon__highlight');
399
- highlight.setAttribute(
400
- 'd',
401
- name === 'terrain-grass'
402
- ? 'M10.4 23.5c2.2-5.3 5.4-8.7 10.2-11.6'
403
- : name === 'terrain-water'
404
- ? 'M12.2 19.2a4.5 4.5 0 0 0 4.5 4.1'
405
- : 'm10.8 14.6 2-3.6 3.1 5.1 1.4-2.9 2.4 3.6-3.5-1.1-2.5 2.1-2.9-3.2Z',
406
- );
407
- svg.append(fill, highlight);
408
- }
409
-
410
356
  function renderMedia(
411
357
  media: DisplayMedia,
412
358
  className: string,
@@ -476,7 +422,8 @@ function renderCodeBlock(
476
422
  return content;
477
423
  }
478
424
 
479
- const shell = createElement('nox-code-block', 'code-block-shell');
425
+ const shell = document.createElement('nox-code-block');
426
+ shell.className = 'code-block-shell';
480
427
  shell.dataset.widget = 'code-block';
481
428
  shell.dataset.tone = tone;
482
429
  content.dataset.codeBlockContent = '';
@@ -530,10 +477,7 @@ function createCopyIcon(): SVGSVGElement {
530
477
  return svg;
531
478
  }
532
479
 
533
- function createHeading(
534
- level: number,
535
- className: string,
536
- ): HTMLHeadingElement {
480
+ function createHeading(level: number, className: string): HTMLHeadingElement {
537
481
  const heading = createElement(
538
482
  `h${Math.min(6, Math.max(2, level))}` as keyof HTMLElementTagNameMap,
539
483
  className,