Skip to content

Commit f80b67d

Browse files
authored
Merge pull request #6333 from Blargian/galaxy-on-page
Add useGalaxyOnPage hook and wire it into docs pages and KB articles
2 parents 521f547 + cf59275 commit f80b67d

4 files changed

Lines changed: 133 additions & 7 deletions

File tree

src/lib/galaxy/galaxy.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ export const useInitGalaxy = () => {
114114

115115
const [galaxy, stopGalaxy] = Galaxy.init(galaxyOptions);
116116
window.galaxy = galaxy;
117+
// Signal readiness so page-level effects that mounted before init (the
118+
// common case on a cold visit) can fire their queued load events.
119+
window.dispatchEvent(new Event("galaxy:ready"));
117120

118121
return () => {
119122
void stopGalaxy();
@@ -138,7 +141,7 @@ export const galaxyOnLoad = (event) => {
138141
*/
139142
export const galaxyOnFocus = (event, depsArray) => {
140143
const listener = () => {
141-
window.galaxy.track(event, { interaction: "trigger" });
144+
window.galaxy?.track(event, { interaction: "trigger" });
142145
};
143146

144147
useEffect(() => {
@@ -157,7 +160,7 @@ export const galaxyOnFocus = (event, depsArray) => {
157160
*/
158161
export const galaxyOnBlur = (event, depsArray) => {
159162
const listener = () => {
160-
window.galaxy.track(event, { interaction: "trigger" });
163+
window.galaxy?.track(event, { interaction: "trigger" });
161164
};
162165

163166
useEffect(() => {
@@ -171,14 +174,34 @@ export const galaxyOnBlur = (event, depsArray) => {
171174
/**
172175
* Instrument galaxy for this page for load, blur and focus events.
173176
*
177+
* Fires `${prefix}.window.load` once each time the page (or `depsArray`)
178+
* changes, and tracks blur/focus for the lifetime of the component.
179+
*
174180
* @param prefix used to name the events sent to galaxy (`${prefix}.window.load`, `${prefix}.window.blur` and `${prefix}.window.focus`)
175181
* @param depsArray used to trigger a rerender of the component that will re-run the useEffect
176182
*
177183
*/
178-
export const galaxyOnPage = (prefix, depsArray = []) => {
179-
galaxyOnLoad(`${prefix}.window.load`);
180-
galaxyOnBlur(`${prefix}.window.blur`, depsArray);
181-
galaxyOnFocus(`${prefix}.window.focus`, depsArray);
184+
export const useGalaxyOnPage = (prefix, depsArray = []) => {
185+
// Fire the load event once per page change. Galaxy is initialised in an
186+
// effect of its own (see useInitGalaxy) in an unrelated subtree, so on a
187+
// cold visit this effect usually runs before galaxy is ready. If it isn't
188+
// ready yet, wait for the one-shot `galaxy:ready` signal instead of dropping
189+
// the event with no retry.
190+
useEffect(() => {
191+
const track = () =>
192+
window.galaxy.track(`${prefix}.window.load`, { interaction: "trigger" });
193+
194+
if (window.galaxy) {
195+
track();
196+
return;
197+
}
198+
199+
window.addEventListener("galaxy:ready", track, { once: true });
200+
return () => window.removeEventListener("galaxy:ready", track);
201+
}, [prefix, ...depsArray]);
202+
203+
galaxyOnBlur(`${prefix}.window.blur`, [prefix, ...depsArray]);
204+
galaxyOnFocus(`${prefix}.window.focus`, [prefix, ...depsArray]);
182205
};
183206

184207
// Pass String with convention 'namespace.component.eventName'

src/theme/BlogPostPage/index.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* Swizzled to instrument knowledge base articles with galaxy
8+
* load/blur/focus events. The KB is served by the blog plugin
9+
* (routeBasePath "/knowledgebase"), so it does not pass through
10+
* the docs DocItem/Layout where docs pages are instrumented.
11+
*/
12+
import React from 'react';
13+
import clsx from 'clsx';
14+
import {HtmlClassNameProvider, ThemeClassNames} from '@docusaurus/theme-common';
15+
import {
16+
BlogPostProvider,
17+
useBlogPost,
18+
} from '@docusaurus/plugin-content-blog/client';
19+
import BlogLayout from '@theme/BlogLayout';
20+
import BlogPostItem from '@theme/BlogPostItem';
21+
import BlogPostPaginator from '@theme/BlogPostPaginator';
22+
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
23+
import BlogPostPageStructuredData from '@theme/BlogPostPage/StructuredData';
24+
import TOC from '@theme/TOC';
25+
import ContentVisibility from '@theme/ContentVisibility';
26+
import {useGalaxyOnPage} from '../../lib/galaxy/galaxy';
27+
import styles from './styles.module.scss';
28+
function BlogPostPageContent({sidebar, children}) {
29+
const {metadata, toc} = useBlogPost();
30+
const {nextItem, prevItem, frontMatter} = metadata;
31+
const {
32+
hide_table_of_contents: hideTableOfContents,
33+
toc_min_heading_level: tocMinHeadingLevel,
34+
toc_max_heading_level: tocMaxHeadingLevel,
35+
} = frontMatter;
36+
37+
// Instrument every KB article, keyed on its permalink. Mirrors the
38+
// `docs.page.*` convention used for docs pages in DocItem/Layout.
39+
useGalaxyOnPage(`knowledgebase.page.${metadata.permalink}`, [
40+
metadata.permalink,
41+
]);
42+
43+
return (
44+
<BlogLayout
45+
sidebar={sidebar}
46+
toc={
47+
!hideTableOfContents && toc.length > 0 ? (
48+
<div className={styles.tocWrapper}>
49+
<TOC
50+
toc={toc}
51+
minHeadingLevel={tocMinHeadingLevel}
52+
maxHeadingLevel={tocMaxHeadingLevel}
53+
/>
54+
</div>
55+
) : undefined
56+
}>
57+
<ContentVisibility metadata={metadata} />
58+
59+
<BlogPostItem>{children}</BlogPostItem>
60+
61+
{(nextItem || prevItem) && (
62+
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
63+
)}
64+
</BlogLayout>
65+
);
66+
}
67+
export default function BlogPostPage(props) {
68+
const BlogPostContent = props.content;
69+
return (
70+
<BlogPostProvider content={props.content} isBlogPostPage>
71+
<HtmlClassNameProvider
72+
className={clsx(
73+
ThemeClassNames.wrapper.blogPages,
74+
ThemeClassNames.page.blogPostPage,
75+
)}>
76+
<BlogPostPageMetadata />
77+
<BlogPostPageStructuredData />
78+
<BlogPostPageContent sidebar={props.sidebar}>
79+
<BlogPostContent />
80+
</BlogPostPageContent>
81+
</HtmlClassNameProvider>
82+
</BlogPostProvider>
83+
);
84+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* Align the KB article TOC with the left sidebar's search bar, which is
2+
* offset from the top of the row by `margin-top: 2rem`
3+
* (see BlogSidebar/Desktop .sidebarSearchContainer). The TOC's sticky
4+
* element has no top offset in normal flow, so without this it sits ~2rem
5+
* higher than the search bar.
6+
*
7+
* The wrapper stretches to the full column height (height: 100% +
8+
* border-box) so the sticky TOC inside still travels with the article as
9+
* you scroll; padding-top (not margin) provides the 2rem offset without
10+
* changing the row height. */
11+
.tocWrapper {
12+
height: 100%;
13+
padding-top: 2rem;
14+
box-sizing: border-box;
15+
}

src/theme/DocItem/Layout/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import IconClose from "@theme/Icon/Close";
1717
import {useLocation} from "@docusaurus/router";
1818
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
1919
import RelatedBlogs from "../../../components/RelatedBlogs/RelatedBlogs";
20-
import {galaxyOnClick} from "../../../lib/galaxy/galaxy";
20+
import {galaxyOnClick, useGalaxyOnPage} from "../../../lib/galaxy/galaxy";
2121
/**
2222
* Decide if the toc should be rendered, on mobile or desktop viewports
2323
*/
@@ -43,6 +43,10 @@ export default function DocItemLayout({children}) {
4343
const {metadata, frontMatter} = useDoc();
4444
const {editUrl} = metadata;
4545

46+
// Instrument every docs page with galaxy load/blur/focus events, keyed on
47+
// the page's permalink so each page reports under its own prefix.
48+
useGalaxyOnPage(`docs.page.${metadata.permalink}`, [metadata.permalink]);
49+
4650
const location = useLocation();
4751
const context = useDocusaurusContext();
4852

0 commit comments

Comments
 (0)