coherent-docs-theme 1.0.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 +314 -0
- package/assets/gameface-ui-header-dark.svg +5 -0
- package/assets/gameface-ui-header-light.svg +5 -0
- package/components/Api.astro +29 -0
- package/components/BeforeAfter.astro +124 -0
- package/components/Details.astro +37 -0
- package/components/Enhancement.astro +29 -0
- package/components/Feature.astro +29 -0
- package/components/Figure.astro +107 -0
- package/components/Fix.astro +29 -0
- package/components/GallerySlider.astro +260 -0
- package/components/If.astro +6 -0
- package/components/IfEnv.astro +5 -0
- package/components/IfNot.astro +6 -0
- package/components/IncludeSnippets.astro +13 -0
- package/components/Internal.astro +3 -0
- package/components/ProductName.astro +13 -0
- package/components/Release.astro +68 -0
- package/components/SideBarWithDropdown.astro +8 -0
- package/components/Typeref.astro +35 -0
- package/index.ts +152 -0
- package/internal/overrideComponents.ts +29 -0
- package/internal/themeConfig.ts +21 -0
- package/internal-components/ChangelogRow.astro +39 -0
- package/internal-components/NavLinks.astro +253 -0
- package/internal-components/ProgressIndicator.astro +53 -0
- package/overrides/Footer.astro +103 -0
- package/overrides/Header.astro +91 -0
- package/overrides/Search.astro +234 -0
- package/overrides/ThemeSelect.astro +218 -0
- package/package.json +46 -0
- package/remark-directives/if.ts +51 -0
- package/remark-directives/includeSnippets.ts +120 -0
- package/remark-directives/index.ts +13 -0
- package/remark-directives/internal.ts +20 -0
- package/remark-directives/release.ts +29 -0
- package/styles.css +452 -0
- package/utils/changelogSideBar.ts +105 -0
- package/utils/changelogSideBarMultipleDocs.ts +119 -0
- package/utils/coherentReleases.ts +62 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
import DefaultSearch from '@astrojs/starlight/components/Search.astro';
|
|
3
|
+
|
|
4
|
+
import getThemeConfig from "../internal/themeConfig";
|
|
5
|
+
const { documentationSearchTag } = getThemeConfig();
|
|
6
|
+
const isDev = import.meta.env.DEV;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<DefaultSearch {...Astro.props} />
|
|
10
|
+
|
|
11
|
+
<style is:global>
|
|
12
|
+
.pagefind-ui__result-excerpt mark {
|
|
13
|
+
font-weight: 900 !important;
|
|
14
|
+
background-color: rgba(255, 215, 0, 0.3) !important;
|
|
15
|
+
color: var(--sl-color-white) !important;
|
|
16
|
+
padding: 0 2px;
|
|
17
|
+
border-radius: 2px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.pagefind-ui__result-title::before,
|
|
21
|
+
.pagefind-ui__result-link::before,
|
|
22
|
+
.pagefind-ui__result-nested::before {
|
|
23
|
+
display: none !important;
|
|
24
|
+
content: none !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#starlight__search .pagefind-ui__result-inner>.pagefind-ui__result-title {
|
|
28
|
+
padding-inline-start: 1rem !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#starlight__search .pagefind-ui__result-inner>.pagefind-ui__result-nested {
|
|
32
|
+
padding-inline-start: 2rem !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.pagefind-ui__filter-checkbox,
|
|
36
|
+
.pagefind-ui__filter-checkbox::before {
|
|
37
|
+
display: none !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.pagefind-ui__filter-value::before {
|
|
41
|
+
display: none !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.pagefind-ui__filter-label::before,
|
|
45
|
+
.pagefind-ui__filter-label svg {
|
|
46
|
+
display: none !important;
|
|
47
|
+
content: none !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.pagefind-ui__filter-label {
|
|
51
|
+
display: inline-flex !important;
|
|
52
|
+
align-items: center !important;
|
|
53
|
+
justify-content: center !important;
|
|
54
|
+
cursor: pointer !important;
|
|
55
|
+
padding: 0.4rem 1rem !important;
|
|
56
|
+
margin: 0.2rem 0.3rem 0.2rem 0 !important;
|
|
57
|
+
font-size: inherit !important;
|
|
58
|
+
font-weight: 600 !important;
|
|
59
|
+
background-color: var(--sl-color-bg-nav) !important;
|
|
60
|
+
border: 1px solid var(--sl-color-gray-5) !important;
|
|
61
|
+
color: var(--sl-color-gray-3) !important;
|
|
62
|
+
transition: all 0.2s ease !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.pagefind-ui__filter-label:hover {
|
|
66
|
+
border-color: var(--sl-color-gray-2) !important;
|
|
67
|
+
color: var(--sl-color-white) !important;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.pagefind-ui__filter-checkbox:checked+.pagefind-ui__filter-label {
|
|
71
|
+
background-color: var(--sl-color-accent) !important;
|
|
72
|
+
border-color: var(--sl-color-accent) !important;
|
|
73
|
+
color: var(--gameface-bg-main) !important;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.custom-docs-badge {
|
|
77
|
+
color: white;
|
|
78
|
+
padding: 2px 8px;
|
|
79
|
+
border-radius: 12px;
|
|
80
|
+
font-size: 11px;
|
|
81
|
+
margin-right: 10px;
|
|
82
|
+
vertical-align: middle;
|
|
83
|
+
font-weight: bold;
|
|
84
|
+
text-transform: uppercase;
|
|
85
|
+
letter-spacing: 0.5px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (min-width: 50rem) {
|
|
89
|
+
site-search dialog {
|
|
90
|
+
max-width: 90%;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
|
|
95
|
+
{!isDev &&
|
|
96
|
+
<script is:inline define:vars={{documentationSearchTag}}>
|
|
97
|
+
const badgesConfig = {
|
|
98
|
+
"frontend-tools.coherent-labs.com/e2e": { text: "UI Tools | Gameface E2E", color: "#007aaa" },
|
|
99
|
+
"frontend-tools.coherent-labs.com/interaction-manager": { text: "UI Tools | Interaction Manager", color: "#007abb" },
|
|
100
|
+
"frontend-tools.coherent-labs.com/gameface-vite-plugin": { text: "UI Tools | Gameface Vite Plugin", color: "#007acc" },
|
|
101
|
+
"frontend-tools.coherent-labs.com/vite-solid-style-to-css-plugin": { text: "UI Tools | Solid Style to CSS Plugin", color: "#007add" },
|
|
102
|
+
"frontend-tools.coherent-labs.com/data-binding-autocomplete": { text: "UI Tools | Data Binding Autocomplete", color: "#007aee" },
|
|
103
|
+
"frontend-tools.coherent-labs.com/frontend-tools.coherent-labs.com": { text: "UI Tools", color: "#007aff" },
|
|
104
|
+
"gameface-ui": { text: "Gameface UI", color: "#e24a4a" },
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function setupSearchInputListener(searchInput, pagefind) {
|
|
108
|
+
if (!searchInput || searchInput.dataset.countListener) return;
|
|
109
|
+
|
|
110
|
+
searchInput.dataset.countListener = "true";
|
|
111
|
+
searchInput.addEventListener("input", async (e) => {
|
|
112
|
+
const term = e.target.value;
|
|
113
|
+
if (!term) {
|
|
114
|
+
window.__pagefindTrueCounts = {};
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const search = await pagefind.search(term);
|
|
118
|
+
window.__pagefindTrueCounts = search.filters?.resource || {};
|
|
119
|
+
searchInput.dataset.forceUpdate = Date.now();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function applyBadgesToResults(container) {
|
|
124
|
+
const resultTitles = container.querySelectorAll(".pagefind-ui__result-inner > p.pagefind-ui__result-title:not([data-badged])");
|
|
125
|
+
if (resultTitles.length === 0) return;
|
|
126
|
+
|
|
127
|
+
resultTitles.forEach((titleElement) => {
|
|
128
|
+
titleElement.dataset.badged = "true";
|
|
129
|
+
const link = titleElement.querySelector("a");
|
|
130
|
+
if (!link) return;
|
|
131
|
+
|
|
132
|
+
let badgeText = documentationSearchTag;
|
|
133
|
+
let badgeColor = "#883aea";
|
|
134
|
+
|
|
135
|
+
for (const key in badgesConfig) {
|
|
136
|
+
if (link.href.includes(key)) {
|
|
137
|
+
badgeText = badgesConfig[key].text;
|
|
138
|
+
badgeColor = badgesConfig[key].color;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const badge = document.createElement("span");
|
|
144
|
+
badge.className = "custom-docs-badge";
|
|
145
|
+
badge.textContent = badgeText;
|
|
146
|
+
badge.style.backgroundColor = badgeColor;
|
|
147
|
+
titleElement.prepend(badge);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function applyDefaultFilter(checkboxes) {
|
|
152
|
+
if (checkboxes.length === 0 || window.__pagefindDefaultApplied) return;
|
|
153
|
+
|
|
154
|
+
window.__pagefindDefaultApplied = true;
|
|
155
|
+
const defaultCb = Array.from(checkboxes).find(cb => cb.value === documentationSearchTag);
|
|
156
|
+
|
|
157
|
+
if (defaultCb && !defaultCb.checked) {
|
|
158
|
+
defaultCb.checked = true;
|
|
159
|
+
defaultCb.dispatchEvent(new Event("change", { bubbles: true }));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function processFiltersAndCounts(checkboxes, searchInput, observer, searchContainer) {
|
|
164
|
+
checkboxes.forEach((cb) => {
|
|
165
|
+
if (!cb.dataset.radioFixed) {
|
|
166
|
+
cb.dataset.radioFixed = "true";
|
|
167
|
+
cb.addEventListener("change", (e) => {
|
|
168
|
+
if (e.target.checked) {
|
|
169
|
+
checkboxes.forEach((other) => {
|
|
170
|
+
if (other !== e.target && other.checked) {
|
|
171
|
+
other.checked = false;
|
|
172
|
+
other.dispatchEvent(new Event("change", { bubbles: true }));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (window.__pagefindTrueCounts && searchInput && searchInput.value) {
|
|
180
|
+
const docName = cb.value;
|
|
181
|
+
const trueCount = window.__pagefindTrueCounts[docName] || 0;
|
|
182
|
+
const label = searchContainer.querySelector(`[for="resource-${docName}"]`);
|
|
183
|
+
|
|
184
|
+
if(!label) return;
|
|
185
|
+
|
|
186
|
+
const currentText = label.textContent;
|
|
187
|
+
const newText = currentText.replace(/\(\d+\)/, `(${trueCount})`);
|
|
188
|
+
|
|
189
|
+
if (currentText !== newText) {
|
|
190
|
+
observer.disconnect();
|
|
191
|
+
label.textContent = newText;
|
|
192
|
+
observer.observe(searchContainer, { childList: true, subtree: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
199
|
+
const searchContainer = document.getElementById("starlight__search");
|
|
200
|
+
if (!searchContainer) return;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const pagefind = await import("/pagefind/pagefind.js");
|
|
204
|
+
window.__pagefindTrueCounts = {};
|
|
205
|
+
|
|
206
|
+
let frameRequested = false;
|
|
207
|
+
|
|
208
|
+
const observer = new MutationObserver((mutations) => {
|
|
209
|
+
const hasInterestingChanges = mutations.some(m => m.addedNodes.length > 0);
|
|
210
|
+
if (!hasInterestingChanges && !window.__pagefindTrueCounts) return;
|
|
211
|
+
|
|
212
|
+
if (frameRequested) return;
|
|
213
|
+
frameRequested = true;
|
|
214
|
+
|
|
215
|
+
requestAnimationFrame(() => {
|
|
216
|
+
const searchInput = searchContainer.querySelector(".pagefind-ui__search-input");
|
|
217
|
+
const checkboxes = searchContainer.querySelectorAll(".pagefind-ui__filter-checkbox");
|
|
218
|
+
|
|
219
|
+
setupSearchInputListener(searchInput, pagefind);
|
|
220
|
+
applyBadgesToResults(searchContainer);
|
|
221
|
+
applyDefaultFilter(checkboxes);
|
|
222
|
+
processFiltersAndCounts(checkboxes, searchInput, observer, searchContainer);
|
|
223
|
+
|
|
224
|
+
frameRequested = false;
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
observer.observe(searchContainer, { childList: true, subtree: true });
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.warn("Failed to load raw Pagefind API for counts", e);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
</script>
|
|
234
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
|
|
4
|
+
const id = `moon-mask-${crypto.randomBytes(4).toString("hex")}`;
|
|
5
|
+
|
|
6
|
+
// https://web.dev/building-a-theme-switch-component/
|
|
7
|
+
// https://github.com/withastro/starlight/blob/9237581c766f68fbb3ce5f9401ca2046f106c7d5/packages/starlight/components/ThemeSelect.astro
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<starlight-coherent-docs-theme-select>
|
|
11
|
+
<button
|
|
12
|
+
aria-label={Astro.locals.t("themeSelect.accessibleLabel")}
|
|
13
|
+
aria-live="polite"
|
|
14
|
+
class="sl-flex"
|
|
15
|
+
title={Astro.locals.t("themeSelect.accessibleLabel")}
|
|
16
|
+
>
|
|
17
|
+
<svg aria-hidden="true" height="16" viewBox="0 0 24 24" width="16">
|
|
18
|
+
<mask class="moon" id={id}>
|
|
19
|
+
<rect x="0" y="0" width="100%" height="100%" fill="white"></rect>
|
|
20
|
+
<circle cx="24" cy="10" r="6" fill="black"></circle>
|
|
21
|
+
</mask>
|
|
22
|
+
<circle class="sun" cx="12" cy="12" r="6" mask={`url(#${id})`}></circle>
|
|
23
|
+
<g class="sun-beams" stroke="currentColor">
|
|
24
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
25
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
26
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
27
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
28
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
29
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
30
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
31
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
32
|
+
</g>
|
|
33
|
+
</svg>
|
|
34
|
+
</button>
|
|
35
|
+
</starlight-coherent-docs-theme-select>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
starlight-coherent-docs-theme-select {
|
|
39
|
+
--sl-rapide-theme-select-animation-duration: 400ms;
|
|
40
|
+
--sl-rapide-theme-select-ease-elastic: cubic-bezier(0.5, 1.25, 0.75, 1.25);
|
|
41
|
+
|
|
42
|
+
align-self: stretch;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
button {
|
|
46
|
+
align-items: center;
|
|
47
|
+
background-color: transparent;
|
|
48
|
+
border: none;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
height: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
svg {
|
|
54
|
+
stroke-linecap: round;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
svg :is(.moon, .sun, .sun-beams) {
|
|
58
|
+
transform-origin: center;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
svg :is(.moon, .sun) {
|
|
62
|
+
fill: var(--sl-color-text-accent);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
svg .sun-beams {
|
|
66
|
+
stroke: var(--sl-color-text-accent);
|
|
67
|
+
stroke-width: 2px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
button:is(:hover, :focus-visible) svg :is(.moon, .sun) {
|
|
71
|
+
fill: var(--sl-color-accent-high);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
button:is(:hover, :focus-visible) svg .sun-beams {
|
|
75
|
+
stroke: var(--sl-color-accent-high);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:global([data-theme="dark"]) svg .sun {
|
|
79
|
+
transform: scale(1.75);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:global([data-theme="dark"]) svg .sun-beams {
|
|
83
|
+
opacity: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:global([data-theme="dark"]) svg .moon circle {
|
|
87
|
+
transform: translateX(-7px);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@supports (cx: 1) {
|
|
91
|
+
:global([data-theme="dark"]) svg .moon circle {
|
|
92
|
+
cx: 17;
|
|
93
|
+
transform: translateX(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
98
|
+
svg .sun {
|
|
99
|
+
transition: transform var(--sl-rapide-theme-select-animation-duration)
|
|
100
|
+
var(--sl-rapide-theme-select-ease-elastic);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
svg .sun-beams {
|
|
104
|
+
transition:
|
|
105
|
+
opacity var(--sl-rapide-theme-select-animation-duration) ease,
|
|
106
|
+
transform var(--sl-rapide-theme-select-animation-duration)
|
|
107
|
+
var(--sl-rapide-theme-select-ease-elastic);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
svg .moon circle {
|
|
111
|
+
transition: transform
|
|
112
|
+
calc(var(--sl-rapide-theme-select-animation-duration) / 2) ease-out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@supports (cx: 1) {
|
|
116
|
+
svg .moon circle {
|
|
117
|
+
transition: cx
|
|
118
|
+
calc(var(--sl-rapide-theme-select-animation-duration) / 2) ease-out;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
:global([data-theme="dark"]) svg .sun {
|
|
123
|
+
transform: scale(1.75);
|
|
124
|
+
transition-duration: calc(
|
|
125
|
+
var(--sl-rapide-theme-select-animation-duration) / 2
|
|
126
|
+
);
|
|
127
|
+
transition-timing-function: ease;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:global([data-theme="dark"]) svg .sun-beams {
|
|
131
|
+
transform: rotateZ(-25deg);
|
|
132
|
+
transition-duration: calc(
|
|
133
|
+
var(--sl-rapide-theme-select-animation-duration) / 4
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:global([data-theme="dark"]) svg .moon circle {
|
|
138
|
+
transition-delay: calc(
|
|
139
|
+
var(--sl-rapide-theme-select-animation-duration) / 4
|
|
140
|
+
);
|
|
141
|
+
transition-duration: var(--sl-rapide-theme-select-animation-duration);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
145
|
+
|
|
146
|
+
{/* Inlined to avoid FOUC. Uses global scope from `ThemeProvider.astro` */}
|
|
147
|
+
<script is:inline>
|
|
148
|
+
StarlightThemeProvider.updatePickers();
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<script>
|
|
152
|
+
type Theme = "auto" | "dark" | "light";
|
|
153
|
+
|
|
154
|
+
/** Key in `localStorage` to store color theme preference at. */
|
|
155
|
+
const storageKey = "starlight-theme";
|
|
156
|
+
|
|
157
|
+
/** Get a typesafe theme string from any JS value (unknown values are coerced to `'auto'`). */
|
|
158
|
+
function parseTheme(theme: unknown): Theme {
|
|
159
|
+
return theme === "auto" || theme === "dark" || theme === "light"
|
|
160
|
+
? theme
|
|
161
|
+
: "auto";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Load the user’s preference from `localStorage`. */
|
|
165
|
+
function loadTheme(): Theme {
|
|
166
|
+
return parseTheme(
|
|
167
|
+
typeof localStorage !== "undefined" && localStorage.getItem(storageKey),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Store the user’s preference in `localStorage`. */
|
|
172
|
+
function storeTheme(theme: Theme): void {
|
|
173
|
+
if (typeof localStorage !== "undefined") {
|
|
174
|
+
localStorage.setItem(
|
|
175
|
+
storageKey,
|
|
176
|
+
theme === "light" || theme === "dark" ? theme : "",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Get the preferred system color scheme. */
|
|
182
|
+
function getPreferredColorScheme(): Theme {
|
|
183
|
+
return matchMedia("(prefers-color-scheme: light)").matches
|
|
184
|
+
? "light"
|
|
185
|
+
: "dark";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Update select menu UI, document theme, and local storage state. */
|
|
189
|
+
function onThemeChange(theme: Theme): void {
|
|
190
|
+
StarlightThemeProvider.updatePickers(theme);
|
|
191
|
+
document.documentElement.dataset["theme"] =
|
|
192
|
+
theme === "auto" ? getPreferredColorScheme() : theme;
|
|
193
|
+
storeTheme(theme);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// React to changes in system color scheme.
|
|
197
|
+
matchMedia(`(prefers-color-scheme: light)`).addEventListener("change", () => {
|
|
198
|
+
if (loadTheme() === "auto") onThemeChange("auto");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
customElements.define(
|
|
202
|
+
"starlight-coherent-docs-theme-select",
|
|
203
|
+
class CoherentThemeSelect extends HTMLElement {
|
|
204
|
+
constructor() {
|
|
205
|
+
super();
|
|
206
|
+
onThemeChange(loadTheme());
|
|
207
|
+
const button = this.querySelector("button");
|
|
208
|
+
button?.addEventListener("click", () => {
|
|
209
|
+
const theme = parseTheme(document.documentElement.dataset["theme"]);
|
|
210
|
+
const newTheme =
|
|
211
|
+
theme === "dark" ? "light" : theme === "light" ? "dark" : "auto";
|
|
212
|
+
onThemeChange(newTheme);
|
|
213
|
+
button?.setAttribute("aria-label", `${newTheme} theme`);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
</script>
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coherent-docs-theme",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Theme for all the coherent documentations",
|
|
6
|
+
"author": "CoherentLabs",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"publish:package": "npm i && npm publish"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.ts",
|
|
13
|
+
"./styles": "./styles.css",
|
|
14
|
+
"./components/*.astro": "./components/*.astro",
|
|
15
|
+
"./overrides/*.astro": "./overrides/*.astro",
|
|
16
|
+
"./assets/*": "./assets/*"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@astrojs/starlight": "^0.37.6",
|
|
20
|
+
"@types/mdast": "^4.0.4",
|
|
21
|
+
"@types/unist": "^3.0.3",
|
|
22
|
+
"astro": "^5.17.1"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"starlight-heading-badges": "^0.6.1",
|
|
26
|
+
"starlight-links-validator": "^0.19.2",
|
|
27
|
+
"starlight-sidebar-topics": "^0.6.2",
|
|
28
|
+
"starlight-sidebar-topics-dropdown": "^0.5.2",
|
|
29
|
+
"gray-matter": "^4.0.3"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@astrojs/starlight": ">=0.37"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/CoherentLabs/frontend-tools/tree/master/docs-theme/packages/coherent-docs-theme",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/CoherentLabs/frontend-tools/coherent-docs-theme.git",
|
|
44
|
+
"directory": "packages/coherent-docs-theme"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
|
|
4
|
+
const ifDirectiveNames = ['If', 'IfNot', 'IfEnv']
|
|
5
|
+
export function remarkIfDirective() {
|
|
6
|
+
return (tree: Root) => {
|
|
7
|
+
visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node: any, index, parent) => {
|
|
8
|
+
const nodeName = node.name;
|
|
9
|
+
if (!ifDirectiveNames.includes(nodeName) || index === undefined || !parent) return;
|
|
10
|
+
|
|
11
|
+
const attrs: Record<string, string | undefined> = {};
|
|
12
|
+
node.attributes?.forEach((attr: any) => {
|
|
13
|
+
if (attr.type === 'mdxJsxAttribute' && typeof attr.value === 'string') {
|
|
14
|
+
attrs[attr.name] = attr.value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const { product, type, env } = attrs;
|
|
19
|
+
|
|
20
|
+
const currentProduct = process.env.DOCS_PRODUCT;
|
|
21
|
+
const currentType = process.env.DOCS_TYPE;
|
|
22
|
+
const currentEnv = process.env.MODE;
|
|
23
|
+
|
|
24
|
+
let shouldRender = true;
|
|
25
|
+
|
|
26
|
+
switch (nodeName) {
|
|
27
|
+
case 'If':
|
|
28
|
+
if (product && product !== currentProduct) shouldRender = false;
|
|
29
|
+
if (type && type !== currentType) shouldRender = false;
|
|
30
|
+
break;
|
|
31
|
+
|
|
32
|
+
case 'IfNot':
|
|
33
|
+
if (product && product === currentProduct) shouldRender = false;
|
|
34
|
+
if (type && type === currentType) shouldRender = false;
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'IfEnv':
|
|
38
|
+
if (env && env !== currentEnv) shouldRender = false;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (shouldRender) {
|
|
43
|
+
parent.children.splice(index, 1, ...node.children);
|
|
44
|
+
return index;
|
|
45
|
+
} else {
|
|
46
|
+
parent.children.splice(index, 1);
|
|
47
|
+
return index;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { visit } from 'unist-util-visit';
|
|
5
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
6
|
+
import { mdxFromMarkdown } from 'mdast-util-mdx';
|
|
7
|
+
import { mdxjs } from 'micromark-extension-mdxjs';
|
|
8
|
+
import type { Root, Content, Heading } from 'mdast';
|
|
9
|
+
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
|
10
|
+
|
|
11
|
+
type SnippetTag = "changelog" | "rendering" | "content_development" | "migration" | "core" | "unreal_engine";
|
|
12
|
+
|
|
13
|
+
interface TagConfig {
|
|
14
|
+
title: string;
|
|
15
|
+
level: 1 | 2 | 3;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TAG_CONFIG: Record<SnippetTag, TagConfig> = {
|
|
19
|
+
changelog: { title: "Changelog", level: 1 },
|
|
20
|
+
migration: { title: "Migration guide", level: 1 },
|
|
21
|
+
content_development: { title: "Content Development", level: 2 },
|
|
22
|
+
core: { title: "Core", level: 2 },
|
|
23
|
+
rendering: { title: "Rendering", level: 2 },
|
|
24
|
+
unreal_engine: { title: "Unreal Engine", level: 2 },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const isDev = process.env.NODE_ENV === 'development' || process.env.MODE === 'development';
|
|
28
|
+
|
|
29
|
+
export function remarkIncludeSnippets() {
|
|
30
|
+
return (tree: Root) => {
|
|
31
|
+
|
|
32
|
+
visit(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement, index, parent) => {
|
|
33
|
+
if (node.name !== 'IncludeSnippets' || index === undefined || !parent) return;
|
|
34
|
+
|
|
35
|
+
const attrs = Object.fromEntries(
|
|
36
|
+
node.attributes.map((attr) => {
|
|
37
|
+
if ('name' in attr && 'value' in attr) {
|
|
38
|
+
return [attr.name, attr.value];
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const release = attrs.release as string;
|
|
45
|
+
const tag = attrs.tag as SnippetTag;
|
|
46
|
+
const noBullets = attrs.noBullets === 'true' || attrs.noBullets === true;
|
|
47
|
+
|
|
48
|
+
const currentConfig = TAG_CONFIG[tag];
|
|
49
|
+
if (!currentConfig || !release) return;
|
|
50
|
+
|
|
51
|
+
const folderName = release === 'next_release' ? 'next_release' : `Release_${release}`;
|
|
52
|
+
const releaseDir = path.resolve(`./src/content/docs/releases/${folderName}/`);
|
|
53
|
+
|
|
54
|
+
if (release === 'next_release' && !isDev) {
|
|
55
|
+
parent.children.splice(index, 1);
|
|
56
|
+
return index;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const injectedNodes: Content[] = [];
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(releaseDir)) {
|
|
62
|
+
const files = fs.readdirSync(releaseDir);
|
|
63
|
+
const matchingSnippets: Array<{ data: any; content: string }> = [];
|
|
64
|
+
|
|
65
|
+
for (const fileName of files) {
|
|
66
|
+
if (!fileName.startsWith('_')) continue;
|
|
67
|
+
if (!fileName.endsWith('.md') && !fileName.endsWith('.mdx')) continue;
|
|
68
|
+
|
|
69
|
+
const filePath = path.join(releaseDir, fileName);
|
|
70
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
71
|
+
const { data, content } = matter(fileContent);
|
|
72
|
+
|
|
73
|
+
if (data?.tag === tag && data?.draft !== true) {
|
|
74
|
+
matchingSnippets.push({ data, content });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
matchingSnippets.sort((a, b) => (a.data.weight || 0) - (b.data.weight || 0));
|
|
79
|
+
|
|
80
|
+
if (matchingSnippets.length > 0) {
|
|
81
|
+
injectedNodes.push({
|
|
82
|
+
type: 'heading',
|
|
83
|
+
depth: currentConfig.level as Heading['depth'],
|
|
84
|
+
data: {
|
|
85
|
+
hProperties: {
|
|
86
|
+
id: currentConfig.title.toLowerCase().replace(/[^a-z0-9]+/g, "-")
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
children: [{ type: 'text', value: currentConfig.title }]
|
|
90
|
+
} as Heading);
|
|
91
|
+
|
|
92
|
+
for (const snippet of matchingSnippets) {
|
|
93
|
+
if (!noBullets && snippet.data.title) {
|
|
94
|
+
injectedNodes.push({
|
|
95
|
+
type: 'heading',
|
|
96
|
+
depth: 3,
|
|
97
|
+
children: [{ type: 'text', value: snippet.data.title }]
|
|
98
|
+
} as Heading);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const snippetAst = fromMarkdown(snippet.content, {
|
|
102
|
+
extensions: [mdxjs()],
|
|
103
|
+
mdastExtensions: [mdxFromMarkdown()]
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
injectedNodes.push(...(snippetAst.children as Content[]));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (injectedNodes.length > 0) {
|
|
112
|
+
parent.children.splice(index, 1, ...injectedNodes);
|
|
113
|
+
return index + injectedNodes.length;
|
|
114
|
+
} else {
|
|
115
|
+
parent.children.splice(index, 1);
|
|
116
|
+
return index;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import remarkDirective from 'remark-directive';
|
|
2
|
+
import { remarkIfDirective } from "./if";
|
|
3
|
+
import { remarkIncludeSnippets } from "./includeSnippets";
|
|
4
|
+
import { remarkReleaseDirective } from './release';
|
|
5
|
+
import { remarkInternalDirective } from './internal';
|
|
6
|
+
|
|
7
|
+
export const directives = [
|
|
8
|
+
remarkDirective,
|
|
9
|
+
remarkIfDirective,
|
|
10
|
+
remarkInternalDirective,
|
|
11
|
+
remarkReleaseDirective,
|
|
12
|
+
remarkIncludeSnippets
|
|
13
|
+
];
|