coherent-docs-theme 1.0.6 → 1.0.7

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.
@@ -0,0 +1,341 @@
1
+ ---
2
+ import DefaultPageTitle from "@astrojs/starlight/components/PageTitle.astro";
3
+ import SidebarSublist from "@astrojs/starlight/components/SidebarSublist.astro";
4
+ import getThemeConfig from "../internal/themeConfig";
5
+ import readingTime from "reading-time";
6
+ import BreadcrumbArrow from "../internal-components/BreadcrumbArrow.astro";
7
+
8
+ const { breadcrumbs: showBreadcrumbs } = getThemeConfig();
9
+ //@ts-ignore
10
+ const routeData = Astro.locals.starlightRoute ?? Astro.props;
11
+ const { sidebar, entry } = routeData;
12
+
13
+ //@ts-ignore
14
+ function findActiveTrail(entries, currentTrail = []) {
15
+ if (!entries) return null;
16
+
17
+ for (const item of entries) {
18
+ if (item.type === "link" && item.isCurrent) {
19
+ return [...currentTrail, item];
20
+ }
21
+ if (item.type === "group") {
22
+ //@ts-ignore
23
+ const trail = findActiveTrail(item.entries, [
24
+ //@ts-ignore
25
+ ...currentTrail,
26
+ //@ts-ignore
27
+ item,
28
+ ]);
29
+ if (trail) return trail;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ const activeTrail = findActiveTrail(sidebar) || [];
36
+ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
37
+
38
+ const stats = readingTime(entry?.body || "");
39
+ ---
40
+
41
+ {
42
+ showBreadcrumbs && activeTrail.length > 0 && (
43
+ <div class="breadcrumb-header">
44
+ <nav aria-label="Breadcrumb" class="breadcrumbs">
45
+ <ol>
46
+ <li>
47
+ <a href={`${base}/`} class="link">
48
+ Home
49
+ </a>
50
+ </li>
51
+
52
+ {activeTrail
53
+ .slice(0, -1)
54
+ .map((group: any, index: number) => (
55
+ <li class="dropdown-wrapper sidebar-content">
56
+ <details class="crumb-details">
57
+ <summary class="crumb-summary link">
58
+ {group.label}
59
+ <BreadcrumbArrow />
60
+ </summary>
61
+
62
+ <div
63
+ class="dropdown-menu"
64
+ data-menu-id={`menu-${index}`}
65
+ >
66
+ <SidebarSublist
67
+ sublist={group.entries}
68
+ />
69
+ </div>
70
+ </details>
71
+ {index === activeTrail.length - 1 && (
72
+ <BreadcrumbArrow />
73
+ )}
74
+ </li>
75
+ ))}
76
+
77
+ <li class="end-label">
78
+ <BreadcrumbArrow />
79
+ <span class="current" aria-current="page">
80
+ {activeTrail[activeTrail.length - 1]?.label ||
81
+ entry?.data?.title ||
82
+ "Page"}
83
+ </span>
84
+ </li>
85
+ </ol>
86
+ </nav>
87
+ <div class="read-time">
88
+ <svg
89
+ width="14"
90
+ height="14"
91
+ viewBox="0 0 24 24"
92
+ fill="none"
93
+ stroke="currentColor"
94
+ stroke-width="2"
95
+ stroke-linecap="round"
96
+ stroke-linejoin="round"
97
+ >
98
+ <circle cx="12" cy="12" r="10" />
99
+ <polyline points="12 6 12 12 16 14" />
100
+ </svg>
101
+ READ TIME: {Math.ceil(stats.minutes)} MIN
102
+ </div>
103
+ </div>
104
+ )
105
+ }
106
+
107
+ <DefaultPageTitle {...Astro.props} />
108
+
109
+ <script>
110
+ const detailsElements = document.querySelectorAll(".crumb-details");
111
+
112
+ document.addEventListener("click", (e) => {
113
+ const target = e.target as HTMLElement;
114
+ if (!target.closest(".dropdown-wrapper")) {
115
+ detailsElements.forEach((details) => {
116
+ details.removeAttribute("open");
117
+ details
118
+ .closest(".dropdown-wrapper")
119
+ ?.classList.remove("is-ready", "is-overflowing");
120
+ });
121
+ }
122
+ });
123
+
124
+ detailsElements.forEach((details) => {
125
+ const summary = details.querySelector(".crumb-summary") as HTMLElement;
126
+ const wrapper = details.closest(".dropdown-wrapper") as HTMLElement;
127
+ const menu = details.querySelector(".dropdown-menu") as HTMLElement;
128
+
129
+ summary.addEventListener("click", (e) => {
130
+ e.preventDefault();
131
+
132
+ const isOpen = details.hasAttribute("open");
133
+
134
+ if (isOpen) {
135
+ details.removeAttribute("open");
136
+ wrapper.classList.remove("is-ready", "is-overflowing");
137
+ } else {
138
+ detailsElements.forEach((otherDetails) => {
139
+ if (
140
+ otherDetails !== details &&
141
+ otherDetails.hasAttribute("open")
142
+ ) {
143
+ otherDetails.removeAttribute("open");
144
+ otherDetails
145
+ .closest(".dropdown-wrapper")
146
+ ?.classList.remove("is-ready", "is-overflowing");
147
+ }
148
+ });
149
+
150
+ details.setAttribute("open", "");
151
+ wrapper.classList.remove("is-overflowing", "is-ready");
152
+
153
+ const rect = menu.getBoundingClientRect();
154
+
155
+ if (rect.right > window.innerWidth - 20) {
156
+ wrapper.classList.add("is-overflowing");
157
+ }
158
+
159
+ wrapper.classList.add("is-ready");
160
+ }
161
+ });
162
+ });
163
+
164
+ window.addEventListener("resize", () => {
165
+ detailsElements.forEach((details) => {
166
+ if (details.hasAttribute("open")) {
167
+ const wrapper = details.closest(
168
+ ".dropdown-wrapper",
169
+ ) as HTMLElement;
170
+ const menu = details.querySelector(
171
+ ".dropdown-menu",
172
+ ) as HTMLElement;
173
+
174
+ wrapper.classList.remove("is-overflowing");
175
+ const rect = menu.getBoundingClientRect();
176
+
177
+ if (rect.right > window.innerWidth - 20) {
178
+ wrapper.classList.add("is-overflowing");
179
+ }
180
+ }
181
+ });
182
+ });
183
+ </script>
184
+
185
+ <style>
186
+ .breadcrumb-header {
187
+ display: flex;
188
+ justify-content: space-between;
189
+ align-items: flex-end;
190
+ gap: 1rem;
191
+ margin-bottom: 1.5rem;
192
+ position: relative;
193
+ flex-wrap: wrap-reverse;
194
+ z-index: 10;
195
+ }
196
+
197
+ .breadcrumbs {
198
+ font-size: 0.85rem;
199
+ color: var(--sl-color-gray-3);
200
+ }
201
+
202
+ .read-time {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 0.35rem;
206
+ font-size: 0.8rem;
207
+ color: var(--sl-color-gray-2);
208
+ white-space: nowrap;
209
+ padding-top: 0.1rem;
210
+ }
211
+
212
+ .breadcrumbs ol {
213
+ list-style: none;
214
+ padding: 0;
215
+ margin: 0;
216
+ display: flex;
217
+ flex-wrap: wrap;
218
+ align-items: center;
219
+ gap: 0.5rem;
220
+ }
221
+
222
+ .breadcrumbs > ol > li {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 0.5rem;
226
+ }
227
+
228
+ .breadcrumbs .link,
229
+ .crumb-summary {
230
+ color: var(--sl-color-gray-3);
231
+ text-decoration: none;
232
+ transition: color 0.2s ease;
233
+ cursor: pointer;
234
+ display: flex;
235
+ align-items: center;
236
+ gap: 0.25rem;
237
+ user-select: none;
238
+ }
239
+
240
+ .breadcrumbs .link:hover,
241
+ .breadcrumbs .link:focus,
242
+ .crumb-summary:hover,
243
+ .crumb-summary:focus {
244
+ color: var(--sl-color-white);
245
+ }
246
+
247
+ .breadcrumbs .current {
248
+ color: var(--sl-color-white);
249
+ font-weight: 800;
250
+ }
251
+
252
+ .breadcrumbs .separator {
253
+ color: var(--sl-color-gray-4);
254
+ user-select: none;
255
+ }
256
+
257
+ .breadcrumbs :global(svg) {
258
+ transition: transform 0.2s ease-in-out;
259
+ }
260
+
261
+ .crumb-summary::marker,
262
+ .crumb-summary::-webkit-details-marker {
263
+ display: none;
264
+ content: "";
265
+ }
266
+
267
+ .crumb-summary :global(svg),
268
+ .breadcrumbs .end-label :global(svg) {
269
+ transform: rotate(-90deg);
270
+ }
271
+
272
+ .crumb-details[open] .crumb-summary :global(svg) {
273
+ transform: rotate(0deg);
274
+ }
275
+
276
+ .dropdown-wrapper {
277
+ position: relative;
278
+ }
279
+
280
+ .dropdown-menu {
281
+ position: absolute;
282
+ top: calc(100% + 0.5rem);
283
+ left: 0;
284
+ background-color: var(--sl-color-black);
285
+ border: 1px solid var(--sl-color-hairline);
286
+ border-radius: 0.5rem;
287
+ padding: 0.5rem 0.5rem 0.5rem 0rem;
288
+ min-width: 15rem;
289
+ width: max-content;
290
+ max-height: 50vh;
291
+ overflow-y: auto;
292
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
293
+ z-index: 20;
294
+ visibility: hidden;
295
+ }
296
+
297
+ .dropdown-wrapper.is-ready .dropdown-menu {
298
+ visibility: visible;
299
+ }
300
+
301
+ .dropdown-wrapper.is-overflowing {
302
+ position: static;
303
+ }
304
+
305
+ .dropdown-wrapper.is-overflowing .dropdown-menu {
306
+ left: 0;
307
+ width: 100%;
308
+ max-width: 100%;
309
+ min-width: 0;
310
+ }
311
+
312
+ .dropdown-menu :global(ul) {
313
+ margin-top: 0 !important;
314
+ padding-inline-start: 0 !important;
315
+ }
316
+
317
+ .dropdown-menu :global(.top-level > li) {
318
+ margin-inline-start: 0 !important;
319
+ }
320
+ .dropdown-menu :global(li) {
321
+ margin-top: 0rem !important;
322
+ margin-inline-start: calc(
323
+ var(--sl-sidebar-item-padding-inline) + (1rem / 2)
324
+ ) !important;
325
+ display: block !important;
326
+ }
327
+
328
+ .dropdown-menu :global(a) {
329
+ font-weight: normal !important;
330
+ font-size: 0.9rem !important;
331
+ padding: 0.2rem 0.5rem !important;
332
+ }
333
+
334
+ .dropdown-menu :global(details) {
335
+ margin-top: 0 !important;
336
+ }
337
+ .dropdown-menu :global(summary) {
338
+ font-weight: normal !important;
339
+ padding: 0.2rem 0.5rem !important;
340
+ }
341
+ </style>
@@ -2,8 +2,10 @@
2
2
  import DefaultSearch from '@astrojs/starlight/components/Search.astro';
3
3
 
4
4
  import getThemeConfig from "../internal/themeConfig";
5
- const { documentationSearchTag } = getThemeConfig();
5
+ const { documentationSearchTag, tagManagerId } = getThemeConfig();
6
6
  const isDev = import.meta.env.DEV;
7
+ const baseUrl = import.meta.env.BASE_URL === '/' ? '' : import.meta.env.BASE_URL;
8
+
7
9
  ---
8
10
 
9
11
  <DefaultSearch {...Astro.props} />
@@ -93,7 +95,7 @@ const isDev = import.meta.env.DEV;
93
95
  </style>
94
96
 
95
97
  {!isDev &&
96
- <script is:inline define:vars={{documentationSearchTag}}>
98
+ <script is:inline define:vars={{documentationSearchTag, baseUrl, tagManagerId}}>
97
99
  const badgesConfig = {
98
100
  "frontend-tools.coherent-labs.com/e2e": { text: "UI Tools | Gameface E2E", color: "#007aaa" },
99
101
  "frontend-tools.coherent-labs.com/interaction-manager": { text: "UI Tools | Interaction Manager", color: "#007abb" },
@@ -102,6 +104,8 @@ const isDev = import.meta.env.DEV;
102
104
  "frontend-tools.coherent-labs.com/data-binding-autocomplete": { text: "UI Tools | Data Binding Autocomplete", color: "#007aee" },
103
105
  "frontend-tools.coherent-labs.com": { text: "UI Tools", color: "#007aff" },
104
106
  "gameface-ui.coherent-labs.com": { text: "Gameface UI", color: "#e24a4a" },
107
+ "docs.coherent-labs.com/unreal-gameface": { text: "Gameface Unreal", color: "#C35A1C" },
108
+ "docs.coherent-labs.com/unreal-prysm": { text: "Prysm Unreal", color: "#00897B" },
105
109
  }
106
110
 
107
111
  function setupSearchInputListener(searchInput, pagefind) {
@@ -199,8 +203,29 @@ const isDev = import.meta.env.DEV;
199
203
  const searchContainer = document.getElementById("starlight__search");
200
204
  if (!searchContainer) return;
201
205
 
206
+ if (tagManagerId) {
207
+ searchContainer.addEventListener("click", (event) => {
208
+ const searchResult = event.target?.closest(".pagefind-ui__result");
209
+ if (!searchResult) return;
210
+
211
+ const searchInput = searchContainer.querySelector(".pagefind-ui__search-input");
212
+ const searchTerm = searchInput ? searchInput.value : "";
213
+
214
+ const titleElement = searchResult.querySelector(".pagefind-ui__result-title a") || searchResult.querySelector(".pagefind-ui__result-link");
215
+ const clickedTitle = titleElement ? titleElement.textContent.trim() : "";
216
+ const clickedUrl = titleElement ? titleElement.href : "";
217
+ window.dataLayer = window.dataLayer || [];
218
+ window.dataLayer.push({
219
+ event: "liveSearch",
220
+ searchTerm: searchTerm,
221
+ clickedResult: clickedTitle,
222
+ clickedUrl: clickedUrl
223
+ });
224
+ });
225
+ }
226
+
202
227
  try {
203
- const pagefind = await import("/pagefind/pagefind.js");
228
+ const pagefind = await import(`${baseUrl}/pagefind/pagefind.js`);
204
229
  window.__pagefindTrueCounts = {};
205
230
 
206
231
  let frameRequested = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coherent-docs-theme",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "license": "MIT",
5
5
  "description": "Theme for all the coherent documentations",
6
6
  "author": "CoherentLabs",
@@ -11,21 +11,24 @@
11
11
  "exports": {
12
12
  ".": "./index.ts",
13
13
  "./styles": "./styles.css",
14
+ "./components": "./components/index.ts",
14
15
  "./components/*.astro": "./components/*.astro",
15
16
  "./overrides/*.astro": "./overrides/*.astro",
16
17
  "./assets/*": "./assets/*"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@astrojs/starlight": "0.37.6",
20
- "astro": "5.17.1",
21
21
  "@types/mdast": "^4.0.4",
22
- "@types/unist": "^3.0.3"
22
+ "@types/unist": "^3.0.3",
23
+ "astro": "5.17.1"
23
24
  },
24
25
  "dependencies": {
26
+ "gray-matter": "^4.0.3",
27
+ "reading-time": "^1.5.0",
25
28
  "starlight-heading-badges": "0.6.1",
26
29
  "starlight-sidebar-topics": "0.6.2",
27
30
  "starlight-sidebar-topics-dropdown": "0.5.2",
28
- "gray-matter": "^4.0.3"
31
+ "vanilla-cookieconsent": "^3.1.0"
29
32
  },
30
33
  "peerDependencies": {
31
34
  "@astrojs/starlight": ">=0.37.6",
@@ -46,4 +49,4 @@
46
49
  "url": "https://github.com/CoherentLabs/frontend-tools/coherent-docs-theme.git",
47
50
  "directory": "packages/coherent-docs-theme"
48
51
  }
49
- }
52
+ }
@@ -0,0 +1,39 @@
1
+ import { visit } from 'unist-util-visit';
2
+ import type { Root } from 'mdast';
3
+
4
+ export function remarkFixDoxybookLinks() {
5
+ return (tree: Root, file: any) => {
6
+ if (!file?.history?.[0].includes('API_reference')) return;
7
+
8
+ visit(tree, 'link', (node: any) => {
9
+ if (node.url && node.url.includes('#')) {
10
+ let [base, hash] = node.url.split('#');
11
+
12
+ const linkText = node.children
13
+ .map((c: any) => c.value || '')
14
+ .join('');
15
+
16
+ if (linkText) {
17
+ const wordsWithUnderscores = linkText.match(/[a-zA-Z0-9_]*_[a-zA-Z0-9_]*/g) || [];
18
+ const uniqueWords = [...new Set(wordsWithUnderscores)];
19
+
20
+ //@ts-ignore
21
+ uniqueWords.forEach((word: string) => {
22
+ const correctStr = word.toLowerCase();
23
+ const brokenStr = correctStr.replace(/_/g, '-');
24
+ hash = hash.split(brokenStr).join(correctStr);
25
+ });
26
+ }
27
+
28
+ hash = hash.replace(/~/g, '')
29
+ .replace(/\(\)/g, '')
30
+ .replace(/=/g, '')
31
+ .replace(/</g, '')
32
+ .replace(/>/g, '')
33
+ .replace(/@/g, '')
34
+ .replace(/-+/g, '-')
35
+ node.url = base ? `${base}#${hash}` : `#${hash}`;
36
+ }
37
+ });
38
+ };
39
+ }
@@ -0,0 +1,54 @@
1
+ import { visit } from 'unist-util-visit';
2
+ import type { Root } from 'mdast';
3
+
4
+ function processUrl(base: string, url: string) {
5
+ if (!url || !url.startsWith('/') || url.startsWith('//')) return url;
6
+
7
+ let newUrl = url;
8
+
9
+ if (!newUrl.startsWith(base + '/')) {
10
+ newUrl = `${base}${newUrl}`;
11
+ }
12
+
13
+ const [pathPart, hashPart] = newUrl.split('#');
14
+ //@ts-ignore
15
+ const hasExtension = /\.[a-zA-Z0-9]+$/.test(pathPart);
16
+
17
+ //@ts-ignore
18
+ if (!hasExtension && !pathPart.endsWith('/')) {
19
+ newUrl = `${pathPart}/` + (hashPart !== undefined ? `#${hashPart}` : '');
20
+ }
21
+
22
+ return newUrl;
23
+ };
24
+
25
+ export function remarkFixAbsoluteLinks(options: { basePath: string }) {
26
+ return (tree: Root) => {
27
+ if (!options || !options.basePath || options.basePath === '/') return;
28
+ const base = options.basePath.replace(/\/$/, '');
29
+
30
+ visit(tree, ['link', 'image'], (node: any) => {
31
+ node.url = processUrl(base, node.url);
32
+ });
33
+
34
+ visit(tree, 'html', (node: any) => {
35
+ if (node.value) {
36
+ node.value = node.value.replace(/(src|href)="(\/[^"]+)"/g, (_: string, attr: string, p1: string) => {
37
+ return `${attr}="${processUrl(base, p1)}"`;
38
+ });
39
+ }
40
+ });
41
+
42
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node: any) => {
43
+ if (node.name === 'NativeLink') return;
44
+
45
+ if (node.attributes) {
46
+ for (const attr of node.attributes) {
47
+ if ((attr.name === 'src' || attr.name === 'href') && typeof attr.value === 'string') {
48
+ attr.value = processUrl(base, attr.value);
49
+ }
50
+ }
51
+ }
52
+ });
53
+ };
54
+ }
@@ -3,11 +3,15 @@ import { remarkIfDirective } from "./if";
3
3
  import { remarkIncludeSnippets } from "./includeSnippets";
