@master/internal-site 0.1.2 → 0.2.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 (239) hide show
  1. package/README.md +43 -0
  2. package/dist/astro.mjs +233 -47
  3. package/dist/config.d.mts +6 -0
  4. package/dist/localization-LfIbIysE.mjs +356 -0
  5. package/dist/localization.d.mts +69 -0
  6. package/dist/localization.mjs +1 -0
  7. package/package.json +12 -9
  8. package/src/astro/content-pages.ts +30 -11
  9. package/src/astro/index.ts +11 -5
  10. package/src/astro/localized-mdx.ts +45 -0
  11. package/src/astro/routes.template.ts +4 -0
  12. package/src/astro/site-context.ts +10 -2
  13. package/src/astro/style-assets.ts +107 -0
  14. package/src/benchmarks/BenchmarkBarRows.astro +3 -2
  15. package/src/benchmarks/BenchmarkBars.astro +1 -1
  16. package/src/benchmarks/BenchmarkChart.astro +5 -1
  17. package/src/benchmarks/BenchmarkChartGroup.astro +1 -1
  18. package/src/benchmarks/BenchmarkDataTable.astro +1 -1
  19. package/src/benchmarks/BenchmarkFigure.astro +2 -1
  20. package/src/benchmarks/BenchmarkMetricTable.astro +2 -2
  21. package/src/benchmarks/BenchmarkMetrics.astro +1 -1
  22. package/src/benchmarks/BenchmarkSampleSummary.astro +1 -1
  23. package/src/benchmarks/BenchmarkScrollRegion.svelte +2 -1
  24. package/src/benchmarks/BenchmarkSource.astro +1 -1
  25. package/src/benchmarks/BenchmarkStackedBars.astro +4 -3
  26. package/src/benchmarks/BenchmarkStatus.astro +3 -1
  27. package/src/benchmarks/localization.ts +22 -0
  28. package/src/benchmarks/types.ts +2 -0
  29. package/src/blog/article/BlogArticleContents.svelte +1 -1
  30. package/src/blog/article/BlogArticleEnd.astro +6 -9
  31. package/src/blog/article/BlogArticleMasthead.astro +17 -9
  32. package/src/config.ts +3 -0
  33. package/src/css/NamespaceUtilityTable.astro +1 -1
  34. package/src/css/ThemeNumberVariableTable.astro +1 -1
  35. package/src/css/foundation-data/specimen-content.ts +2 -2
  36. package/src/css/markdown.ts +64 -14
  37. package/src/demo/Demo.svelte +10 -4
  38. package/src/demo/DemoAsset.astro +6 -3
  39. package/src/demo/DemoAxes.svelte +4 -2
  40. package/src/demo/DemoCatalog.astro +9 -6
  41. package/src/demo/DemoComparison.svelte +3 -0
  42. package/src/demo/DemoContainer.svelte +8 -3
  43. package/src/demo/DemoControls.svelte +5 -1
  44. package/src/demo/DemoCopyButton.svelte +9 -4
  45. package/src/demo/DemoCopyGroup.svelte +6 -2
  46. package/src/demo/DemoExample.astro +3 -1
  47. package/src/demo/DemoFeatureSupport.svelte +5 -1
  48. package/src/demo/DemoGeneratedCSS.svelte +5 -1
  49. package/src/demo/DemoIndex.astro +6 -3
  50. package/src/demo/DemoLegend.svelte +3 -1
  51. package/src/demo/DemoMeasure.svelte +4 -2
  52. package/src/demo/DemoMedia.svelte +3 -1
  53. package/src/demo/DemoMotion.svelte +6 -3
  54. package/src/demo/DemoPaletteGroups.svelte +4 -2
  55. package/src/demo/DemoScrollArea.svelte +2 -0
  56. package/src/demo/DemoStyleComparison.astro +6 -3
  57. package/src/demo/DemoSurface.svelte +2 -0
  58. package/src/demo/DemoThemeComparison.astro +5 -2
  59. package/src/demo/DemoTokenTable.astro +5 -3
  60. package/src/demo/DemoViewTransition.astro +3 -1
  61. package/src/demo/DemoViewport.svelte +18 -11
  62. package/src/demo/DemoWaterfall.astro +4 -2
  63. package/src/demo/ProjectStyleExample.astro +4 -1
  64. package/src/demo/foundations/FoundationDialog.astro +6 -2
  65. package/src/demo/foundations/FoundationMedia.astro +1 -0
  66. package/src/demo/foundations/FoundationShrink.astro +1 -0
  67. package/src/demo/foundations/FoundationTokensView.astro +16 -5
  68. package/src/demo/foundations/Palette.svelte +11 -7
  69. package/src/demo/foundations/dialog-copy.d.ts +1 -0
  70. package/src/demo/foundations/dialog-copy.js +5 -0
  71. package/src/demo/illustration-materials.ts +1 -1
  72. package/src/demo/illustration.css +218 -0
  73. package/src/docs-shell/common/i18n.config.d.ts +1 -1
  74. package/src/docs-shell/common/i18n.config.js +4 -12
  75. package/src/docs-shell/common/locales.d.ts +8 -0
  76. package/src/docs-shell/common/locales.js +18 -0
  77. package/src/docs-shell/common/terminology.json +32 -0
  78. package/src/docs-shell/common/translations.d.ts +3 -0
  79. package/src/docs-shell/common/translations.js +294 -0
  80. package/src/docs-shell/components/ArticleHeader.astro +5 -3
  81. package/src/docs-shell/components/Callout.astro +1 -0
  82. package/src/docs-shell/components/CodeCopyButton.astro +5 -1
  83. package/src/docs-shell/components/CodeTabBar.astro +7 -3
  84. package/src/docs-shell/components/CodeTabs.astro +2 -1
  85. package/src/docs-shell/components/CodeView.astro +1 -1
  86. package/src/docs-shell/components/DocHeader.svelte +13 -17
  87. package/src/docs-shell/components/DocHeading.astro +3 -1
  88. package/src/docs-shell/components/DocMenuButton.svelte +126 -61
  89. package/src/docs-shell/components/DocSidebar.svelte +3 -2
  90. package/src/docs-shell/components/DocVersionSelect.svelte +2 -0
  91. package/src/docs-shell/components/DocumentAPIIndex.astro +1 -1
  92. package/src/docs-shell/components/DocumentChoices.astro +1 -1
  93. package/src/docs-shell/components/DocumentCodeExampleView.astro +3 -1
  94. package/src/docs-shell/components/DocumentCodeTable.astro +4 -2
  95. package/src/docs-shell/components/DocumentCopyButton.svelte +3 -0
  96. package/src/docs-shell/components/DocumentDisclosure.astro +2 -0
  97. package/src/docs-shell/components/DocumentFileTree.astro +3 -0
  98. package/src/docs-shell/components/DocumentFlow.astro +4 -1
  99. package/src/docs-shell/components/DocumentIllustration.astro +8 -12
  100. package/src/docs-shell/components/DocumentKeyList.astro +6 -4
  101. package/src/docs-shell/components/DocumentNamespaceTable.astro +5 -3
  102. package/src/docs-shell/components/DocumentOptionEntry.astro +0 -1
  103. package/src/docs-shell/components/DocumentOptionList.astro +1 -1
  104. package/src/docs-shell/components/DocumentOptions.astro +3 -2
  105. package/src/docs-shell/components/DocumentParameters.astro +1 -1
  106. package/src/docs-shell/components/DocumentPrompt.astro +2 -0
  107. package/src/docs-shell/components/DocumentValueList.astro +1 -1
  108. package/src/docs-shell/components/DocumentationIndex.svelte +8 -7
  109. package/src/docs-shell/components/ExpandContent.svelte +3 -0
  110. package/src/docs-shell/components/ExplorerView.astro +1 -0
  111. package/src/docs-shell/components/GeneratedCSS.astro +4 -1
  112. package/src/docs-shell/components/Header.svelte +1 -1
  113. package/src/docs-shell/components/InlineCode.astro +1 -0
  114. package/src/docs-shell/components/InteractingIndicator.astro +1 -0
  115. package/src/docs-shell/components/LanguageButton.svelte +1 -0
  116. package/src/docs-shell/components/LanguageSelect.svelte +2 -0
  117. package/src/docs-shell/components/Link.svelte +3 -2
  118. package/src/docs-shell/components/NotFound.astro +6 -4
  119. package/src/docs-shell/components/PMAction.astro +1 -0
  120. package/src/docs-shell/components/PageContent.svelte +1 -1
  121. package/src/docs-shell/components/PageContentDisclosure.astro +2 -1
  122. package/src/docs-shell/components/PageContentList.svelte +2 -0
  123. package/src/docs-shell/components/PageNavs.astro +3 -3
  124. package/src/docs-shell/components/SearchButton.svelte +3 -0
  125. package/src/docs-shell/components/SearchDialog.svelte +18 -16
  126. package/src/docs-shell/components/Segments.svelte +2 -0
  127. package/src/docs-shell/components/StepSection.astro +1 -2
  128. package/src/docs-shell/components/Tabs.astro +1 -0
  129. package/src/docs-shell/components/ThemeSelect.svelte +2 -0
  130. package/src/docs-shell/components/TimeAgo.svelte +6 -6
  131. package/src/docs-shell/components/markdown/Table.astro +1 -0
  132. package/src/docs-shell/components/markdown-components.ts +0 -1
  133. package/src/docs-shell/contexts/search.ts +1 -1
  134. package/src/docs-shell/factories/Page.astro +10 -5
  135. package/src/docs-shell/factories/create-page.ts +33 -6
  136. package/src/docs-shell/layouts/BlogArticle.astro +3 -11
  137. package/src/docs-shell/remark/markdown-components.js +28 -7
  138. package/src/docs-shell/remark/slug-and-toc.js +5 -7
  139. package/src/docs-shell/scripts/code.ts +5 -2
  140. package/src/docs-shell/scripts/document.ts +1 -1
  141. package/src/docs-shell/site.ts +9 -1
  142. package/src/docs-shell/types/Metadata.ts +11 -0
  143. package/src/docs-shell/utils/document-copy.d.ts +2 -0
  144. package/src/docs-shell/utils/document-copy.js +18 -0
  145. package/src/docs-shell/utils/generate-metadata.ts +5 -1
  146. package/src/docs-shell/utils/head.ts +5 -2
  147. package/src/docs-shell/utils/i18n-pathname.ts +13 -2
  148. package/src/docs-shell/utils/i18n.ts +4 -1
  149. package/src/docs-shell/utils/localized-metadata.ts +22 -0
  150. package/src/docs-shell/utils/markdown-links.ts +61 -0
  151. package/src/docs-shell/utils/markdown-tree.ts +19 -6
  152. package/src/docs-shell/utils/resolve-heading.d.ts +1 -0
  153. package/src/docs-shell/utils/resolve-heading.js +21 -0
  154. package/src/docs-shell/utils/search-pages.ts +75 -22
  155. package/src/examples/components/ArticleTransitionScene.svelte +4 -0
  156. package/src/examples/layout-system/page.astro +0 -1
  157. package/src/home/HomeBentoFrame.astro +1 -0
  158. package/src/home/HomeBentoGrid.astro +1 -0
  159. package/src/home/bento.css +167 -0
  160. package/src/home/design.css +1 -1
  161. package/src/home/hero.css +5 -0
  162. package/src/home/home.css +4 -576
  163. package/src/home/segmented-control.css +94 -0
  164. package/src/layouts/Document.astro +4 -2
  165. package/src/public/icons/json.svg +1 -0
  166. package/src/scripts/atomic-class-authoring.ts +98 -0
  167. package/src/scripts/localization.ts +239 -0
  168. package/src/scripts/mdx-sample.ts +12 -0
  169. package/src/scripts/static-social.ts +17 -3
  170. package/src/scripts/style-delivery.mjs +120 -0
  171. package/src/sections/blog.astro +1 -1
  172. package/src/sections/guide.astro +4 -3
  173. package/src/styles/blog-article.css +10 -7
  174. package/src/styles/blog-materials.css +13 -0
  175. package/src/styles/demo.css +0 -1
  176. package/src/styles/docs-shell/code.css +5 -61
  177. package/src/styles/docs-shell/documentation-search.css +1 -0
  178. package/src/styles/docs-shell/header.css +2 -73
  179. package/src/styles/docs-shell/menu.css +37 -17
  180. package/src/styles/docs-shell/page-content.css +2 -37
  181. package/src/styles/docs-shell/prose.css +18 -9
  182. package/src/styles/docs-shell/steps.css +2 -39
  183. package/src/styles/document-comparison.css +5 -2
  184. package/src/styles/documentation-illustration.css +1 -9
  185. package/src/styles/documentation-index.css +2 -1
  186. package/src/styles/documents.css +2 -4
  187. package/src/styles/interface.css +5 -3
  188. package/src/styles/system/DESIGN.md +34 -14
  189. package/src/styles/system/browser.css +21 -0
  190. package/src/styles/system/button-feedback.css +13 -0
  191. package/src/styles/system/callout.css +14 -0
  192. package/src/styles/system/chart.css +18 -0
  193. package/src/styles/system/checks.css +18 -0
  194. package/src/styles/system/code-panel.css +82 -0
  195. package/src/styles/system/code-tabs.css +40 -0
  196. package/src/styles/system/code.css +8 -85
  197. package/src/styles/system/compare.css +7 -0
  198. package/src/styles/system/controls.css +10 -109
  199. package/src/styles/system/copy.css +32 -0
  200. package/src/styles/system/data.css +10 -134
  201. package/src/styles/system/dialog.css +16 -0
  202. package/src/styles/system/disclosure.css +19 -0
  203. package/src/styles/system/expand.css +11 -0
  204. package/src/styles/system/fields.css +25 -0
  205. package/src/styles/system/header-core.css +101 -0
  206. package/src/styles/system/hint.css +5 -0
  207. package/src/styles/system/index.css +13 -0
  208. package/src/styles/system/inline-code.css +4 -0
  209. package/src/styles/system/kbd.css +4 -0
  210. package/src/styles/system/logos.css +10 -0
  211. package/src/styles/system/menu.css +14 -0
  212. package/src/styles/system/metrics.css +22 -0
  213. package/src/styles/system/pagehead.css +13 -0
  214. package/src/styles/system/pager.css +13 -0
  215. package/src/styles/system/parameters.css +17 -0
  216. package/src/styles/system/prompt.css +7 -0
  217. package/src/styles/system/prose.css +4 -75
  218. package/src/styles/system/range.css +12 -0
  219. package/src/styles/system/reading.css +44 -0
  220. package/src/styles/system/scroll.css +6 -0
  221. package/src/styles/system/search-trigger.css +10 -0
  222. package/src/styles/system/segmented.css +22 -0
  223. package/src/styles/system/sheet.css +8 -0
  224. package/src/styles/system/shell.css +8 -102
  225. package/src/styles/system/sidebar.css +14 -0
  226. package/src/styles/system/source.css +5 -0
  227. package/src/styles/system/specimens.css +3 -0
  228. package/src/styles/system/stage.css +18 -0
  229. package/src/styles/system/steps.css +51 -0
  230. package/src/styles/system/surfaces.css +8 -104
  231. package/src/styles/system/table.css +23 -0
  232. package/src/styles/system/toc.css +59 -0
  233. package/src/styles/system/tooltip.css +6 -0
  234. package/src/styles/system/tray-code.css +6 -0
  235. package/src/styles/system/tray.css +20 -0
  236. package/src/styles/system/tree.css +15 -0
  237. package/src/styles/system/waterfall.css +14 -0
  238. package/src/virtual.d.ts +5 -1
  239. package/src/blog/article/language.ts +0 -29
