@hagicode/hagilight-docusaurus 0.0.0-stage → 0.1.0-dev.148.1.e0c630e

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/dist/seo.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { HtmlTagObject } from '@docusaurus/types';
2
+ import type { ResolvedSeoOptions } from './options.js';
3
+ export interface SeoSite {
4
+ title: string;
5
+ url: string;
6
+ baseUrl: string;
7
+ }
8
+ export declare function resolveSharingImage(image: string | undefined, site: Pick<SeoSite, 'url' | 'baseUrl'>): string | undefined;
9
+ export interface SharingMetadataValues {
10
+ siteName: string;
11
+ image: string | undefined;
12
+ }
13
+ export declare function sharingMetadataFor(seo: ResolvedSeoOptions, site: Pick<SeoSite, 'title' | 'url' | 'baseUrl'>, themeImage: string | undefined): SharingMetadataValues | undefined;
14
+ export declare function seoHeadTags(seo: ResolvedSeoOptions, site: SeoSite): HtmlTagObject[];
package/dist/seo.js ADDED
@@ -0,0 +1,32 @@
1
+ import { buildOrganizationStructuredData, resolveSeoImageUrl, serializeJsonLd } from '@hagicode/hagilight-core/seo';
2
+ export function resolveSharingImage(image, site) {
3
+ return image === undefined ? undefined : resolveSeoImageUrl(image, { site: site.url, basePath: site.baseUrl });
4
+ }
5
+ export function sharingMetadataFor(seo, site, themeImage) {
6
+ if (!seo.enabled)
7
+ return undefined;
8
+ return {
9
+ siteName: site.title,
10
+ image: themeImage === undefined ? resolveSharingImage(seo.image, site) : undefined,
11
+ };
12
+ }
13
+ export function seoHeadTags(seo, site) {
14
+ if (!seo.enabled)
15
+ return [];
16
+ const organization = buildOrganizationStructuredData({
17
+ name: seo.organization.name ?? site.title,
18
+ url: seo.organization.url ?? site.url,
19
+ ...(seo.organization.logo === undefined ? {} : { logo: seo.organization.logo }),
20
+ });
21
+ const website = {
22
+ '@context': 'https://schema.org',
23
+ '@type': 'WebSite',
24
+ name: site.title,
25
+ url: new URL(site.baseUrl, site.url).href,
26
+ };
27
+ return [organization, website].map((data) => ({
28
+ tagName: 'script',
29
+ attributes: { type: 'application/ld+json' },
30
+ innerHTML: serializeJsonLd(data),
31
+ }));
32
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type BuildInfo } from '@hagicode/hagilight-core/build-info';
3
+ import type { FooterLinks } from '../../footer.js';
4
+ /** The decorative avatar: the vendored default pair and, when the random option is on, the baked pool. */
5
+ export interface FooterAvatar {
6
+ avif: string;
7
+ webp: string;
8
+ randomPool?: readonly string[];
9
+ }
10
+ export interface FooterViewProps {
11
+ links: FooterLinks;
12
+ version: string;
13
+ year: number;
14
+ buildInfo?: BuildInfo;
15
+ avatar?: FooterAvatar;
16
+ }
17
+ export declare function FooterView({ links, version, year, buildInfo, avatar }: FooterViewProps): ReactElement;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from 'react';
3
+ import { siteLinkGaAttributes } from '@hagicode/hagilight-core/analytics-events';
4
+ import { buildInfoItems } from '@hagicode/hagilight-core/build-info';
5
+ import { pickFooterBrandLinks, resolveFooterBrand } from '@hagicode/hagilight-core/footer-brand';
6
+ import { footerAvatarImageAttributes } from '@hagicode/hagilight-core/footer-avatar';
7
+ import { randomPoolAttribute } from '@hagicode/hagilight-core/random-image';
8
+ function LinkSection({ heading, navLabel, links, tagged }) {
9
+ return (_jsxs("section", { children: [_jsx("h2", { children: heading }), _jsx("nav", { "aria-label": navLabel, children: _jsx("ul", { children: links.map((link) => (_jsx("li", { children: _jsx("a", { href: link.href, target: link.target, rel: link.rel, "aria-label": link.ariaLabel, ...(tagged ? siteLinkGaAttributes(link, 'footer') : {}), children: link.label }) }, link.id))) }) })] }));
10
+ }
11
+ function FooterBrand({ locale, links, avatar }) {
12
+ const brand = resolveFooterBrand(locale);
13
+ const { primary, secondary } = pickFooterBrandLinks(links);
14
+ return (_jsxs("section", { className: "hagilight-brand", lang: brand.locale, "aria-labelledby": "hagilight-brand-title", children: [_jsxs("div", { className: "hagilight-brand__lead", children: [_jsxs("div", { className: "hagilight-brand__title", children: [avatar && (_jsxs("picture", { className: "hagilight-brand__avatar", "data-random-pool": avatar.randomPool && randomPoolAttribute(avatar.randomPool), children: [_jsx("source", { type: "image/avif", srcSet: avatar.avif }), _jsx("source", { type: "image/webp", srcSet: avatar.webp }), _jsx("img", { src: avatar.webp, ...footerAvatarImageAttributes() })] })), _jsx("h2", { className: "hagilight-brand__headline", id: "hagilight-brand-title", children: brand.headline })] }), _jsxs("div", { className: "hagilight-brand__body", children: [_jsx("p", { className: "hagilight-brand__summary", children: brand.summary }), (primary || secondary) && (_jsxs("div", { className: "hagilight-brand__actions", children: [primary && (_jsxs("a", { className: "hagilight-brand__primary", href: primary.href, target: primary.target, rel: primary.rel, "aria-label": primary.ariaLabel, ...siteLinkGaAttributes(primary, 'footer_brand'), children: [_jsx("span", { children: primary.label }), _jsx("svg", { className: "hagilight-brand__arrow", viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M2.5 8h10M8.5 3.5 13 8l-4.5 4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), secondary && (_jsx("a", { className: "hagilight-brand__secondary", href: secondary.href, target: secondary.target, rel: secondary.rel, "aria-label": secondary.ariaLabel, ...siteLinkGaAttributes(secondary, 'footer_brand'), children: secondary.label }))] }))] })] }), _jsx("dl", { className: "hagilight-brand__pillars", children: brand.pillars.map((pillar, index) => (_jsxs("div", { className: "hagilight-brand__pillar", "data-pillar": pillar.id, style: { '--hagilight-brand-i': index }, children: [_jsx("dt", { children: pillar.label }), _jsx("dd", { children: pillar.description })] }, pillar.id))) })] }));
15
+ }
16
+ export function FooterView({ links, version, year, buildInfo, avatar }) {
17
+ const { labels } = links;
18
+ const buildItems = buildInfoItems(buildInfo);
19
+ return (_jsxs("footer", { className: "hagilight-footer", children: [_jsx(FooterBrand, { locale: links.locale, links: links.quick, avatar: avatar }), _jsxs("div", { className: "hagilight-footer__sections", children: [links.relatedSites.length > 0 && (_jsxs("section", { children: [_jsx("h2", { children: labels.relatedSites }), _jsx("nav", { "aria-label": labels.navigation.relatedSites, children: _jsx("ul", { children: links.relatedSites.map((site) => (_jsx("li", { children: _jsx("a", { href: site.href, target: site.target, rel: site.rel, children: site.name }) }, site.id))) }) })] })), _jsx(LinkSection, { heading: labels.quickLinks, navLabel: labels.navigation.quickLinks, links: links.quick, tagged: true }), _jsx(LinkSection, { heading: labels.community, navLabel: labels.navigation.community, links: links.community, tagged: true })] }), links.filings.length > 0 && (_jsx("nav", { className: "hagilight-footer__filings", "aria-label": labels.navigation.filings, children: links.filings.map((link) => (_jsx("a", { href: link.href, target: link.target, rel: link.rel, "aria-label": link.ariaLabel, children: link.label }, link.id))) })), _jsxs("p", { className: "hagilight-footer__copyright", children: [_jsxs("span", { children: ["\u00A9 ", year, " HagiCode"] }), ' | ', _jsxs("span", { className: "hagilight-footer__attribution", children: ["Powered By hagilight-docusaurus@", version] }), buildItems.map((item) => (_jsxs(Fragment, { children: [' | ', _jsxs("span", { className: "hagilight-footer__build", "data-build-field": item.field, title: item.title, "data-build-commit": item.title, children: [item.label, ": ", item.datetime ? _jsx("time", { dateTime: item.datetime, children: item.text }) : item.text] })] }, item.field)))] })] }));
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { ReactElement } from 'react';
2
+ export default function Footer(): ReactElement;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import FooterDefaults from '@theme-init/Footer';
3
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
4
+ import { usePluginData } from '@docusaurus/useGlobalData';
5
+ import { resolveFooterLinks } from '../../footer.js';
6
+ import { PLUGIN_NAME } from '../../global-data.js';
7
+ import { FooterView } from './FooterView.js';
8
+ import avatarAvif from '@hagicode/hagilight-core/assets/footer/avatar.avif';
9
+ import avatarWebp from '@hagicode/hagilight-core/assets/footer/avatar.webp';
10
+ export default function Footer() {
11
+ const { i18n, siteConfig } = useDocusaurusContext();
12
+ const { version, options, buildInfo, footerAvatarPool } = usePluginData(PLUGIN_NAME);
13
+ if (!options.footer)
14
+ return _jsx(FooterDefaults, {});
15
+ const avatar = options.footerAvatar.avatar
16
+ ? { avif: avatarAvif, webp: avatarWebp, randomPool: footerAvatarPool }
17
+ : undefined;
18
+ const links = resolveFooterLinks({
19
+ locale: i18n.currentLocale,
20
+ baseUrl: siteConfig.baseUrl,
21
+ links: options.links,
22
+ rssEnabled: options.rss.enabled,
23
+ });
24
+ return (_jsx(FooterView, { links: links, version: version, year: new Date().getFullYear(), buildInfo: buildInfo, avatar: avatar }));
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { Props as HeadProps } from '@docusaurus/Head';
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ export interface SharingMetadataProps {
4
+ Head: (props: HeadProps) => ReactNode;
5
+ siteName: string;
6
+ image: string | undefined;
7
+ }
8
+ export declare function SharingMetadata({ Head, siteName, image }: SharingMetadataProps): ReactElement;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function SharingMetadata({ Head, siteName, image }) {
3
+ return (_jsxs(Head, { children: [_jsx("meta", { property: "og:site_name", content: siteName }), image !== undefined && _jsx("meta", { property: "og:image", content: image }), image !== undefined && _jsx("meta", { name: "twitter:image", content: image })] }));
4
+ }
@@ -0,0 +1,2 @@
1
+ import type { ReactElement } from 'react';
2
+ export default function SiteMetadata(): ReactElement;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Head from '@docusaurus/Head';
3
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
4
+ import { usePluginData } from '@docusaurus/useGlobalData';
5
+ import ClassicSiteMetadata from '@docusaurus/theme-classic/lib/theme/SiteMetadata/index.js';
6
+ import { PLUGIN_NAME } from '../../global-data.js';
7
+ import { sharingMetadataFor } from '../../seo.js';
8
+ import { SharingMetadata } from './SharingMetadata.js';
9
+ export default function SiteMetadata() {
10
+ const { siteConfig } = useDocusaurusContext();
11
+ const { options } = usePluginData(PLUGIN_NAME);
12
+ const themeImage = siteConfig.themeConfig?.image;
13
+ const sharing = sharingMetadataFor(options.seo, siteConfig, typeof themeImage === 'string' ? themeImage : undefined);
14
+ return (_jsxs(_Fragment, { children: [sharing !== undefined && _jsx(SharingMetadata, { Head: Head, ...sharing }), _jsx(ClassicSiteMetadata, {})] }));
15
+ }
@@ -0,0 +1,4 @@
1
+ import type { LoadContext } from '@docusaurus/types';
2
+ import type { ResolvedHagilightOptions } from './options.js';
3
+ export declare function siteOwnsThemeComponent(siteDir: string, name: string): boolean;
4
+ export declare function findConfigurationWarnings(context: Pick<LoadContext, 'siteDir' | 'siteConfig' | 'i18n'>, options: ResolvedHagilightOptions): string[];
@@ -0,0 +1,70 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { locales as catalogLocales } from './locales.js';
4
+ const THEME_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js'];
5
+ const GTAG_PLUGIN = '@docusaurus/plugin-google-gtag';
6
+ const CLASSIC_PRESET = '@docusaurus/preset-classic';
7
+ const CATALOG_LOCALES = new Set(catalogLocales);
8
+ export function siteOwnsThemeComponent(siteDir, name) {
9
+ const themeDir = join(siteDir, 'src', 'theme');
10
+ return THEME_EXTENSIONS.some((ext) => existsSync(join(themeDir, name, `index.${ext}`)) || existsSync(join(themeDir, `${name}.${ext}`)));
11
+ }
12
+ function entryName(entry) {
13
+ if (typeof entry === 'string')
14
+ return entry;
15
+ if (Array.isArray(entry) && typeof entry[0] === 'string')
16
+ return entry[0];
17
+ return undefined;
18
+ }
19
+ function entryOptions(entry) {
20
+ if (!Array.isArray(entry))
21
+ return undefined;
22
+ const options = entry[1];
23
+ return typeof options === 'object' && options !== null ? options : undefined;
24
+ }
25
+ function hasPluginEntry(entries, name) {
26
+ return Array.isArray(entries) && entries.some((entry) => entryName(entry) === name);
27
+ }
28
+ function hasLocaleDropdown(themeConfig) {
29
+ const navbar = themeConfig.navbar;
30
+ if (typeof navbar !== 'object' || navbar === null)
31
+ return false;
32
+ const items = navbar.items;
33
+ return Array.isArray(items) && items.some((item) => item?.type === 'localeDropdown');
34
+ }
35
+ function officialGtagConfigured(siteConfig) {
36
+ const raw = siteConfig;
37
+ if (hasPluginEntry(raw.plugins, GTAG_PLUGIN))
38
+ return true;
39
+ return Array.isArray(raw.presets)
40
+ && raw.presets.some((preset) => entryName(preset) === CLASSIC_PRESET && entryOptions(preset)?.gtag !== undefined);
41
+ }
42
+ export function findConfigurationWarnings(context, options) {
43
+ const warnings = [];
44
+ const themeConfig = (context.siteConfig.themeConfig ?? {});
45
+ if (options.languageSelector && context.i18n.locales.length > 1) {
46
+ if (!hasLocaleDropdown(themeConfig)) {
47
+ warnings.push("Several locales are configured but themeConfig.navbar.items has no localeDropdown item, so visitors cannot switch language. Add { type: 'localeDropdown' } or set languageSelector: { enabled: false }.");
48
+ }
49
+ const nonCatalog = context.i18n.locales.filter((locale) => !CATALOG_LOCALES.has(locale));
50
+ if (nonCatalog.length > 0) {
51
+ warnings.push(`${nonCatalog.join(', ')} ${nonCatalog.length === 1 ? 'is' : 'are'} not in the Hagilight locale catalog, so its native label and lang value are not checked against the Starlight locales. Use catalog locales or set languageSelector: { enabled: false }.`);
52
+ }
53
+ }
54
+ if (options.footer && siteOwnsThemeComponent(context.siteDir, 'Footer')) {
55
+ warnings.push('src/theme/Footer replaces the Hagilight footer. Remove the site Footer component or set footer: { enabled: false } to keep one footer.');
56
+ }
57
+ if (options.seo.enabled && siteOwnsThemeComponent(context.siteDir, 'SiteMetadata')) {
58
+ warnings.push('src/theme/SiteMetadata replaces the Hagilight sharing-image fallback. Remove the site component or set seo: { enabled: false }; the Organization and WebSite JSON-LD are still emitted.');
59
+ }
60
+ if (options.seo.enabled && options.seo.image === undefined && themeConfig.image === undefined) {
61
+ warnings.push('Neither themeConfig.image nor seo.image is set, so pages without their own image get no default sharing image. Set one of them, or set seo: { enabled: false }.');
62
+ }
63
+ if (options.footer && themeConfig.footer !== undefined) {
64
+ warnings.push('themeConfig.footer is ignored by the Hagilight footer. Move its links into the links option or set footer: { enabled: false }.');
65
+ }
66
+ if (options.googleAnalyticsId !== undefined && (themeConfig.gtag !== undefined || officialGtagConfigured(context.siteConfig))) {
67
+ warnings.push('Google Analytics would load twice: the site already configures gtag. Remove themeConfig.gtag or the official gtag plugin, or set analytics.googleAnalytics: { enabled: false }.');
68
+ }
69
+ return warnings;
70
+ }
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight-docusaurus",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-dev.148.1.e0c630e",
4
+ "type": "module",
5
+ "description": "Docusaurus plugin for HagiCode site features: footer, analytics, promote card, locales, SEO, sitemap, robots.txt, and RSS",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/HagiCode-org/hagilight.git",
9
+ "directory": "packages/docusaurus"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "provenance": true
14
+ },
15
+ "engines": {
16
+ "node": ">=24.0.0"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -b",
20
+ "prepack": "tsc -b"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./locales": {
28
+ "types": "./dist/locales.d.ts",
29
+ "default": "./dist/locales.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "README.md",
34
+ "styles.css",
35
+ "promoto.css",
36
+ "dist/**/*.js",
37
+ "dist/**/*.d.ts"
38
+ ],
39
+ "dependencies": {
40
+ "@docusaurus/types": "^3.9.2",
41
+ "@hagicode/hagilight-core": "0.1.0-dev.148.1.e0c630e"
42
+ },
43
+ "peerDependencies": {
44
+ "@docusaurus/core": "^3.9.2",
45
+ "@docusaurus/theme-classic": "^3.9.2",
46
+ "react": "^18.0.0 || ^19.0.0",
47
+ "react-dom": "^18.0.0 || ^19.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@docusaurus/core": "^3.9.2",
51
+ "@docusaurus/module-type-aliases": "^3.9.2",
52
+ "@docusaurus/theme-classic": "^3.9.2",
53
+ "@types/react": "^19.0.0",
54
+ "@types/react-dom": "^19.0.0",
55
+ "react": "^19.0.0",
56
+ "react-dom": "^19.0.0",
57
+ "react-helmet-async": "^3.0.0"
58
+ },
59
+ "keywords": [
60
+ "hagicode",
61
+ "hagicode-org",
62
+ "hagilight",
63
+ "docusaurus",
64
+ "docusaurus-plugin",
65
+ "multilingual-documentation",
66
+ "hreflang",
67
+ "structured-data",
68
+ "rss",
69
+ "sitemap"
70
+ ],
71
+ "homepage": "https://github.com/HagiCode-org/hagilight#readme",
72
+ "bugs": {
73
+ "url": "https://github.com/HagiCode-org/hagilight/issues"
74
+ },
75
+ "license": "MIT",
76
+ "author": {
77
+ "name": "HagiCode",
78
+ "url": "https://github.com/HagiCode-org"
79
+ }
80
+ }