@hagicode/hagilight-starlight 0.1.2-dev.8.1.a91b4b9 → 0.1.2-dev.9.1.eccbdf8
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/AIDisclosureNotice.astro +12 -0
- package/ContentLayoutToggle.astro +31 -0
- package/MarkdownContent.astro +36 -0
- package/PageTitle.astro +7 -0
- package/ai-disclosure-schema.mjs +9 -0
- package/ai-disclosures.mjs +108 -0
- package/content-width-i18n.mjs +16 -0
- package/content-width.css +51 -0
- package/content-width.mjs +46 -0
- package/index.mjs +93 -5
- package/package.json +7 -2
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getAIDisclosureCopy } from './ai-disclosures.mjs';
|
|
3
|
+
const { kind, lang, sourceHref } = Astro.props;
|
|
4
|
+
const copy = getAIDisclosureCopy(lang);
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<aside class="hagilight-ai-disclosure" role="note">
|
|
8
|
+
{kind === 'author' ? copy.author : copy.translation}
|
|
9
|
+
{kind === 'translation' && sourceHref && (
|
|
10
|
+
<> <a href={sourceHref}>{copy.source}</a></>
|
|
11
|
+
)}
|
|
12
|
+
</aside>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getContentWidthLabels } from './content-width-i18n.mjs';
|
|
3
|
+
const { lang } = Astro.locals.starlightRoute;
|
|
4
|
+
const labels = getContentWidthLabels(lang);
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class="hagilight-content-width-toggle" role="group" aria-label={labels.group}>
|
|
8
|
+
<button type="button" data-hagilight-content-width-choice="wide" aria-pressed="true">
|
|
9
|
+
{labels.wide}
|
|
10
|
+
</button>
|
|
11
|
+
<button type="button" data-hagilight-content-width-choice="narrow" aria-pressed="false">
|
|
12
|
+
{labels.narrow}
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
import {
|
|
18
|
+
restoreContentWidth,
|
|
19
|
+
setContentWidth,
|
|
20
|
+
synchronizeContentWidthFromStorage,
|
|
21
|
+
} from './content-width.mjs';
|
|
22
|
+
|
|
23
|
+
const root = document.documentElement;
|
|
24
|
+
restoreContentWidth(root);
|
|
25
|
+
document.querySelectorAll('[data-hagilight-content-width-choice]').forEach((button) => {
|
|
26
|
+
button.addEventListener('click', () => {
|
|
27
|
+
setContentWidth(button.dataset.hagilightContentWidthChoice, root);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
window.addEventListener('storage', (event) => synchronizeContentWidthFromStorage(event, root));
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import DefaultMarkdownContent from '@astrojs/starlight/components/MarkdownContent.astro';
|
|
4
|
+
import AIDisclosureNotice from './AIDisclosureNotice.astro';
|
|
5
|
+
import {
|
|
6
|
+
getSourcePath,
|
|
7
|
+
isTranslationLocale,
|
|
8
|
+
resolveAIDisclosureFlags,
|
|
9
|
+
} from './ai-disclosures.mjs';
|
|
10
|
+
|
|
11
|
+
const { aiDisclosures = { isAITranslation: false, isAIAuthor: false, sourceLocale: 'root' } } = Astro.props;
|
|
12
|
+
const route = Astro.locals.starlightRoute;
|
|
13
|
+
const isDocsEntry = route.entry.id !== '404' && !route.entry.id.endsWith('/404');
|
|
14
|
+
const flags = isDocsEntry
|
|
15
|
+
? resolveAIDisclosureFlags(route.entry.data, aiDisclosures)
|
|
16
|
+
: { isAITranslation: false, isAIAuthor: false };
|
|
17
|
+
const showTranslation = flags.isAITranslation
|
|
18
|
+
&& isTranslationLocale(route.entryMeta.locale, aiDisclosures.sourceLocale);
|
|
19
|
+
let sourceHref;
|
|
20
|
+
|
|
21
|
+
if (showTranslation) {
|
|
22
|
+
const docs = await getCollection('docs');
|
|
23
|
+
sourceHref = getSourcePath({
|
|
24
|
+
docs,
|
|
25
|
+
routeId: route.id,
|
|
26
|
+
currentLocale: route.locale,
|
|
27
|
+
sourceLocale: aiDisclosures.sourceLocale,
|
|
28
|
+
pathname: Astro.url.pathname,
|
|
29
|
+
baseUrl: import.meta.env.BASE_URL,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
{flags.isAIAuthor && <AIDisclosureNotice kind="author" lang={route.lang} />}
|
|
35
|
+
<DefaultMarkdownContent><slot /></DefaultMarkdownContent>
|
|
36
|
+
{showTranslation && <AIDisclosureNotice kind="translation" lang={route.lang} sourceHref={sourceHref} />}
|
package/PageTitle.astro
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const COPY = {
|
|
2
|
+
'de-DE': {
|
|
3
|
+
author: 'Dieser Inhalt wurde mit KI-Unterstützung erstellt. Bitte prüfen Sie wichtige Angaben.',
|
|
4
|
+
translation: 'Dieser Beitrag wurde mit KI übersetzt.',
|
|
5
|
+
source: 'Original ansehen',
|
|
6
|
+
},
|
|
7
|
+
'en-US': {
|
|
8
|
+
author: 'This content was created with AI assistance. Please verify important details.',
|
|
9
|
+
translation: 'This post was translated with AI.',
|
|
10
|
+
source: 'View source',
|
|
11
|
+
},
|
|
12
|
+
'es-ES': {
|
|
13
|
+
author: 'Este contenido se creó con ayuda de IA. Verifica los detalles importantes.',
|
|
14
|
+
translation: 'Esta publicación se tradujo con IA.',
|
|
15
|
+
source: 'Ver original',
|
|
16
|
+
},
|
|
17
|
+
'fr-FR': {
|
|
18
|
+
author: 'Ce contenu a été créé avec l’aide de l’IA. Vérifiez les informations importantes.',
|
|
19
|
+
translation: 'Cet article a été traduit avec l’IA.',
|
|
20
|
+
source: 'Voir la source',
|
|
21
|
+
},
|
|
22
|
+
'ja-JP': {
|
|
23
|
+
author: 'この記事は AI の支援を受けて作成されました。重要な情報はご確認ください。',
|
|
24
|
+
translation: 'この記事は AI によって翻訳されました。',
|
|
25
|
+
source: '原文を見る',
|
|
26
|
+
},
|
|
27
|
+
'ko-KR': {
|
|
28
|
+
author: '이 콘텐츠는 AI의 도움을 받아 작성되었습니다. 중요한 세부 정보는 확인해 주세요.',
|
|
29
|
+
translation: '이 게시물은 AI로 번역되었습니다.',
|
|
30
|
+
source: '원문 보기',
|
|
31
|
+
},
|
|
32
|
+
'pt-BR': {
|
|
33
|
+
author: 'Este conteúdo foi criado com auxílio de IA. Confira os detalhes importantes.',
|
|
34
|
+
translation: 'Esta publicação foi traduzida com IA.',
|
|
35
|
+
source: 'Ver original',
|
|
36
|
+
},
|
|
37
|
+
'ru-RU': {
|
|
38
|
+
author: 'Этот материал создан с помощью ИИ. Проверьте важные сведения.',
|
|
39
|
+
translation: 'Эта публикация переведена с помощью ИИ.',
|
|
40
|
+
source: 'Открыть оригинал',
|
|
41
|
+
},
|
|
42
|
+
'zh-CN': {
|
|
43
|
+
author: '本文内容由 AI 辅助创作。请核实重要信息。',
|
|
44
|
+
translation: '本文由 AI 翻译。',
|
|
45
|
+
source: '查看原文',
|
|
46
|
+
},
|
|
47
|
+
'zh-Hant': {
|
|
48
|
+
author: '本文內容由 AI 輔助創作。請核實重要資訊。',
|
|
49
|
+
translation: '本文由 AI 翻譯。',
|
|
50
|
+
source: '查看原文',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function resolveAIDisclosureFlags(frontmatter, defaults) {
|
|
55
|
+
for (const key of ['isAITranslation', 'isAIAuthor']) {
|
|
56
|
+
if (frontmatter[key] !== undefined && typeof frontmatter[key] !== 'boolean') {
|
|
57
|
+
throw new TypeError(`Hagilight frontmatter ${key} must be a boolean.`);
|
|
58
|
+
}
|
|
59
|
+
if (typeof defaults[key] !== 'boolean') {
|
|
60
|
+
throw new TypeError(`Hagilight aiDisclosures ${key} default must be a boolean.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
isAITranslation: frontmatter.isAITranslation ?? defaults.isAITranslation,
|
|
65
|
+
isAIAuthor: frontmatter.isAIAuthor ?? defaults.isAIAuthor,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getAIDisclosureCopy(lang) {
|
|
70
|
+
if (COPY[lang]) return COPY[lang];
|
|
71
|
+
const language = lang?.split('-')[0];
|
|
72
|
+
return COPY[language] ?? COPY['en-US'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getSourceEntryId(routeId, currentLocale, sourceLocale) {
|
|
76
|
+
const prefix = currentLocale && currentLocale !== 'root' ? `${currentLocale}/` : '';
|
|
77
|
+
const routeSlug = prefix && routeId.startsWith(prefix) ? routeId.slice(prefix.length) : routeId;
|
|
78
|
+
if (sourceLocale === 'root') return routeSlug === 'index' ? '' : routeSlug;
|
|
79
|
+
return `${sourceLocale}/${routeSlug || 'index'}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeDocsEntryId(id) {
|
|
83
|
+
return id === 'index' ? '' : id;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getSourcePath({ docs, routeId, currentLocale, sourceLocale, pathname, baseUrl }) {
|
|
87
|
+
const sourceId = getSourceEntryId(routeId, currentLocale, sourceLocale);
|
|
88
|
+
if (!docs.some((entry) => normalizeDocsEntryId(entry.id) === sourceId)) return undefined;
|
|
89
|
+
return buildSourcePathname(pathname, baseUrl, currentLocale, sourceLocale);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isTranslationLocale(locale, sourceLocale) {
|
|
93
|
+
return (locale ?? 'root') !== sourceLocale;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildSourcePathname(pathname, baseUrl, currentLocale, sourceLocale) {
|
|
97
|
+
const basePath = new URL(baseUrl, 'https://hagilight.invalid').pathname;
|
|
98
|
+
const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;
|
|
99
|
+
const relativePath = pathname.startsWith(normalizedBase)
|
|
100
|
+
? pathname.slice(normalizedBase.length)
|
|
101
|
+
: pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
|
102
|
+
const segments = relativePath.split('/');
|
|
103
|
+
if (currentLocale && currentLocale !== 'root' && segments[0] === currentLocale) segments.shift();
|
|
104
|
+
if (sourceLocale !== 'root') segments.unshift(sourceLocale);
|
|
105
|
+
return `${normalizedBase}${segments.join('/')}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { COPY };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const labels = {
|
|
2
|
+
'de-DE': { group: 'Inhaltsbreite', wide: 'Breit', narrow: 'Schmal' },
|
|
3
|
+
'en-US': { group: 'Content width', wide: 'Wide', narrow: 'Narrow' },
|
|
4
|
+
'es-ES': { group: 'Ancho del contenido', wide: 'Ancho', narrow: 'Estrecho' },
|
|
5
|
+
'fr-FR': { group: 'Largeur du contenu', wide: 'Large', narrow: 'Étroit' },
|
|
6
|
+
'ja-JP': { group: 'コンテンツ幅', wide: '広い', narrow: '狭い' },
|
|
7
|
+
'ko-KR': { group: '콘텐츠 너비', wide: '넓게', narrow: '좁게' },
|
|
8
|
+
'pt-BR': { group: 'Largura do conteúdo', wide: 'Ampla', narrow: 'Estreita' },
|
|
9
|
+
'ru-RU': { group: 'Ширина содержимого', wide: 'Широкая', narrow: 'Узкая' },
|
|
10
|
+
'zh-CN': { group: '内容宽度', wide: '宽', narrow: '窄' },
|
|
11
|
+
'zh-Hant': { group: '內容寬度', wide: '寬', narrow: '窄' },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function getContentWidthLabels(lang) {
|
|
15
|
+
return labels[lang] ?? labels['en-US'];
|
|
16
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@media (min-width: 50em) {
|
|
2
|
+
:root[data-hagilight-content-width='wide'] {
|
|
3
|
+
--sl-content-width: min(67.5rem, calc(100vw - 2rem));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.hagilight-content-width-toggle {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
gap: 0.25rem;
|
|
9
|
+
padding: 0.25rem;
|
|
10
|
+
border: 1px solid var(--sl-color-hairline);
|
|
11
|
+
border-radius: 0.5rem;
|
|
12
|
+
background: var(--sl-color-bg-sidebar);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.hagilight-content-width-toggle button {
|
|
16
|
+
border: 0;
|
|
17
|
+
border-radius: 0.25rem;
|
|
18
|
+
padding: 0.35rem 0.75rem;
|
|
19
|
+
color: var(--sl-color-text);
|
|
20
|
+
background: transparent;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.hagilight-content-width-toggle button[aria-pressed='true'] {
|
|
25
|
+
color: var(--sl-color-text-invert);
|
|
26
|
+
background: var(--sl-color-bg-accent);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.hagilight-content-width-toggle button:focus-visible {
|
|
30
|
+
outline: 2px solid var(--sl-color-accent);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@media (max-width: 49.999em) {
|
|
36
|
+
.hagilight-content-width-toggle {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.hagilight-ai-disclosure {
|
|
42
|
+
border-inline-start: 0.25rem solid var(--sl-color-accent);
|
|
43
|
+
padding: 0.75rem 1rem;
|
|
44
|
+
color: var(--sl-color-text);
|
|
45
|
+
background: var(--sl-color-bg-sidebar);
|
|
46
|
+
font-size: var(--sl-text-sm);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.hagilight-ai-disclosure a {
|
|
50
|
+
margin-inline-start: 0.5rem;
|
|
51
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const STORAGE_KEY = 'hagilight-content-width';
|
|
2
|
+
const VALID_MODES = new Set(['wide', 'narrow']);
|
|
3
|
+
|
|
4
|
+
function normalizeMode(value) {
|
|
5
|
+
return VALID_MODES.has(value) ? value : 'wide';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function restoreContentWidth(root = document.documentElement) {
|
|
9
|
+
let mode = 'wide';
|
|
10
|
+
try {
|
|
11
|
+
mode = normalizeMode(globalThis.localStorage.getItem(STORAGE_KEY));
|
|
12
|
+
} catch {
|
|
13
|
+
mode = 'wide';
|
|
14
|
+
}
|
|
15
|
+
root.dataset.hagilightContentWidth = mode;
|
|
16
|
+
synchronizeButtons(root, mode);
|
|
17
|
+
return mode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function setContentWidth(mode, root = document.documentElement) {
|
|
21
|
+
if (!VALID_MODES.has(mode)) {
|
|
22
|
+
throw new TypeError('Hagilight content width must be "wide" or "narrow".');
|
|
23
|
+
}
|
|
24
|
+
root.dataset.hagilightContentWidth = mode;
|
|
25
|
+
synchronizeButtons(root, mode);
|
|
26
|
+
try {
|
|
27
|
+
globalThis.localStorage.setItem(STORAGE_KEY, mode);
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function synchronizeContentWidthFromStorage(event, root = document.documentElement) {
|
|
34
|
+
if (event.key !== STORAGE_KEY && event.key !== null) return;
|
|
35
|
+
const mode = normalizeMode(event.newValue);
|
|
36
|
+
root.dataset.hagilightContentWidth = mode;
|
|
37
|
+
synchronizeButtons(root, mode);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function synchronizeButtons(root, mode) {
|
|
41
|
+
root.querySelectorAll('[data-hagilight-content-width-choice]').forEach((button) => {
|
|
42
|
+
button.setAttribute('aria-pressed', String(button.dataset.hagilightContentWidthChoice === mode));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { STORAGE_KEY };
|
package/index.mjs
CHANGED
|
@@ -3,18 +3,30 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
|
|
4
4
|
const GOOGLE_ID_PATTERN = /^G-[A-Z0-9]+$/u;
|
|
5
5
|
const FIFTY_ONE_LA_ID_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
6
|
+
const DEFAULT_GOOGLE_ANALYTICS_ID = 'G-EN03FMT2Q4';
|
|
7
|
+
const DEFAULT_51LA_ID = 'L6b88a5yK4h2Xnci';
|
|
6
8
|
const defaultLogo = fileURLToPath(import.meta.resolve('@hagicode/hagilight/logo.png'));
|
|
9
|
+
const contentWidthCssPath = fileURLToPath(new URL('./content-width.css', import.meta.url));
|
|
10
|
+
const contentWidthHeadScript = `(() => {
|
|
11
|
+
let mode = 'wide';
|
|
12
|
+
try {
|
|
13
|
+
const stored = localStorage.getItem('hagilight-content-width');
|
|
14
|
+
if (stored === 'wide' || stored === 'narrow') mode = stored;
|
|
15
|
+
} catch {
|
|
16
|
+
mode = 'wide';
|
|
17
|
+
}
|
|
18
|
+
document.documentElement.dataset.hagilightContentWidth = mode;
|
|
19
|
+
})();`;
|
|
7
20
|
|
|
8
|
-
function resolveProvider(config, { name, idKey, pattern }) {
|
|
9
|
-
if (config
|
|
10
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
21
|
+
function resolveProvider(config, { name, idKey, pattern, defaultId }) {
|
|
22
|
+
if (config !== undefined && (!config || typeof config !== 'object' || Array.isArray(config))) {
|
|
11
23
|
throw new TypeError(`Hagilight ${name} analytics options must be an object.`);
|
|
12
24
|
}
|
|
13
25
|
|
|
14
|
-
const enabled = config.enabled ?? Boolean(config[idKey]);
|
|
26
|
+
const enabled = config === undefined ? true : config.enabled ?? Boolean(config[idKey]);
|
|
15
27
|
if (!enabled) return undefined;
|
|
16
28
|
|
|
17
|
-
const id = config[idKey];
|
|
29
|
+
const id = config === undefined ? defaultId : config[idKey];
|
|
18
30
|
if (typeof id !== 'string' || !id.trim()) {
|
|
19
31
|
throw new Error(`Hagilight ${name} analytics is enabled but its ${idKey} is missing.`);
|
|
20
32
|
}
|
|
@@ -24,12 +36,41 @@ function resolveProvider(config, { name, idKey, pattern }) {
|
|
|
24
36
|
return id;
|
|
25
37
|
}
|
|
26
38
|
|
|
39
|
+
function resolveAIDisclosures(config) {
|
|
40
|
+
if (config === undefined) {
|
|
41
|
+
return {
|
|
42
|
+
isAITranslation: false,
|
|
43
|
+
isAIAuthor: false,
|
|
44
|
+
sourceLocale: 'root',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
48
|
+
throw new TypeError('Hagilight aiDisclosures options must be an object.');
|
|
49
|
+
}
|
|
50
|
+
for (const key of ['isAITranslation', 'isAIAuthor']) {
|
|
51
|
+
if (config[key] !== undefined && typeof config[key] !== 'boolean') {
|
|
52
|
+
throw new TypeError(`Hagilight aiDisclosures ${key} option must be a boolean.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const sourceLocale = config.sourceLocale === undefined ? 'root' : config.sourceLocale;
|
|
56
|
+
if (typeof sourceLocale !== 'string' || !sourceLocale.trim() || /[/\\]/u.test(sourceLocale)) {
|
|
57
|
+
throw new TypeError('Hagilight aiDisclosures sourceLocale option must be a non-empty string.');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
isAITranslation: config.isAITranslation ?? false,
|
|
61
|
+
isAIAuthor: config.isAIAuthor ?? false,
|
|
62
|
+
sourceLocale: sourceLocale.trim(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
function createConfiguredIntegration(instanceId, serializedOptions, componentIds) {
|
|
28
67
|
const optionsId = `virtual:hagilight-starlight/${instanceId}/options`;
|
|
29
68
|
const headerPath = componentIds.header
|
|
30
69
|
? fileURLToPath(new URL('./Header.astro', import.meta.url))
|
|
31
70
|
: undefined;
|
|
32
71
|
const footerPath = fileURLToPath(new URL('./Footer.astro', import.meta.url));
|
|
72
|
+
const pageTitlePath = fileURLToPath(new URL('./PageTitle.astro', import.meta.url));
|
|
73
|
+
const markdownContentPath = fileURLToPath(new URL('./MarkdownContent.astro', import.meta.url));
|
|
33
74
|
const headerSource = headerPath
|
|
34
75
|
? `---
|
|
35
76
|
import Header from ${JSON.stringify(headerPath)};
|
|
@@ -59,6 +100,19 @@ import options from '${optionsId}';
|
|
|
59
100
|
<Footer locale={Astro.locals?.starlightRoute?.locale} links={options.links} />
|
|
60
101
|
${promotionRender}
|
|
61
102
|
${analyticsRender}
|
|
103
|
+
`;
|
|
104
|
+
const pageTitleSource = `---
|
|
105
|
+
import PageTitle from ${JSON.stringify(pageTitlePath)};
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
<PageTitle />
|
|
109
|
+
`;
|
|
110
|
+
const markdownContentSource = `---
|
|
111
|
+
import MarkdownContent from ${JSON.stringify(markdownContentPath)};
|
|
112
|
+
import options from '${optionsId}';
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
<MarkdownContent aiDisclosures={options.aiDisclosures}><slot /></MarkdownContent>
|
|
62
116
|
`;
|
|
63
117
|
const headSource = `---
|
|
64
118
|
import DefaultHead from '@astrojs/starlight/components/Head.astro';
|
|
@@ -75,6 +129,8 @@ const isNotFound = /(?:^|\\/)404(?:\\.html)?\\/?$/u.test(Astro.url.pathname);
|
|
|
75
129
|
const modules = new Map([
|
|
76
130
|
...(componentIds.header ? [[componentIds.header, headerSource]] : []),
|
|
77
131
|
[componentIds.footer, footerSource],
|
|
132
|
+
...(componentIds.pageTitle ? [[componentIds.pageTitle, pageTitleSource]] : []),
|
|
133
|
+
...(componentIds.markdownContent ? [[componentIds.markdownContent, markdownContentSource]] : []),
|
|
78
134
|
...(componentIds.head ? [[componentIds.head, headSource]] : []),
|
|
79
135
|
[optionsId, `export default ${JSON.stringify(serializedOptions)};`],
|
|
80
136
|
]);
|
|
@@ -113,24 +169,42 @@ export default function hagilight(options = {}) {
|
|
|
113
169
|
&& (!options.analytics || typeof options.analytics !== 'object' || Array.isArray(options.analytics))) {
|
|
114
170
|
throw new TypeError('Hagilight analytics options must be an object.');
|
|
115
171
|
}
|
|
172
|
+
if (options.contentComponents !== undefined
|
|
173
|
+
&& (!options.contentComponents || typeof options.contentComponents !== 'object' || Array.isArray(options.contentComponents))) {
|
|
174
|
+
throw new TypeError('Hagilight contentComponents options must be an object.');
|
|
175
|
+
}
|
|
176
|
+
for (const key of ['pageTitle', 'markdownContent']) {
|
|
177
|
+
if (options.contentComponents?.[key] !== undefined && typeof options.contentComponents[key] !== 'boolean') {
|
|
178
|
+
throw new TypeError(`Hagilight contentComponents ${key} option must be a boolean.`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
116
181
|
const analytics = options.analytics ?? {};
|
|
182
|
+
const aiDisclosures = resolveAIDisclosures(options.aiDisclosures);
|
|
117
183
|
const googleAnalyticsMeasurementId = resolveProvider(analytics.googleAnalytics, {
|
|
118
184
|
name: 'Google Analytics',
|
|
119
185
|
idKey: 'measurementId',
|
|
120
186
|
pattern: GOOGLE_ID_PATTERN,
|
|
187
|
+
defaultId: DEFAULT_GOOGLE_ANALYTICS_ID,
|
|
121
188
|
});
|
|
122
189
|
const fiftyOneLaId = resolveProvider(analytics.fiftyOneLa, {
|
|
123
190
|
name: '51LA',
|
|
124
191
|
idKey: 'siteId',
|
|
125
192
|
pattern: FIFTY_ONE_LA_ID_PATTERN,
|
|
193
|
+
defaultId: DEFAULT_51LA_ID,
|
|
126
194
|
});
|
|
127
195
|
const instanceId = randomUUID();
|
|
128
196
|
const headerEnabled = options.header?.enabled !== false;
|
|
197
|
+
const pageTitleEnabled = options.contentComponents?.pageTitle !== false;
|
|
198
|
+
const markdownContentEnabled = options.contentComponents?.markdownContent !== false;
|
|
129
199
|
const componentIds = {
|
|
130
200
|
header: headerEnabled
|
|
131
201
|
? `virtual:hagilight-starlight/${instanceId}/Header.astro`
|
|
132
202
|
: undefined,
|
|
133
203
|
footer: `virtual:hagilight-starlight/${instanceId}/Footer.astro`,
|
|
204
|
+
pageTitle: pageTitleEnabled ? `virtual:hagilight-starlight/${instanceId}/PageTitle.astro` : undefined,
|
|
205
|
+
markdownContent: markdownContentEnabled
|
|
206
|
+
? `virtual:hagilight-starlight/${instanceId}/MarkdownContent.astro`
|
|
207
|
+
: undefined,
|
|
134
208
|
head: googleAnalyticsMeasurementId
|
|
135
209
|
? `virtual:hagilight-starlight/${instanceId}/Head.astro`
|
|
136
210
|
: undefined,
|
|
@@ -140,6 +214,7 @@ export default function hagilight(options = {}) {
|
|
|
140
214
|
links: options.links ?? {},
|
|
141
215
|
googleAnalyticsMeasurementId,
|
|
142
216
|
fiftyOneLaId,
|
|
217
|
+
aiDisclosures,
|
|
143
218
|
};
|
|
144
219
|
|
|
145
220
|
return {
|
|
@@ -152,6 +227,12 @@ export default function hagilight(options = {}) {
|
|
|
152
227
|
if (config.components?.Footer) {
|
|
153
228
|
throw new Error('Hagilight cannot replace an existing Starlight Footer override.');
|
|
154
229
|
}
|
|
230
|
+
if (pageTitleEnabled && config.components?.PageTitle) {
|
|
231
|
+
throw new Error('Hagilight cannot replace an existing Starlight PageTitle override. Set contentComponents: { pageTitle: false } and compose @hagicode/hagilight-starlight/ContentLayoutToggle into your PageTitle component.');
|
|
232
|
+
}
|
|
233
|
+
if (markdownContentEnabled && config.components?.MarkdownContent) {
|
|
234
|
+
throw new Error('Hagilight cannot replace an existing Starlight MarkdownContent override. Set contentComponents: { markdownContent: false } and compose @hagicode/hagilight-starlight/MarkdownContent into your MarkdownContent component.');
|
|
235
|
+
}
|
|
155
236
|
if (googleAnalyticsMeasurementId && config.components?.Head) {
|
|
156
237
|
throw new Error('Hagilight Google Analytics cannot replace an existing Starlight Head override. Compose @hagicode/hagilight/GoogleAnalytics in your Head instead.');
|
|
157
238
|
}
|
|
@@ -159,10 +240,17 @@ export default function hagilight(options = {}) {
|
|
|
159
240
|
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, componentIds));
|
|
160
241
|
updateConfig({
|
|
161
242
|
...(config.logo === undefined ? { logo: { src: defaultLogo, alt: 'HagiCode' } } : {}),
|
|
243
|
+
customCss: [...(config.customCss ?? []), contentWidthCssPath],
|
|
244
|
+
head: [
|
|
245
|
+
...(config.head ?? []),
|
|
246
|
+
{ tag: 'script', content: contentWidthHeadScript },
|
|
247
|
+
],
|
|
162
248
|
components: {
|
|
163
249
|
...config.components,
|
|
164
250
|
...(componentIds.header ? { Header: componentIds.header } : {}),
|
|
165
251
|
Footer: componentIds.footer,
|
|
252
|
+
...(componentIds.pageTitle ? { PageTitle: componentIds.pageTitle } : {}),
|
|
253
|
+
...(componentIds.markdownContent ? { MarkdownContent: componentIds.markdownContent } : {}),
|
|
166
254
|
...(componentIds.head ? { Head: componentIds.head } : {}),
|
|
167
255
|
},
|
|
168
256
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight-starlight",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.9.1.eccbdf8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Starlight customizations for HagiCode documentation",
|
|
6
6
|
"repository": {
|
|
@@ -14,17 +14,22 @@
|
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./index.mjs",
|
|
17
|
+
"./ai-disclosure-schema": "./ai-disclosure-schema.mjs",
|
|
18
|
+
"./ContentLayoutToggle": "./ContentLayoutToggle.astro",
|
|
17
19
|
"./Header": "./Header.astro",
|
|
18
20
|
"./Footer": "./Footer.astro",
|
|
21
|
+
"./MarkdownContent": "./MarkdownContent.astro",
|
|
22
|
+
"./PageTitle": "./PageTitle.astro",
|
|
19
23
|
"./PromotoFooter": "./PromotoFooter.astro"
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
22
26
|
"index.mjs",
|
|
27
|
+
"*.css",
|
|
23
28
|
"*.mjs",
|
|
24
29
|
"*.astro"
|
|
25
30
|
],
|
|
26
31
|
"dependencies": {
|
|
27
|
-
"@hagicode/hagilight": "0.1.2-dev.
|
|
32
|
+
"@hagicode/hagilight": "0.1.2-dev.9.1.eccbdf8"
|
|
28
33
|
},
|
|
29
34
|
"peerDependencies": {
|
|
30
35
|
"@astrojs/starlight": "^0.42.4",
|