@hagicode/hagilight 0.3.2-dev.55.1.1e29eba → 0.3.2-dev.57.1.ee1f17e
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/SEOHead.astro +4 -9
- package/dist/integration.d.ts +33 -0
- package/dist/integration.js +243 -0
- package/dist/rss-config.d.ts +28 -0
- package/dist/rss-config.js +1 -0
- package/dist/rss-middleware.d.ts +2 -0
- package/{rss-middleware.mjs → dist/rss-middleware.js} +2 -3
- package/dist/rss-runtime.d.ts +2 -0
- package/dist/rss-runtime.js +44 -0
- package/package.json +21 -28
- package/rss.[language].xml.ts +1 -1
- package/rss.xml.ts +1 -1
- package/Analytics51LA.astro +0 -17
- package/Copyright.astro +0 -9
- package/Footer.astro +0 -157
- package/GoogleAnalytics.astro +0 -21
- package/PromotoBanner.astro +0 -346
- package/favicon.ico +0 -0
- package/favicon.mjs +0 -76
- package/integration.mjs +0 -226
- package/logo.png +0 -0
- package/promotions.ts +0 -263
- package/promoto-banner.ts +0 -368
- package/related-sites.json +0 -306
- package/rss-footer-context.mjs +0 -51
- package/rss-locales.mjs +0 -40
- package/rss-renderer.mjs +0 -114
- package/rss-runtime.mjs +0 -45
- package/seo-schema.d.ts +0 -21
- package/seo-schema.mjs +0 -24
- package/seo-utils.mjs +0 -264
- package/site-links.ts +0 -487
package/SEOHead.astro
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
composeCanonicalSeoHead,
|
|
4
4
|
resolveSeoMetadata,
|
|
5
5
|
serializeJsonLd,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
interface HeadEntry {
|
|
9
|
-
tag: string;
|
|
10
|
-
attrs?: Record<string, string | boolean | number | undefined>;
|
|
11
|
-
content?: string;
|
|
12
|
-
}
|
|
6
|
+
type HeadEntry,
|
|
7
|
+
} from '@hagicode/hagilight-core/seo';
|
|
13
8
|
|
|
14
9
|
interface Props {
|
|
15
10
|
page?: {
|
|
@@ -46,7 +41,7 @@ const metadata = {
|
|
|
46
41
|
site,
|
|
47
42
|
};
|
|
48
43
|
const canonicalUrl = page.url?.toString() ?? currentPageUrl;
|
|
49
|
-
const composedHead =
|
|
44
|
+
const composedHead = composeCanonicalSeoHead(head, metadata, {
|
|
50
45
|
canonicalUrl,
|
|
51
46
|
explicitHead: [head],
|
|
52
47
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import { type FaviconOptions } from '@hagicode/hagilight-core/favicon';
|
|
3
|
+
import { type RssLocalesInput } from '@hagicode/hagilight-core/rss';
|
|
4
|
+
import { RSS_OWNER, type RssOwnerClaim } from '@hagicode/hagilight-core/rss-ownership';
|
|
5
|
+
export type { RssFeedCallback, RssFeedContent, RssFeedRequest, RssFooterContext, } from './rss-config.js';
|
|
6
|
+
export interface HagilightOptions {
|
|
7
|
+
/** Generate `sitemap-index.xml` and `robots.txt`. Defaults to `true`. */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface HagilightRssOptions {
|
|
11
|
+
/** Starlight-shaped locale map, for example `{ root: { lang: 'en-US' }, 'zh-CN': { lang: 'zh-CN' } }`. */
|
|
12
|
+
locales: RssLocalesInput;
|
|
13
|
+
/** Project-root-relative module whose default export is an `RssFeedCallback`. */
|
|
14
|
+
getFeed: string;
|
|
15
|
+
}
|
|
16
|
+
export type HagilightFaviconOptions = FaviconOptions;
|
|
17
|
+
export type HagilightRssIntegration = AstroIntegration & {
|
|
18
|
+
readonly [RSS_OWNER]: RssOwnerClaim;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generate a sitemap and `robots.txt` for a plain Astro site. An existing
|
|
22
|
+
* `@astrojs/sitemap` or Starlight integration owns sitemap generation, and a
|
|
23
|
+
* consumer-owned robots page or public file takes precedence.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hagilight({ enabled }?: HagilightOptions): AstroIntegration;
|
|
26
|
+
/** Inject the bundled HagiCode favicon unless the site head already declares an icon. */
|
|
27
|
+
export declare function hagilightFavicon(options?: HagilightFaviconOptions): AstroIntegration;
|
|
28
|
+
/**
|
|
29
|
+
* Generate localized `/rss.xml`, `/rss.en.xml`, and `/rss.<language>.xml`
|
|
30
|
+
* routes and expose their URLs to the plain-Astro Footer. An enabled Starlight
|
|
31
|
+
* Hagilight RSS integration in the same project owns the routes instead.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hagilightRss(input: HagilightRssOptions): HagilightRssIntegration;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { extname, isAbsolute, resolve, win32 } from 'node:path';
|
|
4
|
+
import sitemap from '@astrojs/sitemap';
|
|
5
|
+
import { resolveFaviconHeadEntry } from '@hagicode/hagilight-core/favicon';
|
|
6
|
+
import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
|
|
7
|
+
import { RSS_OWNER, resolvePlainAstroRssOwner, } from '@hagicode/hagilight-core/rss-ownership';
|
|
8
|
+
const VIRTUAL_RSS_CONFIG = 'virtual:hagilight/rss-config';
|
|
9
|
+
const VIRTUAL_RSS_CONFIG_RESOLVED = `\0${VIRTUAL_RSS_CONFIG}`;
|
|
10
|
+
const PAGE_SOURCE_EXTENSIONS = new Set(['.astro', '.html', '.js', '.jsx', '.md', '.mdx', '.ts', '.tsx']);
|
|
11
|
+
const packageRoot = new URL('../', import.meta.url);
|
|
12
|
+
const routeEntrypoint = (name) => fileURLToPath(new URL(name, packageRoot));
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function integrationName(integration) {
|
|
17
|
+
return isRecord(integration) ? integration.name : undefined;
|
|
18
|
+
}
|
|
19
|
+
function resolveDiscoverySite(site) {
|
|
20
|
+
const message = 'Hagilight sitemap and robots require an absolute HTTP(S) Astro site URL. Set `site` in astro.config.mjs.';
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = new URL(site);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(message);
|
|
27
|
+
}
|
|
28
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname || parsed.username || parsed.password) {
|
|
29
|
+
throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Generate a sitemap and `robots.txt` for a plain Astro site. An existing
|
|
35
|
+
* `@astrojs/sitemap` or Starlight integration owns sitemap generation, and a
|
|
36
|
+
* consumer-owned robots page or public file takes precedence.
|
|
37
|
+
*/
|
|
38
|
+
export function hagilight({ enabled = true } = {}) {
|
|
39
|
+
if (typeof enabled !== 'boolean') {
|
|
40
|
+
throw new TypeError('Hagilight sitemap and robots enabled option must be a boolean.');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: '@hagicode/hagilight:discovery',
|
|
44
|
+
hooks: {
|
|
45
|
+
'astro:config:setup'({ config, injectRoute, updateConfig }) {
|
|
46
|
+
if (!enabled)
|
|
47
|
+
return;
|
|
48
|
+
resolveDiscoverySite(config.site);
|
|
49
|
+
const integrations = config.integrations ?? [];
|
|
50
|
+
if (!integrations.some((integration) => {
|
|
51
|
+
const name = integrationName(integration);
|
|
52
|
+
return name === '@astrojs/sitemap' || name === '@astrojs/starlight';
|
|
53
|
+
})) {
|
|
54
|
+
updateConfig({ integrations: [sitemap()] });
|
|
55
|
+
}
|
|
56
|
+
const pageRoot = resolve(fileURLToPath(config.srcDir), 'pages');
|
|
57
|
+
const publicRoot = fileURLToPath(config.publicDir);
|
|
58
|
+
const hasRobotsPage = existsSync(pageRoot) && readdirSync(pageRoot).some((name) => /^robots\.txt\.(?:astro|html|js|jsx|md|mdx|ts|tsx)$/u.test(name)
|
|
59
|
+
|| name === 'robots.txt');
|
|
60
|
+
if (!hasRobotsPage && !existsSync(resolve(publicRoot, 'robots.txt'))) {
|
|
61
|
+
injectRoute({
|
|
62
|
+
pattern: '/robots.txt',
|
|
63
|
+
entrypoint: routeEntrypoint('robots.txt.ts'),
|
|
64
|
+
prerender: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Inject the bundled HagiCode favicon unless the site head already declares an icon. */
|
|
72
|
+
export function hagilightFavicon(options = {}) {
|
|
73
|
+
if (!isRecord(options)) {
|
|
74
|
+
throw new TypeError('Hagilight favicon options must be an object.');
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
name: '@hagicode/hagilight:favicon',
|
|
78
|
+
hooks: {
|
|
79
|
+
'astro:config:setup'({ config, updateConfig }) {
|
|
80
|
+
const head = config.head;
|
|
81
|
+
const entry = resolveFaviconHeadEntry(head, options);
|
|
82
|
+
if (!entry)
|
|
83
|
+
return;
|
|
84
|
+
updateConfig({ head: [...(head ?? []), entry] });
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function resolveRssOptions(options) {
|
|
90
|
+
if (!isRecord(options)) {
|
|
91
|
+
throw new TypeError('Hagilight RSS integration options must be an object.');
|
|
92
|
+
}
|
|
93
|
+
const locales = resolveRssLocales(options.locales, { requireNonEmpty: true });
|
|
94
|
+
if (typeof options.getFeed !== 'string' || !options.getFeed.trim()) {
|
|
95
|
+
throw new TypeError('Hagilight RSS getFeed must be a non-empty module path such as "./src/rss-feed.mjs".');
|
|
96
|
+
}
|
|
97
|
+
return { locales, getFeed: options.getFeed.trim() };
|
|
98
|
+
}
|
|
99
|
+
function resolveSite(site) {
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = new URL(site);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error('Hagilight RSS requires an absolute Astro site URL. Set `site` in astro.config.mjs.');
|
|
106
|
+
}
|
|
107
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password) {
|
|
108
|
+
throw new Error('Hagilight RSS requires an absolute HTTP(S) Astro site URL.');
|
|
109
|
+
}
|
|
110
|
+
return parsed.href;
|
|
111
|
+
}
|
|
112
|
+
function resolveFeedModule(root, reference) {
|
|
113
|
+
if (reference.startsWith('file:') || isAbsolute(reference) || win32.isAbsolute(reference)) {
|
|
114
|
+
throw new TypeError('Hagilight RSS getFeed must be a path relative to the Astro project root.');
|
|
115
|
+
}
|
|
116
|
+
const modulePath = resolve(fileURLToPath(root), reference);
|
|
117
|
+
if (!existsSync(modulePath) || !statSync(modulePath).isFile()) {
|
|
118
|
+
throw new Error(`Hagilight RSS getFeed module "${reference}" was not found relative to the Astro project root.`);
|
|
119
|
+
}
|
|
120
|
+
return modulePath;
|
|
121
|
+
}
|
|
122
|
+
function assertNoRouteCollisions(config, locales) {
|
|
123
|
+
const expectedFiles = new Set(['rss.xml', 'rss.en.xml', ...locales.map(({ filename }) => `rss.${filename}.xml`)]);
|
|
124
|
+
const pageRoot = resolve(fileURLToPath(config.srcDir), 'pages');
|
|
125
|
+
const publicRoot = fileURLToPath(config.publicDir);
|
|
126
|
+
const collisions = [];
|
|
127
|
+
function findPageCollisions(directory) {
|
|
128
|
+
if (!existsSync(directory))
|
|
129
|
+
return;
|
|
130
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
131
|
+
const entryPath = resolve(directory, entry.name);
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
if (entry.name === 'rss.xml' || /^rss\..+\.xml$/u.test(entry.name)) {
|
|
134
|
+
for (const child of readdirSync(entryPath, { withFileTypes: true })) {
|
|
135
|
+
if (child.isFile()
|
|
136
|
+
&& child.name.startsWith('index.')
|
|
137
|
+
&& PAGE_SOURCE_EXTENSIONS.has(extname(child.name))) {
|
|
138
|
+
collisions.push(resolve(entryPath, child.name));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
findPageCollisions(entryPath);
|
|
143
|
+
}
|
|
144
|
+
else if (entry.isFile()) {
|
|
145
|
+
const extension = extname(entry.name);
|
|
146
|
+
if (!PAGE_SOURCE_EXTENSIONS.has(extension))
|
|
147
|
+
continue;
|
|
148
|
+
const sourceName = entry.name.slice(0, -extension.length);
|
|
149
|
+
if (expectedFiles.has(sourceName) || /^rss\..+\.xml$/u.test(sourceName)) {
|
|
150
|
+
collisions.push(entryPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
findPageCollisions(pageRoot);
|
|
156
|
+
for (const filename of expectedFiles) {
|
|
157
|
+
const publicFile = resolve(publicRoot, filename);
|
|
158
|
+
if (existsSync(publicFile) && statSync(publicFile).isFile())
|
|
159
|
+
collisions.push(publicFile);
|
|
160
|
+
}
|
|
161
|
+
if (collisions.length > 0) {
|
|
162
|
+
throw new Error(`Hagilight RSS route conflicts with consumer-owned file(s): ${collisions.join(', ')}. Remove the conflicting route or do not enable hagilightRss().`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function createVirtualPlugin({ modulePath, site, baseUrl, locales, defaultFeedUrl, localeFeedUrls }) {
|
|
166
|
+
return {
|
|
167
|
+
name: '@hagicode/hagilight:rss',
|
|
168
|
+
resolveId(id) {
|
|
169
|
+
return id === VIRTUAL_RSS_CONFIG ? VIRTUAL_RSS_CONFIG_RESOLVED : null;
|
|
170
|
+
},
|
|
171
|
+
load(id) {
|
|
172
|
+
if (id !== VIRTUAL_RSS_CONFIG_RESOLVED)
|
|
173
|
+
return null;
|
|
174
|
+
return [
|
|
175
|
+
`import * as siteFeedModule from ${JSON.stringify(modulePath)};`,
|
|
176
|
+
'const config = {',
|
|
177
|
+
` site: ${JSON.stringify(site)},`,
|
|
178
|
+
` baseUrl: ${JSON.stringify(baseUrl)},`,
|
|
179
|
+
` locales: ${JSON.stringify(locales)},`,
|
|
180
|
+
' getFeed: siteFeedModule.default,',
|
|
181
|
+
` footer: ${JSON.stringify({ defaultFeedUrl, localeFeedUrls, locales })},`,
|
|
182
|
+
'};',
|
|
183
|
+
'export default config;',
|
|
184
|
+
].join('\n');
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate localized `/rss.xml`, `/rss.en.xml`, and `/rss.<language>.xml`
|
|
190
|
+
* routes and expose their URLs to the plain-Astro Footer. An enabled Starlight
|
|
191
|
+
* Hagilight RSS integration in the same project owns the routes instead.
|
|
192
|
+
*/
|
|
193
|
+
export function hagilightRss(input) {
|
|
194
|
+
const options = resolveRssOptions(input);
|
|
195
|
+
return {
|
|
196
|
+
name: '@hagicode/hagilight:rss',
|
|
197
|
+
[RSS_OWNER]: { package: 'astro', enabled: true },
|
|
198
|
+
hooks: {
|
|
199
|
+
'astro:config:setup'({ config, injectRoute, addMiddleware, updateConfig }) {
|
|
200
|
+
if (resolvePlainAstroRssOwner(config.integrations) === 'starlight')
|
|
201
|
+
return;
|
|
202
|
+
const site = resolveSite(config.site);
|
|
203
|
+
const modulePath = resolveFeedModule(config.root, options.getFeed);
|
|
204
|
+
assertNoRouteCollisions(config, options.locales);
|
|
205
|
+
const baseSegments = `${config.base || '/'}`
|
|
206
|
+
.split('/')
|
|
207
|
+
.filter(Boolean);
|
|
208
|
+
const baseUrl = baseSegments.length > 0 ? `/${baseSegments.join('/')}/` : '/';
|
|
209
|
+
const baseLocation = new URL(baseUrl, site);
|
|
210
|
+
const defaultFeedUrl = new URL('rss.xml', baseLocation).href;
|
|
211
|
+
const localeFeedUrls = Object.fromEntries(options.locales
|
|
212
|
+
.filter(({ filename }) => filename !== 'en')
|
|
213
|
+
.map(({ lang, filename }) => [lang, new URL(`rss.${filename}.xml`, baseLocation).href]));
|
|
214
|
+
injectRoute({
|
|
215
|
+
pattern: '/rss.xml',
|
|
216
|
+
entrypoint: routeEntrypoint('rss.xml.ts'),
|
|
217
|
+
prerender: true,
|
|
218
|
+
});
|
|
219
|
+
injectRoute({
|
|
220
|
+
pattern: '/rss.[language].xml',
|
|
221
|
+
entrypoint: routeEntrypoint('rss.[language].xml.ts'),
|
|
222
|
+
prerender: true,
|
|
223
|
+
});
|
|
224
|
+
addMiddleware({
|
|
225
|
+
entrypoint: fileURLToPath(new URL('./rss-middleware.js', import.meta.url)),
|
|
226
|
+
order: 'pre',
|
|
227
|
+
});
|
|
228
|
+
updateConfig({
|
|
229
|
+
vite: {
|
|
230
|
+
plugins: [createVirtualPlugin({
|
|
231
|
+
modulePath,
|
|
232
|
+
site,
|
|
233
|
+
baseUrl,
|
|
234
|
+
locales: options.locales,
|
|
235
|
+
defaultFeedUrl,
|
|
236
|
+
localeFeedUrls,
|
|
237
|
+
})],
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RssFeedItem, RssLocale } from '@hagicode/hagilight-core/rss';
|
|
2
|
+
export interface RssFeedRequest {
|
|
3
|
+
/** Locale route key from the configured `locales` map (`root` for the default locale). */
|
|
4
|
+
route: string;
|
|
5
|
+
/** Canonical language tag of the requested feed. */
|
|
6
|
+
lang: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RssFeedContent {
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
items: readonly RssFeedItem[];
|
|
12
|
+
}
|
|
13
|
+
/** Default export of the module referenced by `hagilightRss({ getFeed })`. */
|
|
14
|
+
export type RssFeedCallback = (request: RssFeedRequest) => RssFeedContent | Promise<RssFeedContent>;
|
|
15
|
+
/** Request-local feed URLs that the plain-Astro Footer reads from `Astro.locals.hagilightRss`. */
|
|
16
|
+
export interface RssFooterContext {
|
|
17
|
+
defaultFeedUrl: string;
|
|
18
|
+
localeFeedUrls: Readonly<Record<string, string>>;
|
|
19
|
+
locales: readonly RssLocale[];
|
|
20
|
+
}
|
|
21
|
+
/** Serialized configuration exposed to generated routes through `virtual:hagilight/rss-config`. */
|
|
22
|
+
export interface RssRuntimeConfig {
|
|
23
|
+
site: string;
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
locales: readonly RssLocale[];
|
|
26
|
+
getFeed: RssFeedCallback | undefined;
|
|
27
|
+
footer: RssFooterContext;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { generateRssFeed } from '@hagicode/hagilight-core/rss';
|
|
2
|
+
const EMPTY_ENGLISH_FEED = {
|
|
3
|
+
title: 'Hagilight RSS Feed',
|
|
4
|
+
description: 'English-language feed for this site.',
|
|
5
|
+
items: [],
|
|
6
|
+
};
|
|
7
|
+
export async function renderRssFeed(config, filename) {
|
|
8
|
+
const locale = config.locales.find((entry) => entry.filename === filename);
|
|
9
|
+
if (!locale && filename !== 'en') {
|
|
10
|
+
throw new Error(`Hagilight RSS has no configured locale for "${filename}".`);
|
|
11
|
+
}
|
|
12
|
+
let feed;
|
|
13
|
+
if (!locale) {
|
|
14
|
+
feed = EMPTY_ENGLISH_FEED;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
if (typeof config.getFeed !== 'function') {
|
|
18
|
+
throw new TypeError('Hagilight RSS getFeed module must default-export a callback function.');
|
|
19
|
+
}
|
|
20
|
+
const result = await config.getFeed({ route: locale.route, lang: locale.lang });
|
|
21
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
22
|
+
throw new TypeError(`Hagilight RSS getFeed callback for "${locale.lang}" must return an object.`);
|
|
23
|
+
}
|
|
24
|
+
const candidate = result;
|
|
25
|
+
if (typeof candidate.title !== 'string' || !candidate.title.trim()) {
|
|
26
|
+
throw new TypeError(`Hagilight RSS getFeed callback for "${locale.lang}" must return a non-empty title.`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof candidate.description !== 'string' || !candidate.description.trim()) {
|
|
29
|
+
throw new TypeError(`Hagilight RSS getFeed callback for "${locale.lang}" must return a non-empty description.`);
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(candidate.items)) {
|
|
32
|
+
throw new TypeError(`Hagilight RSS getFeed callback for "${locale.lang}" must return an items array.`);
|
|
33
|
+
}
|
|
34
|
+
feed = candidate;
|
|
35
|
+
}
|
|
36
|
+
return generateRssFeed({
|
|
37
|
+
site: config.site,
|
|
38
|
+
baseUrl: config.baseUrl,
|
|
39
|
+
language: locale?.lang ?? 'en',
|
|
40
|
+
title: feed.title,
|
|
41
|
+
description: feed.description,
|
|
42
|
+
items: feed.items,
|
|
43
|
+
});
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight",
|
|
3
|
-
"version": "0.3.2-dev.
|
|
3
|
+
"version": "0.3.2-dev.57.1.ee1f17e",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Astro integrations and components for HagiCode sites",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/HagiCode-org/hagilight.git",
|
|
@@ -12,46 +12,39 @@
|
|
|
12
12
|
"access": "public",
|
|
13
13
|
"provenance": true
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b",
|
|
17
|
+
"prepack": "tsc -b"
|
|
18
|
+
},
|
|
15
19
|
"exports": {
|
|
16
|
-
"./
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"./
|
|
21
|
-
"./favicon": "./favicon.mjs",
|
|
22
|
-
"./site-links": "./site-links.ts",
|
|
23
|
-
"./GoogleAnalytics": "./GoogleAnalytics.astro",
|
|
24
|
-
"./Analytics51LA": "./Analytics51LA.astro",
|
|
25
|
-
"./SEOHead": "./SEOHead.astro",
|
|
26
|
-
"./integration": "./integration.mjs",
|
|
27
|
-
"./seo-utils": "./seo-utils.mjs",
|
|
28
|
-
"./rss-locales": "./rss-locales.mjs",
|
|
29
|
-
"./rss": "./rss-renderer.mjs",
|
|
30
|
-
"./seo-schema": {
|
|
31
|
-
"types": "./seo-schema.d.ts",
|
|
32
|
-
"default": "./seo-schema.mjs"
|
|
33
|
-
}
|
|
20
|
+
"./integration": {
|
|
21
|
+
"types": "./dist/integration.d.ts",
|
|
22
|
+
"default": "./dist/integration.js"
|
|
23
|
+
},
|
|
24
|
+
"./SEOHead": "./SEOHead.astro"
|
|
34
25
|
},
|
|
35
26
|
"files": [
|
|
27
|
+
"dist/**/*.js",
|
|
28
|
+
"dist/**/*.d.ts",
|
|
36
29
|
"*.astro",
|
|
37
|
-
"
|
|
38
|
-
"*.
|
|
39
|
-
"*.mjs",
|
|
40
|
-
"*.ts",
|
|
41
|
-
"*.json"
|
|
30
|
+
"robots.txt.ts",
|
|
31
|
+
"rss*.xml.ts"
|
|
42
32
|
],
|
|
43
33
|
"peerDependencies": {
|
|
44
34
|
"astro": "^6.0.7 || ^7.3.5"
|
|
45
35
|
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"astro": "^7.3.5"
|
|
38
|
+
},
|
|
46
39
|
"dependencies": {
|
|
47
|
-
"@astrojs/
|
|
48
|
-
"@
|
|
40
|
+
"@astrojs/sitemap": "^3.7.4",
|
|
41
|
+
"@hagicode/hagilight-core": "0.3.2-dev.57.1.ee1f17e"
|
|
49
42
|
},
|
|
50
43
|
"keywords": [
|
|
51
44
|
"astro-component",
|
|
45
|
+
"astro-integration",
|
|
52
46
|
"withastro",
|
|
53
47
|
"astro-components",
|
|
54
|
-
"starlight",
|
|
55
48
|
"documentation-components"
|
|
56
49
|
]
|
|
57
50
|
}
|
package/rss.[language].xml.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRoute, GetStaticPaths } from 'astro';
|
|
2
2
|
import rssConfig from 'virtual:hagilight/rss-config';
|
|
3
|
-
import { renderRssFeed } from './rss-runtime.
|
|
3
|
+
import { renderRssFeed } from './dist/rss-runtime.js';
|
|
4
4
|
|
|
5
5
|
export const getStaticPaths: GetStaticPaths = () => [...new Set([
|
|
6
6
|
'en',
|
package/rss.xml.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro';
|
|
2
2
|
import rssConfig from 'virtual:hagilight/rss-config';
|
|
3
|
-
import { renderRssFeed } from './rss-runtime.
|
|
3
|
+
import { renderRssFeed } from './dist/rss-runtime.js';
|
|
4
4
|
|
|
5
5
|
export const GET: APIRoute = () => renderRssFeed(rssConfig, 'en');
|
package/Analytics51LA.astro
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
interface Props {
|
|
3
|
-
siteId: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const { siteId } = Astro.props;
|
|
7
|
-
if (!siteId?.trim()) throw new Error('Analytics51LA requires a siteId prop.');
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
{import.meta.env.PROD && (
|
|
11
|
-
<>
|
|
12
|
-
<script is:inline charset="UTF-8" id="LA_COLLECT" src="https://sdk.51.la/js-sdk-pro.min.js"></script>
|
|
13
|
-
<script is:inline define:vars={{ siteId }}>
|
|
14
|
-
LA.init({ id: siteId, ck: siteId, autoTrack: true, hashMode: true, screenRecord: true });
|
|
15
|
-
</script>
|
|
16
|
-
</>
|
|
17
|
-
)}
|
package/Copyright.astro
DELETED
package/Footer.astro
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import Copyright from './Copyright.astro';
|
|
3
|
-
import { resolveRssFooterLinks } from './rss-footer-context.mjs';
|
|
4
|
-
import { resolveSiteLinks, type SiteLinksOptions } from './site-links.ts';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
locale?: string;
|
|
8
|
-
links?: SiteLinksOptions;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const { locale: propLocale, links } = Astro.props;
|
|
12
|
-
const locale = propLocale ?? Astro.currentLocale ?? 'en-US';
|
|
13
|
-
const footerLinks = resolveRssFooterLinks(Astro.locals, locale, links);
|
|
14
|
-
const resolvedLinks = resolveSiteLinks(locale, footerLinks);
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
<footer class="hagilight-footer">
|
|
18
|
-
<div class="hagilight-footer__sections">
|
|
19
|
-
{resolvedLinks.relatedSites.length > 0 && (
|
|
20
|
-
<section>
|
|
21
|
-
<h2>{resolvedLinks.labels.relatedSites}</h2>
|
|
22
|
-
<nav aria-label={resolvedLinks.labels.navigation.relatedSites}>
|
|
23
|
-
<ul>
|
|
24
|
-
{resolvedLinks.relatedSites.map((site) => (
|
|
25
|
-
<li>
|
|
26
|
-
<a href={site.href} target={site.target} rel={site.rel}>{site.name}</a>
|
|
27
|
-
</li>
|
|
28
|
-
))}
|
|
29
|
-
</ul>
|
|
30
|
-
</nav>
|
|
31
|
-
</section>
|
|
32
|
-
)}
|
|
33
|
-
<section>
|
|
34
|
-
<h2>{resolvedLinks.labels.quickLinks}</h2>
|
|
35
|
-
<nav aria-label={resolvedLinks.labels.navigation.quickLinks}>
|
|
36
|
-
<ul>
|
|
37
|
-
{resolvedLinks.quick.map((link) => (
|
|
38
|
-
<li>
|
|
39
|
-
<a
|
|
40
|
-
href={link.href}
|
|
41
|
-
target={link.target}
|
|
42
|
-
rel={link.rel}
|
|
43
|
-
aria-label={link.ariaLabel}
|
|
44
|
-
>{link.label}</a>
|
|
45
|
-
</li>
|
|
46
|
-
))}
|
|
47
|
-
</ul>
|
|
48
|
-
</nav>
|
|
49
|
-
</section>
|
|
50
|
-
<section>
|
|
51
|
-
<h2>{resolvedLinks.labels.community}</h2>
|
|
52
|
-
<nav aria-label={resolvedLinks.labels.navigation.community}>
|
|
53
|
-
<ul>
|
|
54
|
-
{resolvedLinks.community.map((link) => (
|
|
55
|
-
<li>
|
|
56
|
-
<a
|
|
57
|
-
href={link.href}
|
|
58
|
-
target={link.target}
|
|
59
|
-
rel={link.rel}
|
|
60
|
-
aria-label={link.ariaLabel}
|
|
61
|
-
>{link.label}</a>
|
|
62
|
-
</li>
|
|
63
|
-
))}
|
|
64
|
-
</ul>
|
|
65
|
-
</nav>
|
|
66
|
-
</section>
|
|
67
|
-
</div>
|
|
68
|
-
<nav class="hagilight-footer__filings" aria-label={resolvedLinks.labels.navigation.filings}>
|
|
69
|
-
{resolvedLinks.filings.map((link) => (
|
|
70
|
-
<a
|
|
71
|
-
href={link.href}
|
|
72
|
-
target={link.target}
|
|
73
|
-
rel={link.rel}
|
|
74
|
-
aria-label={link.ariaLabel}
|
|
75
|
-
>{link.label}</a>
|
|
76
|
-
))}
|
|
77
|
-
</nav>
|
|
78
|
-
<p class="hagilight-footer__copyright"><Copyright name="HagiCode" /></p>
|
|
79
|
-
</footer>
|
|
80
|
-
|
|
81
|
-
<style>
|
|
82
|
-
.hagilight-footer {
|
|
83
|
-
border-top: 1px solid var(--hagilight-footer-border, currentColor);
|
|
84
|
-
color: var(--hagilight-footer-color, inherit);
|
|
85
|
-
padding: 1.5rem 1rem 1rem;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.hagilight-footer__sections {
|
|
89
|
-
display: grid;
|
|
90
|
-
grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
|
|
91
|
-
gap: 1.5rem;
|
|
92
|
-
margin: 0 auto 1.5rem;
|
|
93
|
-
max-width: 72rem;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.hagilight-footer section {
|
|
97
|
-
min-width: 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.hagilight-footer h2 {
|
|
101
|
-
margin: 0 0 0.75rem;
|
|
102
|
-
font-size: 0.95rem;
|
|
103
|
-
font-weight: 650;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.hagilight-footer ul {
|
|
107
|
-
display: grid;
|
|
108
|
-
gap: 0.5rem;
|
|
109
|
-
margin: 0;
|
|
110
|
-
padding: 0;
|
|
111
|
-
list-style: none;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.hagilight-footer a {
|
|
115
|
-
color: inherit;
|
|
116
|
-
text-decoration: none;
|
|
117
|
-
text-underline-offset: 0.2em;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.hagilight-footer a:hover {
|
|
121
|
-
text-decoration: underline;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.hagilight-footer a:focus-visible {
|
|
125
|
-
border-radius: 0.125rem;
|
|
126
|
-
outline: 2px solid currentColor;
|
|
127
|
-
outline-offset: 0.2rem;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.hagilight-footer__filings {
|
|
131
|
-
display: flex;
|
|
132
|
-
flex-wrap: wrap;
|
|
133
|
-
justify-content: center;
|
|
134
|
-
gap: 0.75rem 1.5rem;
|
|
135
|
-
border-top: 1px solid var(--hagilight-footer-border, currentColor);
|
|
136
|
-
padding-top: 1rem;
|
|
137
|
-
text-align: center;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.hagilight-footer__copyright {
|
|
141
|
-
margin: 1rem 0 0;
|
|
142
|
-
font-size: 0.8rem;
|
|
143
|
-
text-align: center;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
@media (max-width: 40rem) {
|
|
147
|
-
.hagilight-footer__sections {
|
|
148
|
-
grid-template-columns: 1fr;
|
|
149
|
-
gap: 1.25rem;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.hagilight-footer__filings {
|
|
153
|
-
justify-content: flex-start;
|
|
154
|
-
gap: 0.75rem 1rem;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
</style>
|