@rocket/js 0.1.1 → 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 (107) hide show
  1. package/README.md +14 -23
  2. package/dist-types/exports/layouts/atlasBlog.d.ts +2 -0
  3. package/dist-types/exports/layouts/atlasBlog.d.ts.map +1 -0
  4. package/dist-types/exports/types/rocket.d.ts +68 -2
  5. package/dist-types/exports/types/rocket.d.ts.map +1 -1
  6. package/dist-types/exports/types.d.ts +1 -1
  7. package/dist-types/exports/types.d.ts.map +1 -1
  8. package/dist-types/src/PageData.d.ts +3 -1
  9. package/dist-types/src/PageData.d.ts.map +1 -1
  10. package/dist-types/src/RocketJsDemo.d.ts.map +1 -1
  11. package/dist-types/src/SocialPreviewPlayground.d.ts +1 -0
  12. package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -1
  13. package/dist-types/src/asyncMessage.d.ts +5 -0
  14. package/dist-types/src/asyncMessage.d.ts.map +1 -1
  15. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -1
  16. package/dist-types/src/cli/RocketCli.d.ts.map +1 -1
  17. package/dist-types/src/cli/RocketInit.d.ts +5 -1
  18. package/dist-types/src/cli/RocketInit.d.ts.map +1 -1
  19. package/dist-types/src/cli/RocketStart.d.ts +6 -1
  20. package/dist-types/src/cli/RocketStart.d.ts.map +1 -1
  21. package/dist-types/src/component-hydration.d.ts.map +1 -1
  22. package/dist-types/src/components/RocketDrawer.d.ts +8 -0
  23. package/dist-types/src/components/RocketDrawer.d.ts.map +1 -1
  24. package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -1
  25. package/dist-types/src/config.d.ts.map +1 -1
  26. package/dist-types/src/feeds.d.ts +38 -0
  27. package/dist-types/src/feeds.d.ts.map +1 -0
  28. package/dist-types/src/hydration/hydrationLoader.d.ts +13 -4
  29. package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -1
  30. package/dist-types/src/icons.d.ts.map +1 -1
  31. package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts +42 -0
  32. package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts.map +1 -0
  33. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts +4 -0
  34. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -1
  35. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -1
  36. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -1
  37. package/dist-types/src/layouts/layout-helper.d.ts +3 -3
  38. package/dist-types/src/layouts/layout-helper.d.ts.map +1 -1
  39. package/dist-types/src/markdownHook.d.ts.map +1 -1
  40. package/dist-types/src/menu.d.ts +8 -3
  41. package/dist-types/src/menu.d.ts.map +1 -1
  42. package/dist-types/src/menus/MainMenu.d.ts.map +1 -1
  43. package/dist-types/src/{development-page-module-loader.d.ts → page-module-loader.d.ts} +7 -2
  44. package/dist-types/src/page-module-loader.d.ts.map +1 -0
  45. package/dist-types/src/page-runtime.d.ts +11 -0
  46. package/dist-types/src/page-runtime.d.ts.map +1 -1
  47. package/dist-types/src/pages.d.ts.map +1 -1
  48. package/dist-types/src/route-pattern.d.ts +10 -0
  49. package/dist-types/src/route-pattern.d.ts.map +1 -0
  50. package/dist-types/src/siteDiscoverability.d.ts +4 -0
  51. package/dist-types/src/siteDiscoverability.d.ts.map +1 -1
  52. package/dist-types/src/socialPreviewImages.d.ts +2 -1
  53. package/dist-types/src/socialPreviewImages.d.ts.map +1 -1
  54. package/dist-types/src/static-params.d.ts +33 -0
  55. package/dist-types/src/static-params.d.ts.map +1 -0
  56. package/dist-types/src/transform.d.ts +4 -0
  57. package/dist-types/src/transform.d.ts.map +1 -1
  58. package/dist-types/src/wds-plugin.d.ts.map +1 -1
  59. package/exports/layouts/atlasBlog.js +11 -0
  60. package/exports/types/rocket.test.ts +26 -0
  61. package/exports/types/rocket.ts +78 -2
  62. package/exports/types.ts +7 -0
  63. package/package.json +16 -32
  64. package/src/PageData.js +12 -2
  65. package/src/RocketJsDemo.js +1 -0
  66. package/src/SocialPreviewPlayground.js +26 -20
  67. package/src/asyncMessage.js +8 -2
  68. package/src/cli/RocketBuild.js +110 -13
  69. package/src/cli/RocketCli.js +11 -1
  70. package/src/cli/RocketInit.js +64 -12
  71. package/src/cli/RocketStart.js +60 -16
  72. package/src/cli/cli.js +10 -1
  73. package/src/component-hydration.js +23 -3
  74. package/src/components/RocketDrawer.js +44 -10
  75. package/src/components/RocketSocialLink.js +26 -2
  76. package/src/config.js +33 -4
  77. package/src/feeds.js +212 -0
  78. package/src/hydration/hydrationLoader.js +80 -48
  79. package/src/icons.js +5 -0
  80. package/src/layouts/atlas/atlasBlog.css +143 -0
  81. package/src/layouts/atlas/atlasBlogLayout.js +234 -0
  82. package/src/layouts/atlas/atlasDocLayout.js +10 -1
  83. package/src/layouts/atlas/atlasHeroLayout.js +10 -6
  84. package/src/layouts/atlas/atlasNotFoundLayout.js +12 -22
  85. package/src/layouts/layout-helper.js +5 -5
  86. package/src/layouts/layout.js +2 -2
  87. package/src/loaded-page-module.js +3 -1
  88. package/src/markdownCompiler.js +2 -2
  89. package/src/markdownHook.js +7 -2
  90. package/src/menu.js +25 -19
  91. package/src/menus/MainMenu.js +10 -4
  92. package/src/{static-page-module-loader.js → page-module-loader.js} +6 -1
  93. package/src/page-pagination.js +2 -2
  94. package/src/page-runtime.js +48 -3
  95. package/src/pages.js +47 -56
  96. package/src/publicAssets.js +2 -2
  97. package/src/route-pattern.js +21 -0
  98. package/src/siteDiscoverability.js +14 -1
  99. package/src/socialPreviewImages.js +80 -14
  100. package/src/standalone-demo-url.js +2 -2
  101. package/src/static-params.js +114 -0
  102. package/src/transform.js +89 -22
  103. package/src/wds-plugin.js +16 -3
  104. package/dist-types/src/development-page-module-loader.d.ts.map +0 -1
  105. package/dist-types/src/static-page-module-loader.d.ts +0 -15
  106. package/dist-types/src/static-page-module-loader.d.ts.map +0 -1
  107. package/src/development-page-module-loader.js +0 -29
