@rocket/js 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.
- package/README.md +8 -2
- package/dist-types/exports/layouts/atlasBlog.d.ts +2 -0
- package/dist-types/exports/layouts/atlasBlog.d.ts.map +1 -0
- package/dist-types/exports/types/rocket.d.ts +60 -2
- package/dist-types/exports/types/rocket.d.ts.map +1 -1
- package/dist-types/exports/types.d.ts +1 -1
- package/dist-types/exports/types.d.ts.map +1 -1
- package/dist-types/src/PageData.d.ts +3 -1
- package/dist-types/src/PageData.d.ts.map +1 -1
- package/dist-types/src/RocketJsDemo.d.ts.map +1 -1
- package/dist-types/src/SocialPreviewPlayground.d.ts +1 -0
- package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -1
- package/dist-types/src/asyncMessage.d.ts +5 -0
- package/dist-types/src/asyncMessage.d.ts.map +1 -1
- package/dist-types/src/cli/RocketBuild.d.ts.map +1 -1
- package/dist-types/src/cli/RocketCli.d.ts.map +1 -1
- package/dist-types/src/cli/RocketStart.d.ts +6 -1
- package/dist-types/src/cli/RocketStart.d.ts.map +1 -1
- package/dist-types/src/component-hydration.d.ts.map +1 -1
- package/dist-types/src/components/RocketDrawer.d.ts +8 -0
- package/dist-types/src/components/RocketDrawer.d.ts.map +1 -1
- package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -1
- package/dist-types/src/config.d.ts.map +1 -1
- package/dist-types/src/feeds.d.ts +38 -0
- package/dist-types/src/feeds.d.ts.map +1 -0
- package/dist-types/src/hydration/hydrationLoader.d.ts +13 -4
- package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -1
- package/dist-types/src/icons.d.ts.map +1 -1
- package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts +42 -0
- package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts.map +1 -0
- package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -1
- package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -1
- package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -1
- package/dist-types/src/layouts/layout-helper.d.ts +3 -3
- package/dist-types/src/layouts/layout-helper.d.ts.map +1 -1
- package/dist-types/src/markdownHook.d.ts.map +1 -1
- package/dist-types/src/menu.d.ts +8 -3
- package/dist-types/src/menu.d.ts.map +1 -1
- package/dist-types/src/menus/MainMenu.d.ts.map +1 -1
- package/dist-types/src/{development-page-module-loader.d.ts → page-module-loader.d.ts} +7 -2
- package/dist-types/src/page-module-loader.d.ts.map +1 -0
- package/dist-types/src/page-runtime.d.ts +11 -0
- package/dist-types/src/page-runtime.d.ts.map +1 -1
- package/dist-types/src/pages.d.ts.map +1 -1
- package/dist-types/src/route-pattern.d.ts +10 -0
- package/dist-types/src/route-pattern.d.ts.map +1 -0
- package/dist-types/src/siteDiscoverability.d.ts +4 -0
- package/dist-types/src/siteDiscoverability.d.ts.map +1 -1
- package/dist-types/src/socialPreviewImages.d.ts +2 -1
- package/dist-types/src/socialPreviewImages.d.ts.map +1 -1
- package/dist-types/src/static-params.d.ts +33 -0
- package/dist-types/src/static-params.d.ts.map +1 -0
- package/dist-types/src/transform.d.ts +4 -0
- package/dist-types/src/transform.d.ts.map +1 -1
- package/dist-types/src/wds-plugin.d.ts.map +1 -1
- package/exports/layouts/atlasBlog.js +11 -0
- package/exports/types/rocket.test.ts +26 -0
- package/exports/types/rocket.ts +70 -2
- package/exports/types.ts +7 -0
- package/package.json +15 -31
- package/src/PageData.js +12 -2
- package/src/RocketJsDemo.js +1 -0
- package/src/SocialPreviewPlayground.js +26 -20
- package/src/asyncMessage.js +8 -2
- package/src/cli/RocketBuild.js +110 -13
- package/src/cli/RocketCli.js +11 -1
- package/src/cli/RocketStart.js +60 -16
- package/src/cli/cli.js +10 -1
- package/src/component-hydration.js +23 -3
- package/src/components/RocketDrawer.js +44 -10
- package/src/components/RocketSocialLink.js +26 -2
- package/src/config.js +33 -4
- package/src/feeds.js +212 -0
- package/src/hydration/hydrationLoader.js +80 -48
- package/src/icons.js +5 -0
- package/src/layouts/atlas/atlasBlog.css +143 -0
- package/src/layouts/atlas/atlasBlogLayout.js +234 -0
- package/src/layouts/atlas/atlasDocLayout.js +3 -2
- package/src/layouts/atlas/atlasHeroLayout.js +4 -6
- package/src/layouts/atlas/atlasNotFoundLayout.js +9 -21
- package/src/layouts/layout-helper.js +5 -5
- package/src/layouts/layout.js +2 -2
- package/src/loaded-page-module.js +3 -1
- package/src/markdownCompiler.js +2 -2
- package/src/markdownHook.js +7 -2
- package/src/menu.js +25 -19
- package/src/menus/MainMenu.js +10 -4
- package/src/{static-page-module-loader.js → page-module-loader.js} +6 -1
- package/src/page-pagination.js +2 -2
- package/src/page-runtime.js +48 -3
- package/src/pages.js +47 -56
- package/src/publicAssets.js +2 -2
- package/src/route-pattern.js +21 -0
- package/src/siteDiscoverability.js +14 -1
- package/src/socialPreviewImages.js +80 -14
- package/src/standalone-demo-url.js +2 -2
- package/src/static-params.js +114 -0
- package/src/transform.js +89 -22
- package/src/wds-plugin.js +16 -3
- package/dist-types/src/development-page-module-loader.d.ts.map +0 -1
- package/dist-types/src/static-page-module-loader.d.ts +0 -15
- package/dist-types/src/static-page-module-loader.d.ts.map +0 -1
- package/src/development-page-module-loader.js +0 -29
package/src/feeds.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { PageData } from './PageData.js';
|
|
2
|
+
import { normalizeDocumentPath } from './standalone-demo-url.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('@rocket/js/types.js').Page} Page */
|
|
5
|
+
/** @typedef {import('@rocket/js/types.js').PageCollectionEntry} PageCollectionEntry */
|
|
6
|
+
/** @typedef {import('@rocket/js/types.js').PageFeedConfig} PageFeedConfig */
|
|
7
|
+
/** @typedef {import('@rocket/js/types.js').PageFeedDeclaration} PageFeedDeclaration */
|
|
8
|
+
/** @typedef {import('@rocket/js/types.js').PageRegistry} PageRegistry */
|
|
9
|
+
|
|
10
|
+
// Atom requires a feed-level <updated> value even when no entry has dates yet.
|
|
11
|
+
const FALLBACK_UPDATED = '1970-01-01';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Page} page
|
|
15
|
+
*/
|
|
16
|
+
export function hasPageFeed(page) {
|
|
17
|
+
return page.file.endsWith('.js') && page.module.feed !== undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} pagePath
|
|
22
|
+
*/
|
|
23
|
+
export function pageFeedPath(pagePath) {
|
|
24
|
+
return `${normalizeDocumentPath(pagePath)}feed.xml`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} pathname
|
|
29
|
+
* @param {PageRegistry} pages
|
|
30
|
+
* @returns {{ page: Page; routePath: string } | null}
|
|
31
|
+
*/
|
|
32
|
+
export function matchPageFeedPath(pathname, pages) {
|
|
33
|
+
for (const [routePath, page] of pages) {
|
|
34
|
+
if (!hasPageFeed(page) || hasPathParameter(routePath)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (pageFeedPath(routePath) === pathname) {
|
|
38
|
+
return { page, routePath };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {{
|
|
46
|
+
* pages: PageRegistry;
|
|
47
|
+
* page: Page;
|
|
48
|
+
* pagePath: string;
|
|
49
|
+
* siteOrigin: string;
|
|
50
|
+
* }} options
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function createPageFeed({ pages, page, pagePath, siteOrigin }) {
|
|
54
|
+
if (hasPathParameter(pagePath)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Page Feed for ${page.file} cannot use the parameterized path ${pagePath}. ` +
|
|
57
|
+
`Page Feeds need a concrete owning Page path.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const config = resolvePageFeedConfig({ pages, page, pagePath });
|
|
61
|
+
const feedUrl = absoluteUrl(siteOrigin, pageFeedPath(pagePath));
|
|
62
|
+
const pageUrl = absoluteUrl(siteOrigin, normalizeDocumentPath(pagePath));
|
|
63
|
+
const entries =
|
|
64
|
+
config.limit === undefined ? config.collection : config.collection.slice(0, config.limit);
|
|
65
|
+
|
|
66
|
+
const lines = [
|
|
67
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
68
|
+
'<feed xmlns="http://www.w3.org/2005/Atom">',
|
|
69
|
+
` <title>${escapeXml(config.title)}</title>`,
|
|
70
|
+
];
|
|
71
|
+
if (config.description) {
|
|
72
|
+
lines.push(` <subtitle>${escapeXml(config.description)}</subtitle>`);
|
|
73
|
+
}
|
|
74
|
+
lines.push(
|
|
75
|
+
` <id>${escapeXml(pageUrl)}</id>`,
|
|
76
|
+
` <link rel="self" type="application/atom+xml" href="${escapeXml(feedUrl)}"/>`,
|
|
77
|
+
` <link rel="alternate" type="text/html" href="${escapeXml(pageUrl)}"/>`,
|
|
78
|
+
` <updated>${atomDate(newestEntryDate(entries))}</updated>`,
|
|
79
|
+
);
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
lines.push(...feedEntryLines(entry, siteOrigin));
|
|
82
|
+
}
|
|
83
|
+
lines.push('</feed>', '');
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {PageCollectionEntry} entry
|
|
89
|
+
* @param {string} siteOrigin
|
|
90
|
+
*/
|
|
91
|
+
function feedEntryLines(entry, siteOrigin) {
|
|
92
|
+
const entryUrl = absoluteUrl(siteOrigin, normalizeDocumentPath(entry.path));
|
|
93
|
+
const lines = [
|
|
94
|
+
' <entry>',
|
|
95
|
+
` <title>${escapeXml(entry.metadata.title)}</title>`,
|
|
96
|
+
` <id>${escapeXml(entryUrl)}</id>`,
|
|
97
|
+
` <link rel="alternate" type="text/html" href="${escapeXml(entryUrl)}"/>`,
|
|
98
|
+
` <updated>${atomDate(entry.metadata.updated || entry.metadata.date || FALLBACK_UPDATED)}</updated>`,
|
|
99
|
+
];
|
|
100
|
+
if (entry.metadata.date) {
|
|
101
|
+
lines.push(` <published>${atomDate(entry.metadata.date)}</published>`);
|
|
102
|
+
}
|
|
103
|
+
if (entry.metadata.description) {
|
|
104
|
+
lines.push(` <summary>${escapeXml(entry.metadata.description)}</summary>`);
|
|
105
|
+
}
|
|
106
|
+
for (const author of entry.metadata.authors || []) {
|
|
107
|
+
lines.push(' <author>', ` <name>${escapeXml(author)}</name>`, ' </author>');
|
|
108
|
+
}
|
|
109
|
+
lines.push(' </entry>');
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{
|
|
115
|
+
* pages: PageRegistry;
|
|
116
|
+
* page: Page;
|
|
117
|
+
* pagePath: string;
|
|
118
|
+
* }} options
|
|
119
|
+
* @returns {PageFeedConfig}
|
|
120
|
+
*/
|
|
121
|
+
function resolvePageFeedConfig({ pages, page, pagePath }) {
|
|
122
|
+
const declaration = /** @type {PageFeedDeclaration} */ (page.module.feed);
|
|
123
|
+
const config =
|
|
124
|
+
typeof declaration === 'function'
|
|
125
|
+
? declaration(new PageData(pages, page.metadata, pagePath))
|
|
126
|
+
: declaration;
|
|
127
|
+
return normalizePageFeedConfig(config, page);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {unknown} config
|
|
132
|
+
* @param {Page} page
|
|
133
|
+
* @returns {PageFeedConfig}
|
|
134
|
+
*/
|
|
135
|
+
function normalizePageFeedConfig(config, page) {
|
|
136
|
+
if (typeof config !== 'object' || config === null) {
|
|
137
|
+
throw new Error(pageFeedConfigError(page));
|
|
138
|
+
}
|
|
139
|
+
const feedConfig = /** @type {Record<string, unknown>} */ (config);
|
|
140
|
+
if (
|
|
141
|
+
typeof feedConfig.title !== 'string' ||
|
|
142
|
+
feedConfig.title.trim() === '' ||
|
|
143
|
+
!Array.isArray(feedConfig.collection) ||
|
|
144
|
+
(feedConfig.description !== undefined && typeof feedConfig.description !== 'string') ||
|
|
145
|
+
(feedConfig.limit !== undefined &&
|
|
146
|
+
(typeof feedConfig.limit !== 'number' ||
|
|
147
|
+
!Number.isInteger(feedConfig.limit) ||
|
|
148
|
+
feedConfig.limit <= 0))
|
|
149
|
+
) {
|
|
150
|
+
throw new Error(pageFeedConfigError(page));
|
|
151
|
+
}
|
|
152
|
+
return /** @type {PageFeedConfig} */ (config);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {Page} page
|
|
157
|
+
*/
|
|
158
|
+
function pageFeedConfigError(page) {
|
|
159
|
+
return (
|
|
160
|
+
`Page Feed for ${page.file} must provide a non-empty title, a collection array, ` +
|
|
161
|
+
`an optional string description, and an optional positive integer limit.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {PageCollectionEntry[]} entries
|
|
167
|
+
*/
|
|
168
|
+
function newestEntryDate(entries) {
|
|
169
|
+
let newest = '';
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const date = entry.metadata.updated || entry.metadata.date || '';
|
|
172
|
+
if (date > newest) {
|
|
173
|
+
newest = date;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return newest || FALLBACK_UPDATED;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Page Metadata dates are date-only ISO strings; Atom requires full timestamps.
|
|
181
|
+
* @param {string} date
|
|
182
|
+
*/
|
|
183
|
+
function atomDate(date) {
|
|
184
|
+
return `${date}T00:00:00Z`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {string} siteOrigin
|
|
189
|
+
* @param {string} pagePath
|
|
190
|
+
*/
|
|
191
|
+
function absoluteUrl(siteOrigin, pagePath) {
|
|
192
|
+
return new URL(pagePath, `${siteOrigin}/`).href;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {string} pagePath
|
|
197
|
+
*/
|
|
198
|
+
function hasPathParameter(pagePath) {
|
|
199
|
+
return /(^|\/):[^/]+/.test(pagePath);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} value
|
|
204
|
+
*/
|
|
205
|
+
function escapeXml(value) {
|
|
206
|
+
return value
|
|
207
|
+
.replaceAll('&', '&')
|
|
208
|
+
.replaceAll('"', '"')
|
|
209
|
+
.replaceAll("'", ''')
|
|
210
|
+
.replaceAll('<', '<')
|
|
211
|
+
.replaceAll('>', '>');
|
|
212
|
+
}
|
|
@@ -30,7 +30,7 @@ export class HydrationLoader {
|
|
|
30
30
|
/**
|
|
31
31
|
* @param {number} index
|
|
32
32
|
* @param {object} options
|
|
33
|
-
* @param {
|
|
33
|
+
* @param {EventTarget[]} [options.composedPath]
|
|
34
34
|
* @param {Event} [options.event]
|
|
35
35
|
* @returns {Promise<{ needsCleanup: boolean }>}
|
|
36
36
|
*/
|
|
@@ -124,11 +124,14 @@ export class HydrationLoader {
|
|
|
124
124
|
* @param {String} param1.strategyType
|
|
125
125
|
* @param {String} [param1.strategyOptions]
|
|
126
126
|
* @param {Boolean} param1.resolveAble
|
|
127
|
-
* @param {
|
|
127
|
+
* @param {{ composedPath?: EventTarget[]; event?: Event }} [options]
|
|
128
128
|
*/
|
|
129
129
|
async setResolveAbleOn({ target, strategyType, strategyOptions, resolveAble }, options = {}) {
|
|
130
130
|
let needsCleanup = false;
|
|
131
|
-
|
|
131
|
+
// Events fired on light-DOM descendants should still hydrate the host.
|
|
132
|
+
const foundIndex = this.elements.findIndex(
|
|
133
|
+
el => !el.deleteMe && (el.node === target || options.composedPath?.includes(el.node)),
|
|
134
|
+
);
|
|
132
135
|
if (foundIndex !== -1) {
|
|
133
136
|
const element = this.elements[foundIndex];
|
|
134
137
|
|
|
@@ -162,55 +165,61 @@ export class HydrationLoader {
|
|
|
162
165
|
|
|
163
166
|
let needsCleanup = false;
|
|
164
167
|
for (let i = 0; i < this.elements.length; i += 1) {
|
|
165
|
-
|
|
168
|
+
const result = await this.checkElement(i);
|
|
169
|
+
needsCleanup = needsCleanup || result.needsCleanup;
|
|
166
170
|
}
|
|
167
171
|
if (needsCleanup) {
|
|
168
|
-
|
|
172
|
+
this.cleanup();
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
await this.handleOnVisible();
|
|
172
176
|
await this.handleOnClick();
|
|
173
177
|
await this.handleOnFocus();
|
|
178
|
+
await this.handleOnHover();
|
|
174
179
|
await this.handleOnMedia();
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
async handleOnMedia() {
|
|
183
|
+
// One listener per media query; it looks up the current elements when it
|
|
184
|
+
// fires, so hydrated elements never keep stale per-element listeners alive.
|
|
185
|
+
/** @type {Set<string>} */
|
|
186
|
+
const queries = new Set();
|
|
178
187
|
for (const el of this.elements) {
|
|
179
188
|
for (const strategy of el.strategies) {
|
|
180
189
|
if (strategy.type === 'onMedia' && strategy.options) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
queries.add(strategy.options);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
184
194
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
strategyOptions: strategy.options,
|
|
199
|
-
resolveAble: false,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
});
|
|
195
|
+
for (const query of queries) {
|
|
196
|
+
if (!this.mediaQueries[query]) {
|
|
197
|
+
const mediaQuery = window.matchMedia(query);
|
|
198
|
+
this.mediaQueries[query] = mediaQuery;
|
|
199
|
+
mediaQuery.addEventListener('change', ev => {
|
|
200
|
+
this.updateOnMediaElements(query, ev.matches);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (this.mediaQueries[query].matches) {
|
|
204
|
+
await this.updateOnMediaElements(query, true);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
203
208
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} query
|
|
211
|
+
* @param {boolean} matches
|
|
212
|
+
*/
|
|
213
|
+
async updateOnMediaElements(query, matches) {
|
|
214
|
+
// iterate over a copy — hydration mutates this.elements
|
|
215
|
+
for (const el of [...this.elements]) {
|
|
216
|
+
if (el.strategies.some(s => s.type === 'onMedia' && s.options === query)) {
|
|
217
|
+
await this.setResolveAbleOn({
|
|
218
|
+
target: el.node,
|
|
219
|
+
strategyType: 'onMedia',
|
|
220
|
+
strategyOptions: query,
|
|
221
|
+
resolveAble: matches,
|
|
222
|
+
});
|
|
214
223
|
}
|
|
215
224
|
}
|
|
216
225
|
}
|
|
@@ -220,9 +229,11 @@ export class HydrationLoader {
|
|
|
220
229
|
if (!ev.target) {
|
|
221
230
|
return;
|
|
222
231
|
}
|
|
232
|
+
// composedPath() only returns the full path during dispatch, so capture it now
|
|
233
|
+
const composedPath = ev.composedPath();
|
|
223
234
|
this.setResolveAbleOn(
|
|
224
235
|
{ target: ev.target, strategyType: 'onClick', resolveAble: true },
|
|
225
|
-
{ composedPath
|
|
236
|
+
{ composedPath, event: ev },
|
|
226
237
|
);
|
|
227
238
|
|
|
228
239
|
// reset the onClick resolveAble if the click did not result in the hydration of the element
|
|
@@ -230,11 +241,10 @@ export class HydrationLoader {
|
|
|
230
241
|
if (!ev.target) {
|
|
231
242
|
return;
|
|
232
243
|
}
|
|
233
|
-
this.setResolveAbleOn(
|
|
234
|
-
target: ev.target,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
});
|
|
244
|
+
this.setResolveAbleOn(
|
|
245
|
+
{ target: ev.target, strategyType: 'onClick', resolveAble: false },
|
|
246
|
+
{ composedPath },
|
|
247
|
+
);
|
|
238
248
|
}, 50);
|
|
239
249
|
});
|
|
240
250
|
}
|
|
@@ -244,9 +254,11 @@ export class HydrationLoader {
|
|
|
244
254
|
if (!ev.target) {
|
|
245
255
|
return;
|
|
246
256
|
}
|
|
257
|
+
// composedPath() only returns the full path during dispatch, so capture it now
|
|
258
|
+
const composedPath = ev.composedPath();
|
|
247
259
|
this.setResolveAbleOn(
|
|
248
260
|
{ target: ev.target, strategyType: 'onFocus', resolveAble: true },
|
|
249
|
-
{ composedPath
|
|
261
|
+
{ composedPath, event: ev },
|
|
250
262
|
);
|
|
251
263
|
|
|
252
264
|
// reset the onFocus resolveAble if the focus did not resulted in the hydration of the element
|
|
@@ -254,15 +266,35 @@ export class HydrationLoader {
|
|
|
254
266
|
if (!ev.target) {
|
|
255
267
|
return;
|
|
256
268
|
}
|
|
257
|
-
this.setResolveAbleOn(
|
|
258
|
-
target: ev.target,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
269
|
+
this.setResolveAbleOn(
|
|
270
|
+
{ target: ev.target, strategyType: 'onFocus', resolveAble: false },
|
|
271
|
+
{ composedPath },
|
|
272
|
+
);
|
|
262
273
|
}, 50);
|
|
263
274
|
});
|
|
264
275
|
}
|
|
265
276
|
|
|
277
|
+
async handleOnHover() {
|
|
278
|
+
document.body.addEventListener('pointerover', ev => {
|
|
279
|
+
if (!ev.target) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.setResolveAbleOn(
|
|
283
|
+
{ target: ev.target, strategyType: 'onHover', resolveAble: true },
|
|
284
|
+
{ composedPath: ev.composedPath() },
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
document.body.addEventListener('pointerout', ev => {
|
|
288
|
+
if (!ev.target) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this.setResolveAbleOn(
|
|
292
|
+
{ target: ev.target, strategyType: 'onHover', resolveAble: false },
|
|
293
|
+
{ composedPath: ev.composedPath() },
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
266
298
|
async handleOnVisible() {
|
|
267
299
|
this.intersectionObserver = new IntersectionObserver(entries => {
|
|
268
300
|
for (const entry of entries) {
|
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
|
+
}
|