diff --git a/packages/app/package.json b/packages/app/package.json
index 4522cabaf1..69afcc11e0 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -52,6 +52,7 @@
"@backstage/plugin-shortcuts": "^0.2.5-next.0",
"@backstage/plugin-tech-radar": "^0.5.11-next.1",
"@backstage/plugin-techdocs": "^1.0.1-next.1",
+ "@backstage/plugin-techdocs-addons": "^0.0.0",
"@backstage/plugin-todo": "^0.2.6-next.0",
"@backstage/plugin-user-settings": "^0.4.3-next.0",
"@backstage/plugin-tech-insights": "^0.1.14-next.0",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 9cbbcc86a9..9073af7d2e 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -66,14 +66,15 @@ import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
TechDocsIndexPage,
- techdocsPlugin,
TechDocsReaderPage,
+ techdocsPlugin,
} from '@backstage/plugin-techdocs';
import {
UserSettingsPage,
UserSettingsTab,
} from '@backstage/plugin-user-settings';
import { AdvancedSettings } from './components/advancedSettings';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-addons';
import AlarmIcon from '@material-ui/icons/Alarm';
import React from 'react';
import { hot } from 'react-hot-loader/root';
@@ -87,10 +88,18 @@ import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTe
import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import * as plugins from './plugins';
-import { techDocsPage } from './components/techdocs/TechDocsPage';
+
+// import { techDocsPage } from './components/techdocs/TechDocsPage';
import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow';
import { PermissionedRoute } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
+import {
+ ExampleContent,
+ ExampleHeader,
+ ExamplePrimarySidebar,
+ ExampleSecondarySidebar,
+ ExampleSubHeader,
+} from './components/techdocs/ExampleAddons';
const app = createApp({
apis,
@@ -177,7 +186,13 @@ const routes = (
path="/docs/:namespace/:kind/:name/*"
element={}
>
- {techDocsPage}
+
+
+
+
+
+
+
{
+ return ;
+ },
+ }),
+);
+
+export const ExampleSubHeader = techdocsPlugin.provide(
+ createTechDocsAddon({
+ name: 'ExampleSubHeader',
+ location: TechDocsAddonLocations.SUBHEADER,
+ component: () => {
+ return (
+
+ Subheader.
+
+ );
+ },
+ }),
+);
+
+export const ExamplePrimarySidebar = techdocsPlugin.provide(
+ createTechDocsAddon({
+ name: 'ExamplePrimarySidebar',
+ location: TechDocsAddonLocations.PRIMARY_SIDEBAR,
+ component: () => {
+ return (
+
+ Primary Sidebar.
+
+ );
+ },
+ }),
+);
+
+export const ExampleSecondarySidebar = techdocsPlugin.provide(
+ createTechDocsAddon({
+ name: 'ExampleSecondarySidebar',
+ location: TechDocsAddonLocations.SECONDARY_SIDEBAR,
+ component: () => {
+ return (
+
+ Secondary Sidebar.
+
+ );
+ },
+ }),
+);
+
+const ExampleContentComponent = () => {
+ const h1 = useShadowRootElements(['h1'])[0];
+ useEffect(() => {
+ if (h1 && !h1.innerText.startsWith('Modified: ')) {
+ h1.innerText = `Modified: ${h1.innerText}`;
+ }
+ }, [h1]);
+ return null;
+};
+
+export const ExampleContent = techdocsPlugin.provide(
+ createTechDocsAddon({
+ name: 'ExampleContent',
+ location: TechDocsAddonLocations.CONTENT,
+ component: ExampleContentComponent,
+ }),
+);
diff --git a/plugins/techdocs-addons/api-report.md b/plugins/techdocs-addons/api-report.md
index 26c0825493..5f7f2e8cd0 100644
--- a/plugins/techdocs-addons/api-report.md
+++ b/plugins/techdocs-addons/api-report.md
@@ -7,17 +7,18 @@
import { AsyncState } from 'react-use/lib/useAsyncFn';
import { ComponentType } from 'react';
-import { CompoundEntityRef } from '@backstage/catalog-model';
+import { Entity } from '@backstage/catalog-model';
import { Extension } from '@backstage/core-plugin-api';
import { default as React_2 } from 'react';
-import { TechDocsEntityMetadata } from '@backstage/plugin-techdocs';
-import { TechDocsMetadata } from '@backstage/plugin-techdocs';
// @public
export function createTechDocsAddon(
options: TechDocsAddonOptions,
): Extension>;
+// @public
+export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
+
// @public
export type TechDocsAddonAsyncMetadata = AsyncState;
@@ -41,6 +42,20 @@ export type TechDocsAddonOptions = {
// @public
export const TechDocsAddons: React_2.ComponentType;
+// @public
+export type TechDocsEntityMetadata = Entity & {
+ locationMetadata?: {
+ type: string;
+ target: string;
+ };
+};
+
+// @public
+export type TechDocsMetadata = {
+ site_name: string;
+ site_description: string;
+};
+
// @public
export const TechDocsReaderPage: (
props: TechDocsReaderPageProps,
@@ -48,7 +63,9 @@ export const TechDocsReaderPage: (
// @public (undocumented)
export type TechDocsReaderPageProps = {
- entityName: CompoundEntityRef;
+ dom: Element | null;
+ asyncEntityMetadata: AsyncState;
+ asyncTechDocsMetadata: AsyncState;
};
// @public
diff --git a/plugins/techdocs-addons/package.json b/plugins/techdocs-addons/package.json
index 4769ebd189..1b3d736a7d 100644
--- a/plugins/techdocs-addons/package.json
+++ b/plugins/techdocs-addons/package.json
@@ -25,7 +25,6 @@
"@backstage/catalog-model": "^0.13.0",
"@backstage/core-components": "^0.9.1",
"@backstage/core-plugin-api": "^0.8.0",
- "@backstage/plugin-techdocs": "^0.15.1",
"@material-ui/core": "^4.12.2",
"@material-ui/lab": "4.0.0-alpha.57",
"@material-ui/styles": "^4.11.0",
diff --git a/plugins/techdocs-addons/src/addons.tsx b/plugins/techdocs-addons/src/addons.tsx
index 06ce1eb137..afb3b151c0 100644
--- a/plugins/techdocs-addons/src/addons.tsx
+++ b/plugins/techdocs-addons/src/addons.tsx
@@ -27,6 +27,11 @@ import { useOutlet } from 'react-router-dom';
import { TechDocsAddonLocations, TechDocsAddonOptions } from './types';
export const TECHDOCS_ADDONS_KEY = 'techdocs.addons.addon.v1';
+
+/**
+ * Marks the registry component.
+ * @public
+ */
export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
/**
diff --git a/plugins/techdocs-addons/src/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs-addons/src/components/TechDocsReaderPage/TechDocsReaderPage.tsx
index a9db8ddb6d..d9d54e2248 100644
--- a/plugins/techdocs-addons/src/components/TechDocsReaderPage/TechDocsReaderPage.tsx
+++ b/plugins/techdocs-addons/src/components/TechDocsReaderPage/TechDocsReaderPage.tsx
@@ -14,21 +14,17 @@
* limitations under the License.
*/
-import { CompoundEntityRef } from '@backstage/catalog-model';
import { Page } from '@backstage/core-components';
-// todo(backstage/techdocs-core): Export these from @backstage/plugin-techdocs
-import {
- withTechDocsReaderProvider,
- // @ts-ignore
- TechDocsStateIndicator as TechDocReaderPageIndicator,
-} from '@backstage/plugin-techdocs';
import React from 'react';
+import { useParams } from 'react-router-dom';
+import { AsyncState } from 'react-use/lib/useAsyncFn';
import {
TechDocsMetadataProvider,
TechDocsEntityProvider,
TechDocsReaderPageProvider,
} from '../../context';
+import { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';
import { TechDocsReaderPageContent } from '../TechDocsReaderPageContent';
import { TechDocsReaderPageHeader } from '../TechDocsReaderPageHeader';
import { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader';
@@ -36,29 +32,33 @@ import { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader';
/**
* @public
*/
-export type TechDocsReaderPageProps = { entityName: CompoundEntityRef };
+export type TechDocsReaderPageProps = {
+ dom: Element | null;
+ asyncEntityMetadata: AsyncState;
+ asyncTechDocsMetadata: AsyncState;
+};
/**
* An addon-aware implementation of the TechDocsReaderPage.
* @public
*/
export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
- const { entityName } = props;
- const Component = withTechDocsReaderProvider(() => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }, entityName);
- return ;
+ const { asyncEntityMetadata, asyncTechDocsMetadata, dom } = props;
+ const { namespace, kind, name } = useParams();
+ const entityName = { namespace, kind, name };
+ return (
+
+
+
+
+
+
+ {/* todo(backstage/techdocs-core): handle state indicator */}
+ {/* */}
+
+
+
+
+
+ );
};
diff --git a/plugins/techdocs-addons/src/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs-addons/src/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
index 9bdab3f5c5..6e3ec99b21 100644
--- a/plugins/techdocs-addons/src/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
+++ b/plugins/techdocs-addons/src/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
@@ -15,9 +15,6 @@
*/
import { Content, Progress } from '@backstage/core-components';
-// todo(backstage/techdocs-core): Export these from @backstage/plugin-techdocs
-// @ts-ignore
-import { useTechDocsReaderDom } from '@backstage/plugin-techdocs';
import { Portal } from '@material-ui/core';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import React, { useEffect, useRef, useState } from 'react';
@@ -27,7 +24,7 @@ import { useTechDocsAddons } from '../../addons';
import { useTechDocsReaderPage } from '../../context';
import { TechDocsAddonLocations as locations } from '../../types';
-export const TechDocsReaderPageContent = () => {
+export const TechDocsReaderPageContent = ({ dom }: { dom: Element | null }) => {
const ref = useRef(null);
const [jss, setJss] = useState(
create({
@@ -37,8 +34,7 @@ export const TechDocsReaderPageContent = () => {
);
const addons = useTechDocsAddons();
- const { entityName, setShadowRoot } = useTechDocsReaderPage();
- const dom = useTechDocsReaderDom(entityName);
+ const { setShadowRoot } = useTechDocsReaderPage();
useEffect(() => {
const shadowHost = ref.current;
@@ -61,10 +57,10 @@ export const TechDocsReaderPageContent = () => {
'[data-md-component="container"]',
);
const primarySidebarElement = ref.current?.shadowRoot?.querySelector(
- '[data-md-component="navigation"]',
+ 'div[data-md-component="sidebar"][data-md-type="navigation"], div[data-md-component="navigation"]',
);
const secondarySidebarElement = ref.current?.shadowRoot?.querySelector(
- '[data-md-component="toc"]',
+ 'div[data-md-component="sidebar"][data-md-type="toc"], div[data-md-component="toc"]',
);
const primarySidebarAddonLocation = document.createElement('div');
diff --git a/plugins/techdocs-addons/src/context.tsx b/plugins/techdocs-addons/src/context.tsx
index 9aa057117d..54a73e2589 100644
--- a/plugins/techdocs-addons/src/context.tsx
+++ b/plugins/techdocs-addons/src/context.tsx
@@ -15,12 +15,6 @@
*/
import { CompoundEntityRef } from '@backstage/catalog-model';
-import { useApi } from '@backstage/core-plugin-api';
-import {
- techdocsApiRef,
- TechDocsEntityMetadata,
- TechDocsMetadata,
-} from '@backstage/plugin-techdocs';
import React, {
createContext,
Dispatch,
@@ -29,9 +23,16 @@ import React, {
useContext,
useState,
} from 'react';
-import useAsync from 'react-use/lib/useAsync';
-import { TechDocsAddonAsyncMetadata } from './types';
+import { AsyncState } from 'react-use/lib/useAsync';
+import {
+ TechDocsAddonAsyncMetadata,
+ TechDocsEntityMetadata,
+ TechDocsMetadata,
+} from './types';
+type PropsWithAsyncMetadata = PropsWithChildren<{
+ asyncValue: AsyncState;
+}>;
type PropsWithEntityName = PropsWithChildren<{ entityName: CompoundEntityRef }>;
const initialContextValue = {
@@ -46,17 +47,11 @@ const TechDocsMetadataContext =
);
export const TechDocsMetadataProvider = ({
- entityName,
+ asyncValue,
children,
-}: PropsWithEntityName) => {
- const techdocsApi = useApi(techdocsApiRef);
-
- const metadataResponse = useAsync(async () => {
- return await techdocsApi.getTechDocsMetadata(entityName);
- }, []);
-
+}: PropsWithAsyncMetadata) => {
return (
-
+
{children}
);
@@ -77,17 +72,11 @@ const TechDocsEntityContext =
);
export const TechDocsEntityProvider = ({
- entityName,
+ asyncValue,
children,
-}: PropsWithEntityName) => {
- const techdocsApi = useApi(techdocsApiRef);
-
- const metadataResponse = useAsync(async () => {
- return await techdocsApi.getEntityMetadata(entityName);
- }, []);
-
+}: PropsWithAsyncMetadata) => {
return (
-
+
{children}
);
diff --git a/plugins/techdocs-addons/src/index.ts b/plugins/techdocs-addons/src/index.ts
index 0993eaba97..0d966cfd67 100644
--- a/plugins/techdocs-addons/src/index.ts
+++ b/plugins/techdocs-addons/src/index.ts
@@ -20,12 +20,17 @@
* @packageDocumentation
*/
-export { createTechDocsAddon, TechDocsAddons } from './addons';
+export {
+ createTechDocsAddon,
+ TechDocsAddons,
+ TECHDOCS_ADDONS_WRAPPER_KEY,
+} from './addons';
export * from './components';
export { useEntityMetadata, useTechDocsMetadata } from './context';
-export { useShadowRoot, useShadowRootElements } from './hooks';
+export { TechDocsAddonLocations } from './types';
export type {
TechDocsAddonAsyncMetadata,
- TechDocsAddonLocations,
TechDocsAddonOptions,
+ TechDocsMetadata,
+ TechDocsEntityMetadata,
} from './types';
diff --git a/plugins/techdocs-addons/src/types.ts b/plugins/techdocs-addons/src/types.ts
index 8e42e0a6bc..d8ab7e9520 100644
--- a/plugins/techdocs-addons/src/types.ts
+++ b/plugins/techdocs-addons/src/types.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { Entity } from '@backstage/catalog-model';
import { ComponentType } from 'react';
import { AsyncState } from 'react-use/lib/useAsyncFn';
@@ -77,3 +78,22 @@ export type TechDocsAddonOptions = {
* @public
*/
export type TechDocsAddonAsyncMetadata = AsyncState;
+
+/**
+ * Metadata for TechDocs page
+ *
+ * @public
+ */
+export type TechDocsMetadata = {
+ site_name: string;
+ site_description: string;
+};
+
+/**
+ * Metadata for TechDocs Entity
+ *
+ * @public
+ */
+export type TechDocsEntityMetadata = Entity & {
+ locationMetadata?: { type: string; target: string };
+};
diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json
index d0ef75e32c..b4868ab4e7 100644
--- a/plugins/techdocs/package.json
+++ b/plugins/techdocs/package.json
@@ -43,7 +43,9 @@
"@backstage/integration": "^1.1.0-next.1",
"@backstage/integration-react": "^1.0.1-next.1",
"@backstage/plugin-catalog-react": "^1.0.1-next.2",
+ "@backstage/plugin-catalog": "^0.10.0",
"@backstage/plugin-search": "^0.7.5-next.0",
+ "@backstage/plugin-techdocs-addons": "^0.0.0",
"@backstage/theme": "^0.2.15",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx
index 1bfd0e6f51..1c041dfe97 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx
@@ -17,14 +17,17 @@
import React, { useCallback, useState } from 'react';
import { useOutlet } from 'react-router';
import { useParams } from 'react-router-dom';
-import useAsync from 'react-use/lib/useAsync';
-import { Reader } from './Reader';
-import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader';
+import useAsync, { AsyncState } from 'react-use/lib/useAsync';
import { techdocsApiRef } from '../../api';
import { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';
import { CompoundEntityRef } from '@backstage/catalog-model';
-import { useApi, useApp } from '@backstage/core-plugin-api';
-import { Page, Content } from '@backstage/core-components';
+import { getComponentData, useApi, useApp } from '@backstage/core-plugin-api';
+import { Page } from '@backstage/core-components';
+import {
+ TechDocsReaderPage as AddonAwareReaderPage,
+ TECHDOCS_ADDONS_WRAPPER_KEY,
+} from '@backstage/plugin-techdocs-addons';
+import { useTechDocsReaderDom, withTechDocsReaderProvider } from './Reader';
/**
* Helper function that gives the children of {@link TechDocsReaderPage} access to techdocs and entity metadata
@@ -42,6 +45,24 @@ export type TechDocsReaderPageRenderFunction = ({
onReady: () => void;
}) => JSX.Element;
+type SpecialReaderPageProps = {
+ entityName: CompoundEntityRef;
+ asyncEntityMetadata: AsyncState;
+ asyncTechDocsMetadata: AsyncState;
+};
+
+const SpecialReaderPage = (props: SpecialReaderPageProps) => {
+ const dom = useTechDocsReaderDom(props.entityName);
+
+ return (
+
+ );
+};
+
/**
* Props for {@link TechDocsReaderPage}
*
@@ -61,7 +82,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
const techdocsApi = useApi(techdocsApiRef);
- const { value: techdocsMetadataValue } = useAsync(() => {
+ const asyncTechDocsMetadata = useAsync(() => {
if (documentReady) {
return techdocsApi.getTechDocsMetadata({ kind, namespace, name });
}
@@ -69,50 +90,47 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
return Promise.resolve(undefined);
}, [kind, namespace, name, techdocsApi, documentReady]);
- const { value: entityMetadataValue, error: entityMetadataError } =
- useAsync(() => {
- return techdocsApi.getEntityMetadata({ kind, namespace, name });
- }, [kind, namespace, name, techdocsApi]);
+ const asyncEntityMetadata = useAsync(() => {
+ return techdocsApi.getEntityMetadata({ kind, namespace, name });
+ }, [kind, namespace, name, techdocsApi]);
const onReady = useCallback(() => {
setDocumentReady(true);
}, [setDocumentReady]);
- if (entityMetadataError) return ;
+ if (asyncEntityMetadata.error) return ;
- if (!children)
- return (
- outlet || (
-
-
-
-
-
-
- )
- );
+ );
+ }
+ // Otherwise, just return the outlet (legacy-style composability).
+ return outlet;
+ }
+ }
return (
{children instanceof Function
? children({
- techdocsMetadataValue,
- entityMetadataValue,
+ techdocsMetadataValue: asyncTechDocsMetadata.value,
+ entityMetadataValue: asyncEntityMetadata.value,
entityRef: { kind, namespace, name },
onReady,
})