package/src/icons.js CHANGED
@@ -131,6 +131,7 @@ export async function resolveRocketIconAsset(requestPath, options = {}) {
131
131
  }
132
132
 
133
133
  const librarySegment = match[1];
134
+ const nameSegment = match[2];
134
135
  const resolver = createIconResolver({
135
136
  layoutIconLibraries: options.layoutIconLibraries,
136
137
  layoutDefaultIconLibrary: options.layoutDefaultIconLibrary,
@@ -144,6 +145,10 @@ export async function resolveRocketIconAsset(requestPath, options = {}) {
144
145
  }
145
146
  const icons = await indexIconLibrary(library, config);
146
147
  for (const [name, svg] of icons) {
148
+ // hashing every icon's SVG is expensive, so narrow by name first
149
+ if (sanitizePathSegment(name) !== nameSegment) {
150
+ continue;
151
+ }
147
152
  const url = iconAssetUrl({ library, name, svg });
148
153
  if (url === requestPath) {
149
154
  return { url, svg, library, name };
@@ -0,0 +1,143 @@
1
+ .atlas-blog {
2
+ min-height: 100vh;
3
+ display: flex;
4
+ flex-direction: column;
5
+ color: var(--docs-text-color);
6
+ background: var(--docs-surface-color);
7
+ }
8
+
9
+ .atlas-blog-header {
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ gap: 1.5rem;
14
+ flex-wrap: wrap;
15
+ padding: 0.75rem clamp(1rem, 4vw, 2.5rem);
16
+ border-bottom: 1px solid var(--docs-line-color);
17
+ }
18
+
19
+ .atlas-blog-content {
20
+ width: 100%;
21
+ max-width: 760px;
22
+ margin: 0 auto;
23
+ padding: clamp(1.5rem, 4vw, 3rem) 1rem 4rem;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ .atlas-blog-byline {
28
+ display: flex;
29
+ align-items: center;
30
+ flex-wrap: wrap;
31
+ gap: 0.5rem 1rem;
32
+ margin-block: 0.5rem 2rem;
33
+ color: var(--docs-muted-color);
34
+ font-size: 0.9rem;
35
+ }
36
+
37
+ .atlas-blog-authors::before {
38
+ content: '·';
39
+ margin-right: 1rem;
40
+ }
41
+
42
+ .atlas-blog-tags {
43
+ display: flex;
44
+ flex-wrap: wrap;
45
+ gap: 0.5rem;
46
+ list-style: none;
47
+ margin: 0;
48
+ padding: 0;
49
+ }
50
+
51
+ .atlas-blog-tags a,
52
+ .atlas-blog-tags span {
53
+ display: inline-block;
54
+ padding: 0.1rem 0.6rem;
55
+ border: 1px solid var(--docs-line-color);
56
+ border-radius: 999px;
57
+ background: var(--docs-surface-soft-color);
58
+ color: var(--docs-muted-color);
59
+ font-size: 0.8rem;
60
+ text-decoration: none;
61
+ }
62
+
63
+ .atlas-blog-tags a:hover {
64
+ border-color: var(--primary-color);
65
+ color: var(--primary-color);
66
+ }
67
+
68
+ .atlas-blog-intro {
69
+ margin-bottom: 2rem;
70
+ }
71
+
72
+ .atlas-blog-feed-link {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ gap: 0.4rem;
76
+ color: var(--docs-muted-color);
77
+ text-decoration: none;
78
+ }
79
+
80
+ .atlas-blog-feed-link:hover {
81
+ color: var(--primary-color);
82
+ }
83
+
84
+ .atlas-blog-post-list {
85
+ list-style: none;
86
+ margin: 0;
87
+ padding: 0;
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 1.5rem;
91
+ }
92
+
93
+ .atlas-blog-post-card {
94
+ padding: 1.25rem 1.5rem;
95
+ border: 1px solid var(--docs-line-color);
96
+ border-radius: 12px;
97
+ background: var(--docs-surface-soft-color);
98
+ }
99
+
100
+ .atlas-blog-post-card h2 {
101
+ margin: 0 0 0.25rem;
102
+ font-size: 1.25rem;
103
+ }
104
+
105
+ .atlas-blog-post-card h2 a {
106
+ color: var(--docs-text-dark-color);
107
+ text-decoration: none;
108
+ }
109
+
110
+ .atlas-blog-post-card h2 a:hover {
111
+ color: var(--primary-color);
112
+ }
113
+
114
+ .atlas-blog-post-card time {
115
+ color: var(--docs-muted-color);
116
+ font-size: 0.85rem;
117
+ }
118
+
119
+ .atlas-blog-post-card p {
120
+ margin: 0.5rem 0 0.75rem;
121
+ }
122
+
123
+ .atlas-blog-empty {
124
+ color: var(--docs-muted-color);
125
+ }
126
+
127
+ .atlas-blog-pagination {
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: space-between;
131
+ gap: 1rem;
132
+ margin-top: 2.5rem;
133
+ color: var(--docs-muted-color);
134
+ }
135
+
136
+ .atlas-blog-pagination a {
137
+ color: var(--link-color);
138
+ text-decoration: none;
139
+ }
140
+
141
+ .atlas-blog-pagination a:hover {
142
+ text-decoration: underline;
143
+ }
@@ -0,0 +1,234 @@
1
+ import { html } from 'lit';
2
+ import { document } from '../layout-helper.js';
3
+ import { resolve } from '../../resolve.js';
4
+ import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
5
+ import { addBootstrapIconLibrary } from '../layout.js';
6
+ import { rocketDemoComponents } from '../../components.js';
7
+ import {
8
+ renderHeaderLogo,
9
+ renderHeaderNavLinks,
10
+ renderSocials,
11
+ renderStylesheets,
12
+ } from './atlasDocLayout.js';
13
+
14
+ /** @typedef {import('@rocket/js/types.js').BlogData} BlogData */
15
+ /** @typedef {import('@rocket/js/types.js').PageCollectionEntry} PageCollectionEntry */
16
+
17
+ /** @type {import('@rocket/js/types.js').Components} */
18
+ export const atlasBlogComponents = {
19
+ 'rocket-social-link': {
20
+ file: './components/RocketSocialLink.js',
21
+ className: 'RocketSocialLink',
22
+ loading: 'server',
23
+ },
24
+ ...rocketDemoComponents,
25
+ ...webAwesomeComponents,
26
+ };
27
+
28
+ /**
29
+ * @param {string} date date-only ISO string from Page Metadata
30
+ * @param {string | undefined} language
31
+ */
32
+ export function formatBlogDate(date, language) {
33
+ const parsed = new Date(`${date}T00:00:00Z`);
34
+ if (Number.isNaN(parsed.getTime())) {
35
+ return date;
36
+ }
37
+ return new Intl.DateTimeFormat(language || 'en', {
38
+ dateStyle: 'long',
39
+ timeZone: 'UTC',
40
+ }).format(parsed);
41
+ }
42
+
43
+ /**
44
+ * @param {string[] | undefined} tags
45
+ * @param {BlogData} data
46
+ */
47
+ export function renderBlogTags(tags, data) {
48
+ if (!tags || tags.length === 0) {
49
+ return '';
50
+ }
51
+ return html`
52
+ <ul class="atlas-blog-tags">
53
+ ${tags.map(
54
+ tag => html`
55
+ <li>
56
+ ${data.tagPathPrefix
57
+ ? html`<a href=${blogTagPath(data.tagPathPrefix, tag)} rel="tag">${tag}</a>`
58
+ : html`<span>${tag}</span>`}
59
+ </li>
60
+ `,
61
+ )}
62
+ </ul>
63
+ `;
64
+ }
65
+
66
+ /**
67
+ * @param {string} tagPathPrefix
68
+ * @param {string} tag
69
+ */
70
+ export function blogTagPath(tagPathPrefix, tag) {
71
+ const prefix = tagPathPrefix.endsWith('/') ? tagPathPrefix : `${tagPathPrefix}/`;
72
+ return `${prefix}${tag}/`;
73
+ }
74
+
75
+ /**
76
+ * @param {import('@rocket/js/types.js').PageMetadata} metadata
77
+ * @param {BlogData} data
78
+ * @param {string | undefined} language
79
+ */
80
+ export function renderBlogByline(metadata, data, language) {
81
+ const authors = metadata.authors || [];
82
+ if (!metadata.date && authors.length === 0 && !(metadata.tags || []).length) {
83
+ return '';
84
+ }
85
+ return html`
86
+ <div class="atlas-blog-byline">
87
+ ${metadata.date
88
+ ? html`<time datetime=${metadata.date}>${formatBlogDate(metadata.date, language)}</time>`
89
+ : ''}
90
+ ${authors.length ? html`<span class="atlas-blog-authors">${authors.join(', ')}</span>` : ''}
91
+ ${renderBlogTags(metadata.tags, data)}
92
+ </div>
93
+ `;
94
+ }
95
+
96
+ /**
97
+ * @param {PageCollectionEntry[]} items
98
+ * @param {BlogData} data
99
+ * @param {string | undefined} language
100
+ */
101
+ export function renderBlogPostList(items, data, language) {
102
+ if (items.length === 0) {
103
+ return html`<p class="atlas-blog-empty">No posts yet.</p>`;
104
+ }
105
+ return html`
106
+ <ol class="atlas-blog-post-list" reversed>
107
+ ${items.map(
108
+ item => html`
109
+ <li class="atlas-blog-post-card">
110
+ <h2><a href=${item.url}>${item.metadata.title}</a></h2>
111
+ ${item.metadata.date
112
+ ? html`<time datetime=${item.metadata.date}
113
+ >${formatBlogDate(item.metadata.date, language)}</time
114
+ >`
115
+ : ''}
116
+ ${item.metadata.description ? html`<p>${item.metadata.description}</p>` : ''}
117
+ ${renderBlogTags(item.metadata.tags, data)}
118
+ </li>
119
+ `,
120
+ )}
121
+ </ol>
122
+ `;
123
+ }
124
+
125
+ /**
126
+ * @param {import('@rocket/js/types.js').PagePagination | undefined} pagination
127
+ */
128
+ export function renderBlogPagination(pagination) {
129
+ if (!pagination || pagination.totalPages <= 1) {
130
+ return '';
131
+ }
132
+ return html`
133
+ <nav class="atlas-blog-pagination" aria-label="Blog pages">
134
+ ${pagination.previousPath
135
+ ? html`<a href=${pagination.previousPath} rel="prev">Newer posts</a>`
136
+ : html`<span></span>`}
137
+ <span>Page ${pagination.currentPage} of ${pagination.totalPages}</span>
138
+ ${pagination.nextPath
139
+ ? html`<a href=${pagination.nextPath} rel="next">Older posts</a>`
140
+ : html`<span></span>`}
141
+ </nav>
142
+ `;
143
+ }
144
+
145
+ /**
146
+ * Shared Atlas blog document chrome for post and index layouts.
147
+ *
148
+ * @param {import('../../PageData.js').PageData} pageData
149
+ * @param {BlogData} data
150
+ * @param {unknown} mainContent
151
+ */
152
+ function atlasBlogDocument(pageData, data, mainContent) {
153
+ addBootstrapIconLibrary(pageData);
154
+ const cssPath = resolve('@rocket/js/docs/assets/prism-one-light.css', import.meta);
155
+ const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
156
+ const headContent = data.headContent?.({ pageData });
157
+
158
+ return document(
159
+ pageData,
160
+ html`
161
+ <div class="atlas-blog">
162
+ <header class="atlas-header atlas-blog-header">
163
+ <a href=${data.headerData.homeLink} class="logo-header">
164
+ ${renderHeaderLogo(data.headerData.logo, siteName)}
165
+ </a>
166
+ <nav class="atlas-header-links" aria-label="Primary navigation">
167
+ ${renderHeaderNavLinks(data.headerData.navLinks)}
168
+ ${renderSocials(data.headerData.socials, siteName)}
169
+ </nav>
170
+ </header>
171
+ <main id="content" class="atlas-blog-content">${mainContent}</main>
172
+ </div>
173
+ `,
174
+ {
175
+ menu: false,
176
+ headContent: html`
177
+ <link rel="stylesheet" href=${cssPath} />
178
+ <link
179
+ rel="stylesheet"
180
+ href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
181
+ />
182
+ <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasDoc.css', import.meta)}" />
183
+ <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasBlog.css', import.meta)}" />
184
+ ${data.feedPath
185
+ ? html`<link
186
+ rel="alternate"
187
+ type="application/atom+xml"
188
+ href=${data.feedPath}
189
+ title=${siteName}
190
+ />`
191
+ : ''}
192
+ ${renderStylesheets(data.stylesheets)} ${headContent}
193
+ `,
194
+ },
195
+ );
196
+ }
197
+
198
+ /** @type {import('@rocket/js/types.js').Layout<BlogData>} */
199
+ export const atlasPostLayout = (pageData, data) => {
200
+ const language = pageData.siteHeadMetadata?.language;
201
+ return atlasBlogDocument(
202
+ pageData,
203
+ data,
204
+ html`
205
+ <article class="atlas-blog-post">
206
+ ${renderBlogByline(pageData.metadata, data, language)} ${pageData.content}
207
+ </article>
208
+ `,
209
+ );
210
+ };
211
+
212
+ /** @type {import('@rocket/js/types.js').Layout<BlogData>} */
213
+ export const atlasBlogIndexLayout = (pageData, data) => {
214
+ const language = pageData.siteHeadMetadata?.language;
215
+ const items = pageData.pagination?.items || [];
216
+ return atlasBlogDocument(
217
+ pageData,
218
+ data,
219
+ html`
220
+ <section class="atlas-blog-index">
221
+ <div class="atlas-blog-intro">
222
+ ${pageData.content}
223
+ ${data.feedPath
224
+ ? html`<a class="atlas-blog-feed-link" href=${data.feedPath}>
225
+ <rocket-icon library="bootstrap" name="rss" aria-hidden="true"></rocket-icon>
226
+ Feed
227
+ </a>`
228
+ : ''}
229
+ </div>
230
+ ${renderBlogPostList(items, data, language)} ${renderBlogPagination(pageData.pagination)}
231
+ </section>
232
+ `,
233
+ );
234
+ };
@@ -119,6 +119,13 @@ export function renderHeaderNav(links = []) {
119
119
  `;
120
120
  }
121
121
 
122
+ /**
123
+ * @param {string[] | undefined} stylesheets
124
+ */
125
+ export function renderStylesheets(stylesheets = []) {
126
+ return stylesheets.map(href => html`<link rel="stylesheet" href=${href} />`);
127
+ }
128
+
122
129
  /**
123
130
  * @param {import('@rocket/js/types.js').PageMetadataCustomValue | undefined} customValue
124
131
  * @returns {import('@rocket/js/types.js').AtlasDocAsideTip | false | undefined}
@@ -203,6 +210,7 @@ export const atlasDocLayout = (pageData, data) => {
203
210
  const serializedToc = serializeToc(pageData.toc);
204
211
  const navigationIconServerBudget =
205
212
  data.navigationIconServerBudget ?? DEFAULT_ATLAS_DOC_NAVIGATION_ICON_SERVER_BUDGET;
213
+ const headContent = data.headContent?.({ pageData });
206
214
 
207
215
  return document(
208
216
  pageData,
@@ -262,13 +270,14 @@ export const atlasDocLayout = (pageData, data) => {
262
270
  `,
263
271
  {
264
272
  menu: false,
265
- headerContent: html`
273
+ headContent: html`
266
274
  <link rel="stylesheet" href=${cssPath} />
267
275
  <link
268
276
  rel="stylesheet"
269
277
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
270
278
  />
271
279
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasDoc.css', import.meta)}" />
280
+ ${renderStylesheets(data.stylesheets)} ${headContent}
272
281
  `,
273
282
  },
274
283
  );
@@ -3,7 +3,12 @@ import { document } from '../layout-helper.js';
3
3
  import { resolve } from '../../resolve.js';
4
4
  import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
5
5
  import { addBootstrapIconLibrary } from '../layout.js';
6
- import { renderHeaderLogo, renderHeaderNav, renderSocials } from './atlasDocLayout.js';
6
+ import {
7
+ renderHeaderLogo,
8
+ renderHeaderNav,
9
+ renderSocials,
10
+ renderStylesheets,
11
+ } from './atlasDocLayout.js';
7
12
  import { rocketDemoComponents } from '../../components.js';
8
13
 
9
14
  /** @type {import('@rocket/js/types.js').Components} */
@@ -257,10 +262,8 @@ export const atlasHeroLayout = (pageData, data) => {
257
262
  const heroEyebrow = hero.eyebrow ?? hero.sloganTop;
258
263
  const heroTitle = hero.title ?? hero.sloganBottom ?? pageData.title;
259
264
  const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
260
- const navLinks = data.headerData.navLinks ?? [
261
- { text: 'Docs', href: hero.documentationLink },
262
- { text: 'Examples', href: '/examples' },
263
- ];
265
+ const navLinks = data.headerData.navLinks ?? [{ text: 'Docs', href: hero.documentationLink }];
266
+ const headContent = data.headContent?.({ pageData });
264
267
 
265
268
  return document(
266
269
  pageData,
@@ -324,13 +327,14 @@ export const atlasHeroLayout = (pageData, data) => {
324
327
  `,
325
328
  {
326
329
  menu: false,
327
- headerContent: html`
330
+ headContent: html`
328
331
  <link rel="stylesheet" href=${cssPath} />
329
332
  <link
330
333
  rel="stylesheet"
331
334
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
332
335
  />
333
336
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasHero.css', import.meta)}" />
337
+ ${renderStylesheets(data.stylesheets)} ${headContent}
334
338
  `,
335
339
  },
336
340
  );
@@ -1,26 +1,11 @@
1
1
  import { html } from 'lit';
2
2
  import { document } from '../layout-helper.js';
3
3
  import { resolve } from '../../resolve.js';
4
+ import { renderHeaderLogo, renderStylesheets } from './atlasDocLayout.js';
4
5
 
5
6
  /** @type {import('@rocket/js/types.js').Components} */
6
7
  export const atlasNotFoundComponents = {};
7
8
 
8
- /**
9
- * Renders the header logo.
10
- * @param {Array<string>} logo
11
- */
12
- function renderHeaderLogo(logo) {
13
- if (logo.length === 1) {
14
- return html`<img src=${logo[0]} alt="Logo" width="130" />`;
15
- } else if (logo.length === 2) {
16
- return html`
17
- <img src=${logo[0]} alt="Logo" width="36" />
18
- <img src=${logo[1]} alt="Logo Text" width="88" />
19
- `;
20
- }
21
- return html`<b>Header logo should only contain one or two images.</b>`;
22
- }
23
-
24
9
  /**
25
10
  * @param {Array<{url: string, name: string, label?: string}>} socials
26
11
  */
@@ -32,6 +17,8 @@ function renderSocialLinks(socials) {
32
17
  export const atlasNotFoundLayout = (pageData, data) => {
33
18
  const homeLink = data.headerData.homeLink || '/';
34
19
  const rocketLogo = data.headerData.logo[0];
20
+ const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
21
+ const headContent = data.headContent?.({ pageData });
35
22
 
36
23
  return document(
37
24
  pageData,
@@ -39,7 +26,7 @@ export const atlasNotFoundLayout = (pageData, data) => {
39
26
  <div class="atlas-not-found">
40
27
  <header class="atlas-not-found-header">
41
28
  <a href=${homeLink} class="atlas-not-found-logo">
42
- ${renderHeaderLogo(data.headerData.logo)}
29
+ ${renderHeaderLogo(data.headerData.logo, siteName)}
43
30
  </a>
44
31
  <nav class="atlas-not-found-links" aria-label="Project links">
45
32
  ${renderSocialLinks(data.headerData.socials)}
@@ -53,17 +40,20 @@ export const atlasNotFoundLayout = (pageData, data) => {
53
40
  <div class="atlas-not-found-stars"></div>
54
41
  <div class="atlas-not-found-orbit"></div>
55
42
  <p class="atlas-not-found-code">404</p>
56
- <img class="atlas-not-found-rocket" src=${rocketLogo} alt="" />
43
+ ${rocketLogo
44
+ ? html`<img class="atlas-not-found-rocket" src=${rocketLogo} alt="" />`
45
+ : html``}
57
46
  </div>
58
47
  </main>
59
48
  </div>
60
49
  `,
61
50
  {
62
51
  menu: false,
63
- headerContent: html`<link
64
- rel="stylesheet"
65
- href="${resolve('@rocket/js/layouts/atlasNotFound.css', import.meta)}"
66
- />`,
52
+ headContent: html`<link
53
+ rel="stylesheet"
54
+ href="${resolve('@rocket/js/layouts/atlasNotFound.css', import.meta)}"
55
+ />
56
+ ${renderStylesheets(data.stylesheets)} ${headContent}`,
67
57
  },
68
58
  );
69
59
  };
@@ -11,7 +11,7 @@ import { defaultHtmlMenu } from '../menu.js';
11
11
  * @param {Object} [options]
12
12
  * @param {LitHTML} [options.title]
13
13
  * @param {'html' | false} [options.menu] render a default menu. set to false if you want your own
14
- * @param {LitHTML} [options.headerContent]
14
+ * @param {LitHTML} [options.headContent]
15
15
  */
16
16
  export function document(
17
17
  data,
@@ -19,7 +19,7 @@ export function document(
19
19
  {
20
20
  title = data.siteHeadMetadata?.title ?? data.title,
21
21
  menu = 'html',
22
- headerContent = undefined,
22
+ headContent = undefined,
23
23
  } = {},
24
24
  ) {
25
25
  const siteHeadMetadata = data.siteHeadMetadata;
@@ -40,12 +40,12 @@ export function document(
40
40
  }
41
41
  </style>`
42
42
  : ''}
43
- ${data.clientCode} ${headerContent}
43
+ ${data.clientCode} ${headContent}
44
44
  </head>
45
45
  <body>
46
46
  ${menu
47
- ? html`<div id="menu">${defaultHtmlMenu(data.pageTree)}</div>
48
- <div id="content">${content}</div>`
47
+ ? html`<nav id="menu" aria-label="Site">${defaultHtmlMenu(data.pageTree)}</nav>
48
+ <main id="content">${content}</main>`
49
49
  : content}
50
50
  </body>
51
51
  </html>`;
@@ -9,7 +9,7 @@ export const layout = data => {
9
9
  addBootstrapIconLibrary(data);
10
10
  return document(data, data.content, {
11
11
  menu: 'html',
12
- headerContent: html`
12
+ headContent: html`
13
13
  <link
14
14
  rel="stylesheet"
15
15
  href="${resolve('@rocket/js/docs/assets/prism-one-light.css', import.meta)}"
@@ -27,7 +27,7 @@ export const singleDemoLayout = data => {
27
27
  addBootstrapIconLibrary(data);
28
28
  return document(data, data.content, {
29
29
  menu: false,
30
- headerContent: html`
30
+ headContent: html`
31
31
  <link
32
32
  rel="stylesheet"
33
33
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
@@ -92,6 +92,8 @@ function normalizeMarkdownPageContent(rawModule, { parseComponents }) {
92
92
  */
93
93
  function hasBrowserLoadedComponents(components) {
94
94
  return Object.values(components).some(
95
- component => component.loading === 'client' || component.loading.startsWith('hydrate:'),
95
+ component =>
96
+ component.loading === 'client' ||
97
+ (typeof component.loading === 'string' && component.loading.startsWith('hydrate:')),
96
98
  );
97
99
  }
@@ -5,7 +5,7 @@ import rehypeRaw from 'rehype-raw';
5
5
  import { unified } from 'unified';
6
6
  import { visit } from 'unist-util-visit';
7
7
  import { extractMdCode } from './extractCode.js';
8
- import { mdToJs, mdToJsSingleDemo } from './transform.js';
8
+ import { isDemoCodeNode, mdToJs, mdToJsSingleDemo } from './transform.js';
9
9
  import { init, parse as parseExports } from 'es-module-lexer';
10
10
  import ts from 'typescript';
11
11
  import { parseRequestDemoMetadata } from './requestDemoMetadata.js';
@@ -235,7 +235,7 @@ function extractDemoNames() {
235
235
  /** @type {import('unist-util-visit').Visitor} */
236
236
  const visitor = _node => {
237
237
  const node = /** @type {import('mdast').Code} */ (_node);
238
- if (node.lang === 'js' && node.meta === 'demo' && typeof node.value === 'string') {
238
+ if (isDemoCodeNode(node) && typeof node.value === 'string') {
239
239
  for (const exported of parseExports(node.value)[1]) {
240
240
  demoNames.push(exported.ln || exported.n);
241
241
  }
@@ -102,7 +102,11 @@ export async function resolve(specifier, context, nextResolve) {
102
102
  },
103
103
  };
104
104
  if (match[0] === 'Setup' && isLocalFile(url)) {
105
- await sendPort.sendAndWait({
105
+ // Fire-and-forget: awaiting a reply here can deadlock. The main thread
106
+ // blocks in Atomics.wait during e.g. a nested module.register() call, so
107
+ // it may be unable to answer until this resolve returns. postMessage
108
+ // delivery is ordered, which is all the dependency tracking needs.
109
+ sendPort.postMessage({
106
110
  parent: getPath(/** @type {string} */ (context.parentURL)),
107
111
  url: getPath(resolve.url),
108
112
  });
@@ -116,7 +120,8 @@ export async function resolve(specifier, context, nextResolve) {
116
120
  ...resolve.importAttributes,
117
121
  type: 'cache' + lastReload.toString(36),
118
122
  };
119
- await sendPort.sendAndWait({
123
+ // Fire-and-forget, see above.
124
+ sendPort.postMessage({
120
125
  parent: getPath(/** @type {string} */ (context.parentURL)),
121
126
  url: getPath(resolve.url),
122
127
  });