@@ -3,6 +3,8 @@
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
6
+ import '@master/internal-site/styles/system/segmented.css'
7
+
6
8
  import '@master/internal-site/styles/documentation-index.css'
7
9
  import Link from '@master/internal-site/components/Link.svelte'
8
10
  import { getSite } from '@master/internal-site/site'
@@ -24,7 +26,6 @@
24
26
  } = $props()
25
27
 
26
28
  const { locale, t } = getSite()
27
- const tw = locale === 'tw'
28
29
  let alphabetical = $state(false)
29
30
  const entries = $derived(sections.flatMap(section => section.groups.flatMap(group => group.entries)))
30
31
  const letters = $derived(Map.groupBy([...entries].sort((a, b) => a.title.localeCompare(b.title)), entry => /^[a-z]/i.test(entry.title) ? entry.title[0].toUpperCase() : '#'))
@@ -71,7 +72,7 @@
71
72
 
72
73
  <div class="documentation-index">
73
74
  <!-- A contents list, not a second catalog: each section by its number, name and size; the sibling index closes it. -->
74
- <nav class="documentation-index-directory" aria-label={tw ? `${name === 'guide' ? 'Guide' : 'Reference'} 分區` : `${name === 'guide' ? 'Guide' : 'Reference'} sections`}>
75
+ <nav class="documentation-index-directory" aria-label={t('{name} sections').replace('{name}', t(name === 'guide' ? 'Guide' : 'Reference'))}>
75
76
  {#each sections as section, index (section.id)}
76
77
  <a href={`#${section.id}`} onclick={() => alphabetical = false}>
77
78
  <small>{ordinal(index)}</small>
@@ -87,9 +88,9 @@
87
88
  </nav>
88
89
 
89
90
  <div class="documentation-index-toolbar">
90
- <p>{tw ? (name === 'guide' ? '所有教學' : '所有條目') : (name === 'guide' ? 'All guides' : 'All entries')} <span class="documentation-index-count">{entries.length}</span></p>
91
- <div class="ui-segmented" data-variant="underline" role="group" aria-label={tw ? '索引顯示方式' : 'Index view'}>
92
- <button type="button" aria-pressed={!alphabetical} aria-controls={panelId('category')} onclick={() => alphabetical = false}>{tw ? '依分類' : 'By category'}</button>
91
+ <p>{t(name === 'guide' ? 'All guides' : 'All entries')} <span class="documentation-index-count">{entries.length}</span></p>
92
+ <div class="ui-segmented" data-variant="underline" role="group" aria-label={t('Index view')}>
93
+ <button type="button" aria-pressed={!alphabetical} aria-controls={panelId('category')} onclick={() => alphabetical = false}>{t('By category')}</button>
93
94
  <button type="button" aria-pressed={alphabetical} aria-controls={panelId('alphabetical')} onclick={() => alphabetical = true}>A–Z</button>
94
95
  </div>
95
96
  </div>
@@ -102,7 +103,7 @@
102
103
  <h2 id={section.id}>{section.title} <span class="documentation-index-count">{count(section)}</span></h2>
103
104
  <p>{section.description}</p>
104
105
  {#if section.groups.length > 1}
105
- <nav class="documentation-index-groups" aria-label={tw ? `${section.title}分類` : `${section.title} categories`}>
106
+ <nav class="documentation-index-groups" aria-label={t('{name} categories').replace('{name}', section.title)}>
106
107
  {#each section.groups as group, index (groupId(section, index))}
107
108
  <a href={`#${groupId(section, index)}`}>{group.title}</a>
108
109
  {/each}
@@ -126,7 +127,7 @@
126
127
  </div>
127
128
 
128
129
  <div id={panelId('alphabetical')} hidden={!alphabetical}>
129
- <nav class="documentation-index-letters" aria-label={tw ? '依字母跳轉' : 'Jump to a letter'}>
130
+ <nav class="documentation-index-letters" aria-label={t('Jump to a letter')}>
130
131
  {#each [...letters.keys()] as letter (letter)}
131
132
  <a href={`#${letterId(letter)}`}>{letter}</a>
132
133
  {/each}
@@ -1,4 +1,7 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/interface.css'
3
+ import '@master/internal-site/styles/system/expand.css'
4
+
2
5
  import type { Snippet } from 'svelte'
3
6
  import IconChevronDown from '@tabler/icons-svelte-runes/icons/chevron-down'
4
7
  import { getSite } from '../site'
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/tree.css'
2
3
  import FileIcon from './FileIcon.astro'
3
4
  import FolderSvg from '../../public/icons/folder.svg'
4
5
 
@@ -1,8 +1,11 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/disclosure.css'
3
+ import { siteOf } from '../../astro/site-context'
4
+ const { t } = siteOf(Astro)
2
5
  /** A closed native disclosure keeps the complete output in the server HTML. */
3
6
  ---
4
7
 
5
8
  <details class="ui-disclosure" data-generated="">
6
- <summary>Generated CSS</summary>
9
+ <summary>{t('Generated CSS')}</summary>
7
10
  <slot />
8
11
  </details>
@@ -12,7 +12,7 @@
12
12
  </script>
13
13
 
14
14
  <script lang="ts">
15
- import '../../styles/docs-shell/header.css'
15
+ import '../../styles/system/header-core.css'
16
16
  import { throttle } from 'throttle-debounce'
17
17
  import { flushSync } from 'svelte'
18
18
  import clsx from 'clsx'
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/inline-code.css'
2
3
  import clsx from 'clsx'
3
4
  import { toHtml } from 'hast-util-to-html'
4
5
  import highlightCode from '../utils/highlight-code'
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/hint.css'
2
3
  import IconForms from '@tabler/icons-svelte-runes/icons/forms'
3
4
  import IconHandClick from '@tabler/icons-svelte-runes/icons/hand-click'
4
5
  import IconHandMove from '@tabler/icons-svelte-runes/icons/hand-move'
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/interface.css'
2
3
  import IconLanguage from '@tabler/icons-svelte-runes/icons/language'
3
4
  import LanguageSelect from './LanguageSelect.svelte'
4
5
  import clsx from 'clsx'
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/system/fields.css'
3
+
2
4
  import type { Snippet } from 'svelte'
3
5
  import type { HTMLSelectAttributes } from 'svelte/elements'
4
6
  import clsx from 'clsx'
@@ -58,12 +58,13 @@
58
58
  const isHash = $derived(props.href?.startsWith('#'))
59
59
  const external = $derived(props.href?.startsWith('http'))
60
60
  const href: string = $derived.by(() => {
61
- if (!props.href?.startsWith('/')) return props.href || ''
61
+ if (!props.href) return ''
62
62
  return localizePathname(props.href, {
63
63
  defaultLocale: i18n.defaultLocale,
64
64
  locale,
65
65
  locales: i18n.locales,
66
- localizablePathnameRoots: i18n.localizablePathnameRoots
66
+ localizablePathnameRoots: i18n.localizablePathnameRoots,
67
+ localizableOrigins: i18n.localizableOrigins
67
68
  }) || ''
68
69
  })
69
70
  const canonicalHref = $derived(canonicalizeDefaultLocalePathname(href, i18n.defaultLocale))
@@ -1,12 +1,14 @@
1
1
  ---
2
+ import '@master/internal-site/styles/interface.css'
3
+ import '@master/internal-site/styles/system/pagehead.css'
4
+ import '@master/internal-site/styles/system/search-trigger.css'
2
5
  import '../../styles/docs-shell/not-found.css'
3
6
  import { siteOf } from '../../astro/site-context'
4
7
  import SearchButton from './SearchButton.svelte'
5
8
  import Link from './Link.svelte'
6
9
  import config from 'virtual:master-site/config'
7
10
 
8
- const { t: $, locale } = siteOf(Astro)
9
- const tw = locale === 'tw'
11
+ const { t: $ } = siteOf(Astro)
10
12
  const guide = Boolean(config.sections.guide)
11
13
  ---
12
14
 
@@ -14,8 +16,8 @@ const guide = Boolean(config.sections.guide)
14
16
  <div class="ui-pagehead">
15
17
  <span class="ui-pagehead-kicker">404</span>
16
18
  <h1>{$('This page does not exist')}</h1>
17
- <p>{guide ? (tw ? '試著搜尋文件,或返回 Guide 繼續閱讀。' : 'Try searching the documentation or return to the Guide.') : (tw ? '試著搜尋網站,或返回首頁。' : 'Try searching the site or return to the homepage.')}</p>
19
+ <p>{guide ? $('Try searching the documentation or return to the Guide.') : $('Try searching the site or return to the homepage.')}</p>
18
20
  </div>
19
21
  <SearchButton client:load class="ui-search-trigger" />
20
- <Link href={guide ? "/guide" : "/"} class="ui-button" data-variant="quiet">{guide ? (tw ? '瀏覽 Guide' : 'Browse the Guide') : (tw ? '返回首頁' : 'Back to home')}<span aria-hidden="true">→</span></Link>
22
+ <Link href={guide ? "/guide" : "/"} class="ui-button" data-variant="quiet">{guide ? $('Browse the Guide') : $('Back to home')}<span aria-hidden="true">→</span></Link>
21
23
  </main>
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/code-panel.css'
2
3
  import { resolveCommand } from 'package-manager-detector/commands'
3
4
  import CodeTabs from './CodeTabs.astro'
4
5
  import { slotText } from '../utils/slot-text'
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import '../../styles/docs-shell/page-content.css'
2
+ import '../../styles/system/toc.css'
3
3
  import Link from './Link.svelte'
4
4
  import app from 'virtual:master-site/app'
5
5
  import PageContentProvider from './PageContentProvider.svelte'
@@ -1,5 +1,6 @@
1
1
  ---
2
- import '../../styles/docs-shell/page-content.css'
2
+ import '@master/internal-site/styles/system/disclosure.css'
3
+ import '../../styles/system/toc.css'
3
4
  import type { PageContentItem } from './page-content.svelte'
4
5
  import PageContentTitle from './PageContentTitle.svelte'
5
6
  import groupPageContent from '../utils/group-page-content'
@@ -6,6 +6,8 @@
6
6
  </script>
7
7
 
8
8
  <script lang="ts">
9
+ import '@master/internal-site/styles/system/toc.css'
10
+
9
11
  import Link from './Link.svelte'
10
12
  import PageContentTitle from './PageContentTitle.svelte'
11
13
  import { usePageContent, type PageContentItem } from './page-content.svelte'
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/pager.css'
2
3
  import Link from './Link.svelte'
3
4
  import { createTranslation } from '../utils/i18n'
4
5
 
@@ -12,7 +13,6 @@ interface Props {
12
13
  /** The previous and next documents in the section's order, as a `ui-pager`. */
13
14
  const { pageCategories, metadata, locale, dictionaries } = Astro.props
14
15
  const $ = await createTranslation(locale, dictionaries)
15
- const tw = locale === 'tw'
16
16
  const unit = metadata?.pathname?.split('/')?.[1]
17
17
  const pages = (pageCategories?.flatMap((category: any) => category.pages) || [])
18
18
  .filter(({ pathname }: any) => pathname.split('/').length <= 3 && unit)
@@ -25,13 +25,13 @@ const navs = [
25
25
  ].filter(Boolean).map(({ direction, definedMetadata }: any) => ({
26
26
  direction,
27
27
  href: definedMetadata.pathname,
28
- label: direction === 'prev' ? (tw ? '上一篇' : 'Previous') : (tw ? '下一篇' : 'Next'),
28
+ label: direction === 'prev' ? $('Previous') : $('Next'),
29
29
  title: $(definedMetadata.title.absolute || definedMetadata.title)
30
30
  }))
31
31
  ---
32
32
 
33
33
  {navs.length > 0 && (
34
- <nav class="ui-pager" aria-label={tw ? '前後篇文件' : 'Previous and next documents'}>
34
+ <nav class="ui-pager" aria-label={$('Previous and next documents')}>
35
35
  {navs.map(({ direction, href, label, title }) => <Link {href} rel={direction} aria-label={`${label}: ${title}`}>
36
36
  <small>{label}</small>
37
37
  <b>{title}</b>
@@ -1,4 +1,7 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/interface.css'
3
+ import '@master/internal-site/styles/system/search-trigger.css'
4
+
2
5
  import type { Snippet } from 'svelte'
3
6
  import type { HTMLButtonAttributes } from 'svelte/elements'
4
7
  import IconSearch from '@tabler/icons-svelte-runes/icons/search'
@@ -20,6 +20,9 @@
20
20
  </script>
21
21
 
22
22
  <script lang="ts">
23
+ import '@master/internal-site/styles/interface.css'
24
+ import '@master/internal-site/styles/system/dialog.css'
25
+
23
26
  import '../../styles/docs-shell/documentation-search.css'
24
27
  import { untrack } from 'svelte'
25
28
  import Link from './Link.svelte'
@@ -35,9 +38,9 @@
35
38
 
36
39
  /** The page's single search dialog; search triggers in other islands open it by event (`contexts/search`). */
37
40
  const { t, locale } = getSite()
38
- const tw = locale === 'tw'
39
41
  let opened = $state(false)
40
42
  let query = $state('')
43
+ let shortcutModifier = $state('⌘')
41
44
  let pages = $state.raw<DocumentationSearchPage[]>([])
42
45
  let error = $state('')
43
46
  let loading = $state(false)
@@ -67,6 +70,7 @@
67
70
  }
68
71
 
69
72
  function open(event?: Event) {
73
+ shortcutModifier = /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? '⌘' : 'Ctrl'
70
74
  const trigger = (event as CustomEvent<{ trigger?: HTMLElement }> | undefined)?.detail?.trigger
71
75
  returnFocus = trigger instanceof HTMLElement ? trigger : document.activeElement as HTMLElement
72
76
  opened = true
@@ -125,10 +129,8 @@
125
129
  }
126
130
  }
127
131
 
128
- const reasons: Record<string, string> | undefined = tw ? { Identifier: '識別字', Title: '名稱', Syntax: '語法與範例', Topic: '主題', Content: '內文' } : undefined
129
- const kinds: Record<string, string> = tw
130
- ? { utility: 'Utility', rule: '規則', tokens: 'Tokens', directive: '指令', tool: '工具', package: 'API' }
131
- : { utility: 'Utility', rule: 'Rule', tokens: 'Tokens', directive: 'Directive', tool: 'Tool', package: 'API' }
132
+ const kinds: Record<string, string> = { utility: 'Utility', rule: 'Rule', tokens: 'Tokens', directive: 'Directive', tool: 'Tool', package: 'API' }
133
+
132
134
  </script>
133
135
 
134
136
  <svelte:window onkeydown={shortcut} />
@@ -142,33 +144,33 @@
142
144
  {/snippet}
143
145
 
144
146
  {#if opened}
145
- <dialog bind:this={dialog} {@attach portal} {@attach closeOnBackdropClick} class="ui-dialog" data-variant="search" aria-label={tw ? '搜尋文件' : 'Search documentation'} oncancel={close}>
147
+ <dialog bind:this={dialog} {@attach portal} {@attach closeOnBackdropClick} class="ui-dialog" data-variant="search" aria-label={t('Search documentation')} oncancel={close}>
146
148
  <div class="ui-dialog-search">
147
149
  <IconSearch size={18} stroke={1.5} aria-hidden="true" />
148
- <input bind:this={input} type="search" role="combobox" aria-autocomplete="list" aria-expanded={Boolean(query)} value={query} autocomplete="off" spellcheck={false} placeholder={tw ? '搜尋文件…' : 'Search documentation…'} aria-label={tw ? '搜尋文件' : 'Search documentation'} aria-controls="documentation-search-results" aria-activedescendant={results[selected] ? `documentation-result-${selected}` : undefined}
150
+ <input bind:this={input} type="search" role="combobox" aria-autocomplete="list" aria-expanded={Boolean(query)} value={query} autocomplete="off" spellcheck={false} placeholder={t('Search documentation…')} aria-label={t('Search documentation')} aria-controls="documentation-search-results" aria-activedescendant={results[selected] ? `documentation-result-${selected}` : undefined}
149
151
  oninput={event => selectQuery(event.currentTarget.value)}
150
152
  {onkeydown} />
151
- {#if query}<button type="button" class="ui-button" data-variant="quiet" data-shape="icon" data-size="sm" onclick={() => selectQuery('')} aria-label={tw ? '清除搜尋' : 'Clear search'}><IconX size={16} stroke={1.5} aria-hidden="true" /></button>{/if}
152
- <button type="button" class="ui-button ui-dialog-close" data-variant="quiet" data-size="sm" onclick={close} aria-label={tw ? '關閉搜尋' : 'Close search'}><span class="ui-kbd">esc</span><span>{tw ? '取消' : 'Cancel'}</span></button>
153
+ {#if query}<button type="button" class="ui-button" data-variant="quiet" data-shape="icon" data-size="sm" onclick={() => selectQuery('')} aria-label={t('Clear search')}><IconX size={16} stroke={1.5} aria-hidden="true" /></button>{/if}
154
+ <button type="button" class="ui-button ui-dialog-close" data-variant="quiet" data-size="sm" onclick={close} aria-label={t('Close search')}><span class="ui-kbd">esc</span><span>{t('Cancel')}</span></button>
153
155
  </div>
154
156
  <div class="ui-dialog-body" bind:this={scroll}>
155
- <p class="ui-dialog-status" role="status">{loading ? (tw ? '正在載入文件…' : 'Loading documentation…') : error ? (tw ? '搜尋暫時無法載入。' : 'Search could not load.') : query ? `${results.length === 40 ? (tw ? '前 ' : 'Top ') : ''}${results.length} ${tw ? '筆結果' : results.length === 1 ? 'result' : 'results'}` : (tw ? '依名稱、別名或 CSS 屬性查找' : 'Find a name, alias or CSS property')}</p>
156
- {#if error}<div class="ui-dialog-note"><p>{tw ? '請檢查連線,再試一次。' : 'Check your connection and try again.'}</p><button type="button" class="ui-button" data-size="sm" onclick={() => { retry += 1; input?.focus() }}>{tw ? '再試一次' : 'Try again'}</button></div>{/if}
157
+ <p class="ui-dialog-status" role="status">{loading ? (t('Loading documentation…')) : error ? (t('Search could not load.')) : query ? t(results.length === 40 ? 'Top {count} results' : '{count} results').replace('{count}', String(results.length)) : (t('Find a name, alias or CSS property'))}</p>
158
+ {#if error}<div class="ui-dialog-note"><p>{t('Check your connection and try again.')}</p><button type="button" class="ui-button" data-size="sm" onclick={() => { retry += 1; input?.focus() }}>{t('Try again')}</button></div>{/if}
157
159
  {#if !query && !error && !loading}<div class="ui-dialog-note">
158
- <p>{tw ? '試試看' : 'Try searching'}</p>
160
+ <p>{t('Try searching')}</p>
159
161
  {#each ['padding', 'padding-inline-start:', 'fg-red:hover@sm'] as value (value)}<button type="button" class="ui-button" data-size="sm" onclick={() => selectQuery(value)}><code>{value}</code><IconCornerDownLeft size={14} stroke={1.5} aria-hidden="true" /></button>{/each}
160
162
  </div>{/if}
161
- {#if query && !results.length && !error && !loading}<div class="ui-dialog-note" data-align="center"><IconSearch size={24} stroke={1.25} aria-hidden="true" /><b>{tw ? '找不到相符的文件' : 'No matching documents'}</b><p>{tw ? '試試屬性全名、token 名稱或其他關鍵字。' : 'Try a property name, a token name or a different keyword.'}</p></div>{/if}
162
- <ol id="documentation-search-results" class="ui-results" role="listbox" aria-label={tw ? '搜尋結果' : 'Search results'} aria-busy={loading}>
163
+ {#if query && !results.length && !error && !loading}<div class="ui-dialog-note" data-align="center"><IconSearch size={24} stroke={1.25} aria-hidden="true" /><b>{t('No matching documents')}</b><p>{t('Try a property name, a token name or a different keyword.')}</p></div>{/if}
164
+ <ol id="documentation-search-results" class="ui-results" role="listbox" aria-label={t('Search results')} aria-busy={loading}>
163
165
  {#if !error}{#each results as result, index (result.page.url)}<li role="presentation">
164
166
  <Link id={`documentation-result-${index}`} role="option" tabindex={-1} aria-selected={selected === index} href={result.href} onclick={close} onpointermove={() => { selected = index }}>
165
- <small>{t(result.page.category ?? '')} · {kinds[result.page.kind ?? ''] ?? (result.page.url.includes('/guide') ? 'Guide' : 'Page')} · {reasons?.[result.reason] ?? result.reason}</small>
167
+ <small>{t(result.page.category ?? '')} · {t(kinds[result.page.kind ?? ''] ?? (result.page.url.includes('/guide') ? 'Guide' : 'Page'))} · {t(result.reason)}</small>
166
168
  <b>{result.page.title}</b>
167
169
  <span>{@render excerpt(result.excerpt, query)}</span>
168
170
  </Link>
169
171
  </li>{/each}{/if}
170
172
  </ol>
171
173
  </div>
172
- <div class="ui-dialog-foot" aria-hidden="true"><span><span class="ui-kbd"><IconArrowUp size={12} /></span><span class="ui-kbd"><IconArrowDown size={12} /></span>{tw ? '選擇' : 'Navigate'}</span><span><span class="ui-kbd"><IconCornerDownLeft size={12} /></span>{tw ? '開啟' : 'Open'}</span><span>{tw ? '文件搜尋' : 'Documentation'}</span></div>
174
+ <div class="ui-dialog-foot" aria-hidden="true"><span><span class="ui-kbd"><IconArrowUp size={12} /></span><span class="ui-kbd"><IconArrowDown size={12} /></span>{t('Navigate')}</span><span><span class="ui-kbd"><IconCornerDownLeft size={12} /></span>{t('Open')}</span><span class="ui-search-shortcut">{t('Search anytime')}<kbd class="ui-kbd">{shortcutModifier}</kbd><kbd class="ui-kbd">K</kbd></span></div>
173
175
  </dialog>
174
176
  {/if}
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/system/segmented.css'
3
+
2
4
  import '../../styles/docs-shell/segments.css'
3
5
  import type { Snippet } from 'svelte'
4
6
 
@@ -1,6 +1,5 @@
1
1
  ---
2
- import '../../styles/system/data.css'
3
- import '../../styles/docs-shell/steps.css'
2
+ import '../../styles/system/steps.css'
4
3
  import type { HTMLAttributes } from 'astro/types'
5
4
 
6
5
  type Props = HTMLAttributes<'ol'>
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/segmented.css'
2
3
  import type { HTMLAttributes } from 'astro/types'
3
4
 
4
5
  type Props = HTMLAttributes<'nav'>
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import '@master/internal-site/styles/system/fields.css'
3
+
2
4
  import type { HTMLSelectAttributes } from 'svelte/elements'
3
5
  import clsx from 'clsx'
4
6
  import { useThemeMode } from '../contexts/theme-mode.svelte'
@@ -3,13 +3,13 @@
3
3
  import relativeTime from 'dayjs/plugin/relativeTime'
4
4
  import 'dayjs/locale/zh-tw'
5
5
  import 'dayjs/locale/en'
6
+ import 'dayjs/locale/ja'
7
+ import 'dayjs/locale/ko'
8
+ import 'dayjs/locale/zh-cn'
9
+ import { languageOf, formatDate } from '../common/locales'
6
10
 
7
11
  dayjs.extend(relativeTime)
8
12
 
9
- const locales: Record<string, string> = {
10
- tw: 'zh-tw',
11
- en: 'en'
12
- }
13
13
  </script>
14
14
 
15
15
  <script lang="ts">
@@ -21,8 +21,8 @@
21
21
  const { locale } = getSite()
22
22
  let hydrated = $state(false)
23
23
  const formattedDate = $derived(hydrated
24
- ? dayjs(timestamp).locale(locales[locale]).fromNow()
25
- : dayjs(timestamp).format('ddd, MMMM D YYYY'))
24
+ ? dayjs(timestamp).locale(languageOf(locale).toLowerCase()).fromNow()
25
+ : formatDate(timestamp, locale, { weekday: 'short' }))
26
26
 
27
27
  onMount(() => {
28
28
  hydrated = true
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import '@master/internal-site/styles/system/table.css'
2
3
  ---
3
4
 
4
5
  <div class="ui-table">
@@ -1,4 +1,3 @@
1
- import '../../styles/system/data.css'
2
1
  import Image from './Image.svelte'
3
2
  import Do from './Do.astro'
4
3
  import DoNot from './DoNot.astro'
@@ -13,6 +13,6 @@ export function searchLabels(translate: (text: string) => string, locale: string
13
13
  searchPlaceholder,
14
14
  /** The placeholder without its shortcut, which triggers show as a key. */
15
15
  searchText: searchPlaceholder.replace(/\s*⌘\s*K\s*…?$/u, ''),
16
- searchLabel: locale === 'tw' ? '搜尋文件' : 'Search documentation',
16
+ searchLabel: translate('Search documentation'),
17
17
  }
18
18
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
3
3
  import type { PageParams } from '../types/Props'
4
- import { resolveContent, type ContentModule } from './create-page'
4
+ import { resolveDocument, resolveNavigation, type ContentModule } from './create-page'
5
5
 
6
6
  interface Props {
7
7
  params: PageParams
@@ -18,18 +18,23 @@ interface Props {
18
18
  }
19
19
 
20
20
  const { params, metadata, dictionaries, categories, content, Layout, noTOC = false, subtitle, ...options } = Astro.props
21
- const contentModule = content ? await resolveContent(params.locale, metadata, content) : undefined
21
+ const document = await resolveDocument(params.locale, metadata, content)
22
+ const contentModule = document.content
23
+ const pageMetadata = document.metadata
24
+ const pageCategories = resolveNavigation(params.locale, categories)
25
+ const locals = Astro.locals as Record<string, any>
26
+ locals.masterDocument = contentModule?.frontmatter ?? {}
22
27
  if (contentModule && !contentModule.default) throw new Error('Invalid content module')
23
28
  const Content = contentModule?.default
24
29
  const toc = contentModule?.toc
25
30
  ---
26
31
 
27
32
  {Layout
28
- ? <Layout {...options} params={params} pageCategories={categories} dictionaries={dictionaries} metadata={metadata} toc={noTOC ? null : toc} subtitle={subtitle}>
29
- {Content && <Content pageCategories={categories} />}
33
+ ? <Layout {...options} params={params} {pageCategories} dictionaries={dictionaries} metadata={pageMetadata} toc={noTOC ? null : toc} subtitle={subtitle}>
34
+ {Content && <Content {pageCategories} />}
30
35
  <slot />
31
36
  </Layout>
32
37
  : <Fragment>
33
- {Content && <Content pageCategories={categories} />}
38
+ {Content && <Content {pageCategories} />}
34
39
  <slot />
35
40
  </Fragment>}
@@ -1,10 +1,14 @@
1
1
  import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
2
2
  import type { Props } from '../types/Props'
3
3
  import generate from '../utils/generate-metadata'
4
+ import config from 'virtual:master-site/config'
5
+ import metadataByPathname from 'virtual:master-site/localized-metadata'
6
+ import { localizedMetadata } from '../utils/localized-metadata'
4
7
 
5
8
  export interface ContentModule {
6
9
  default: AstroComponentFactory
7
10
  toc?: any
11
+ frontmatter?: Record<string, any>
8
12
  }
9
13
 
10
14
  /** Translated MDX lives beside its source as `content.<locale>.mdx`. */
@@ -20,12 +24,35 @@ export function createPageMetadata(metadata: any, dictionaries: Record<string, (
20
24
  * Astro collects a page's stylesheets from its module graph when the page module loads, before a pending dynamic import finishes in dev.
21
25
  */
22
26
  export async function resolveContent(locale: string, metadata: any, content: ContentModule) {
23
- const directory = contentDirectoryOf(metadata)
24
- return (directory ? await translatedContent[`/content/${directory}/content.${locale}.mdx`]?.() : undefined) ?? content
27
+ const propagated = await loadLocalizedContent(locale, metadata)
28
+ if (propagated) return await propagated.getMod()
29
+ if (locale !== config.i18n.defaultLocale) throw new Error(`Missing localized content: ${metadata.pathname ?? metadata.sourcePath} (${locale})`)
30
+ return content
25
31
  }
26
32
 
27
- function contentDirectoryOf(metadata: any) {
28
- const sourcePath = metadata?.sourcePath
29
- if (typeof sourcePath !== 'string') return
30
- return sourcePath.match(/^site\/content\/(.+)\/content\.mdx$/)?.[1]
33
+ /** The configured inventory owns content roots; metadata carries a canonical pathname. */
34
+ export async function loadLocalizedContent(locale: string, metadata: any) {
35
+ const documents = translatedContent[metadata.pathname]
36
+ if (!documents) return
37
+ const load = documents[locale]
38
+ if (!load) throw new Error(`Missing localized content: ${metadata.pathname} (${locale})`)
39
+ return await load()
40
+ }
41
+
42
+ export function resolveMetadata(locale: string, metadata: any) {
43
+ const variants = metadataByPathname[metadata.pathname]
44
+ if (!variants) return { ...metadata, locale }
45
+ const frontmatter = variants[locale]
46
+ if (!frontmatter && locale !== config.i18n.defaultLocale) throw new Error(`Missing localized metadata: ${metadata.pathname} (${locale})`)
47
+ return localizedMetadata(metadata, frontmatter ?? {}, locale)
48
+ }
49
+
50
+ export async function resolveDocument(locale: string, metadata: any, content?: ContentModule) {
51
+ const module = content ? await resolveContent(locale, metadata, content) : await (await loadLocalizedContent(locale, metadata))?.getMod()
52
+ return { content: module, metadata: resolveMetadata(locale, metadata) }
53
+ }
54
+
55
+ /** Localized navigation reads lightweight frontmatter, without importing every MDX page. */
56
+ export function resolveNavigation(locale: string, categories: any[] = []) {
57
+ return categories.map(category => ({ ...category, pages: category.pages.filter((page: any) => !metadataByPathname[page.pathname] || metadataByPathname[page.pathname][locale]).map((page: any) => resolveMetadata(locale, page)) }))
31
58
  }
@@ -2,7 +2,6 @@
2
2
  import '@master/internal-site/styles/docs-shell/prose.css'
3
3
  import '@master/internal-site/styles/blog-materials.css'
4
4
  import '@master/internal-site/styles/blog-article.css'
5
- import translations from 'virtual:master-site/translations'
6
5
  import Footer from '@master/internal-site/components/Footer.svelte'
7
6
  import PageContentBootstrap from '@master/internal-site/components/PageContentBootstrap.astro'
8
7
  import { siteOf } from '@master/internal-site/astro/site-context'
@@ -10,10 +9,6 @@ import BlogArticleMasthead from '@master/internal-site/blog/article/BlogArticleM
10
9
  import BlogArticleContents from '@master/internal-site/blog/article/BlogArticleContents.svelte'
11
10
  import BlogArticleEnd from '@master/internal-site/blog/article/BlogArticleEnd.astro'
12
11
  import { neighborsOf } from '@master/internal-site/blog/article/neighbors'
13
- import { contentLanguageOf } from '@master/internal-site/blog/article/language'
14
-
15
- /** Translated MDX lives beside its source as `content.<locale>.mdx` (see docs-shell/factories/create-page). */
16
- const translatedContent = new Set(Object.keys(translations))
17
12
 
18
13
  /**
19
14
  * A blog post, drawn with the homepage's drafting guides: the header, the reading column (the documentation's measure
@@ -22,11 +17,8 @@ const translatedContent = new Set(Object.keys(translations))
22
17
  * left margin.
23
18
  */
24
19
  const { metadata, toc, pageCategories: posts = [], footerProps = {} } = Astro.props
25
- const { t: $, i18n, locale } = siteOf(Astro)
26
- const { defaultLocale, hreflangOfLocale } = i18n
20
+ const { t: $ } = siteOf(Astro)
27
21
  const { newer, older } = neighborsOf(metadata.sourcePath, posts)
28
- // A post not yet translated is shown in its own language; the body says so, for screen readers and Han type rules.
29
- const proseLanguage = contentLanguageOf(metadata.sourcePath, translatedContent, { locale, defaultLocale, hreflangOfLocale })
30
22
  const contents = (toc ?? []).filter((item: any) => item.level === 2 || item.level === 3).map(({ id, title, level }: any) => ({ id, title, level }))
31
23
  const hasSections = contents.some((item: any) => item.level === 2)
32
24
  // As on the blog index, a category every post shares says nothing about the next one.
@@ -39,9 +31,9 @@ const showCategory = posts.length > 1
39
31
  {/* The post's own header, so the title, date and authors belong to the article. */}
40
32
  <BlogArticleMasthead metadata={metadata} />
41
33
  {/* In the page's outer left margin, beside the text it lists. */}
42
- {hasSections ? <BlogArticleContents client:idle={{ timeout: 500 }} items={contents} label={$('On this page')} lang={proseLanguage} /> : null}
34
+ {hasSections ? <BlogArticleContents client:idle={{ timeout: 500 }} items={contents} label={$('On this page')} /> : null}
43
35
  <div class="blog-article-body blog-rails blog-rails-rule">
44
- <div class="blog-article-prose ui-prose" lang={proseLanguage}>
36
+ <div class="blog-article-prose ui-prose">
45
37
  <slot />
46
38
  </div>
47
39
  </div>
@@ -53,8 +53,29 @@ function fenceProps(node) {
53
53
  return props
54
54
  }
55
55
 
56
- const componentsExport = `import { markdownComponents as __markdownComponents } from '@master/internal-site/components/markdown-components'
57
- export const components = __markdownComponents`
56
+ // A document imports only the components it renders. Importing a shared object
57
+ // of every component also propagates every component's styles to every MDX page.
58
+ const componentPaths = {
59
+ a: 'markdown/Anchor.astro', h2: 'markdown/Heading2.astro', h3: 'markdown/Heading3.astro',
60
+ img: 'Image.svelte', Image: 'Image.svelte', table: 'markdown/Table.astro',
61
+ Code: 'Code.astro', InlineCode: 'InlineCode.astro', PMAction: 'PMAction.astro',
62
+ Do: 'Do.astro', DoNot: 'DoNot.astro', Info: 'Info.astro', Warn: 'Warn.astro',
63
+ InteractingIndicator: 'InteractingIndicator.astro'
64
+ }
65
+
66
+ function documentComponents(tree) {
67
+ const used = new Set()
68
+ visit(tree, node => {
69
+ if (node.type === 'link' || node.type === 'linkReference') used.add('a')
70
+ if (node.type === 'image' || node.type === 'imageReference') used.add('img')
71
+ if (node.type === 'table') used.add('table')
72
+ if (node.type === 'heading' && [2, 3].includes(node.depth)) used.add(`h${node.depth}`)
73
+ if (['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type) && node.name in componentPaths) used.add(node.name)
74
+ })
75
+ const entries = [...used].sort().map((name, index) => ({ name, local: `__masterComponent${index}`, path: componentPaths[name] }))
76
+ return entries.map(({ local, path }) => `import ${local} from '@master/internal-site/components/${path}'`).join('\n')
77
+ + `\nexport const components = {${entries.map(({ name, local }) => `${name}: ${local}`).join(', ')}}`
78
+ }
58
79
 
59
80
  /**
60
81
  * Markdown constructs that render through site components: fences become `Code` (or a
@@ -63,11 +84,6 @@ export const components = __markdownComponents`
63
84
  */
64
85
  export default function remarkMarkdownComponents() {
65
86
  return (tree) => {
66
- tree.children.unshift({
67
- type: 'mdxjsEsm',
68
- value: componentsExport,
69
- data: { estree: parse(componentsExport, { ecmaVersion: 'latest', sourceType: 'module' }) }
70
- })
71
87
  visit(tree, (node, index, parent) => {
72
88
  if (!parent || index === undefined) return
73
89
  if (node.type === 'code') {
@@ -97,5 +113,10 @@ export default function remarkMarkdownComponents() {
97
113
  return index
98
114
  }
99
115
  })
116
+ const source = documentComponents(tree)
117
+ tree.children.unshift({
118
+ type: 'mdxjsEsm', value: source,
119
+ data: { estree: parse(source, { ecmaVersion: 'latest', sourceType: 'module' }) }
120
+ })
100
121
  }
101
122
  }