4
4
  import { remarkReleaseDirective } from './release';
5
5
  import { remarkInternalDirective } from './internal';
6
+ import { remarkProductNameDirective } from './productName';
7
+ import { remarkFixDoxybookLinks } from './apiRefFixLinks';
6
8
 
7
9
  export const directives = [
8
10
  remarkDirective,
9
11
  remarkIfDirective,
10
12
  remarkInternalDirective,
11
13
  remarkReleaseDirective,
12
- remarkIncludeSnippets
14
+ remarkIncludeSnippets,
15
+ remarkProductNameDirective,
16
+ remarkFixDoxybookLinks
13
17
  ];
@@ -0,0 +1,29 @@
1
+ import { visit } from 'unist-util-visit';
2
+ import type { Root } from 'mdast';
3
+
4
+ const currentProduct = process.env.DOCS_PRODUCT || 'Gameface';
5
+ let productName = currentProduct;
6
+ if (currentProduct) productName = currentProduct.charAt(0).toUpperCase() + currentProduct.slice(1);
7
+
8
+ const productNameRegex = /<ProductName\s*\/?>|<ProductName>\s*<\/ProductName>/g;
9
+
10
+ export function remarkProductNameDirective() {
11
+ return (tree: Root) => {
12
+
13
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node: any, index, parent) => {
14
+ if (node.name === 'ProductName' && parent && index !== undefined) {
15
+ parent.children.splice(index, 1, {
16
+ type: 'text',
17
+ value: productName
18
+ });
19
+ return index;
20
+ }
21
+ });
22
+
23
+ visit(tree, ['text', 'inlineCode', 'code', 'html'], (node: any) => {
24
+ if (typeof node.value === 'string') {
25
+ node.value = node.value.replace(productNameRegex, productName);
26
+ }
27
+ });
28
+ };
29
+ }
package/styles.css CHANGED
@@ -31,7 +31,7 @@
31
31
  --gameface-border: #262626;
32
32
  --gameface-text-head: #FFFFFF;
33
33
  --gameface-text-body: #F4F4F4;
34
- --gameface-text-meta: #8fa3a3;
34
+ --gameface-text-meta: #b2bebe;
35
35
 
36
36
  /* Starlight Mappings (Dark) */
37
37
  --sl-color-bg: var(--gameface-bg-main);