From 984b5acccc48bcc5344822d25fac4450dfead90e Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Fri, 26 Jun 2020 12:18:52 +0200 Subject: [PATCH] feat(mkdocs-reader): initial navigation inside docs --- plugins/techdocs/package.json | 1 + plugins/techdocs/src/config.js | 16 +++++ plugins/techdocs/src/plugin.ts | 2 +- .../techdocs/src/reader/components/Reader.tsx | 62 ++++++++----------- .../techdocs/src/transformers/addBaseUrl.ts | 52 ++++++++++++++++ plugins/techdocs/src/transformers/index.ts | 28 +++++++++ .../src/transformers/rewriteDocLinks.ts | 38 ++++++++++++ 7 files changed, 163 insertions(+), 36 deletions(-) create mode 100644 plugins/techdocs/src/config.js create mode 100644 plugins/techdocs/src/transformers/addBaseUrl.ts create mode 100644 plugins/techdocs/src/transformers/index.ts create mode 100644 plugins/techdocs/src/transformers/rewriteDocLinks.ts diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 9ef290fa40..b84b98c745 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -29,6 +29,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0", "react-use": "^14.2.0" }, "devDependencies": { diff --git a/plugins/techdocs/src/config.js b/plugins/techdocs/src/config.js new file mode 100644 index 0000000000..2baf8b725c --- /dev/null +++ b/plugins/techdocs/src/config.js @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const baseUrl = 'https://techdocs-mock-sites.storage.googleapis.com'; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 23bfa3b714..fec2b1cf74 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -33,7 +33,7 @@ import { createPlugin, createRouteRef } from '@backstage/core'; import { Reader } from './reader/components/Reader'; export const rootRouteRef = createRouteRef({ - path: '/docs', + path: '/docs/:componentId/*', title: 'Docs', }); diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 5517c0c715..36816fcc9a 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -17,6 +17,10 @@ import React from 'react'; import { useShadowDom } from '..'; import { useAsync } from 'react-use'; +import transformer, { addBaseUrl, rewriteDocLinks } from '../../transformers'; +import { baseUrl } from '../../config'; +import { Link } from '@backstage/core'; +import { useLocation, useParams } from 'react-router-dom'; const useFetch = (url: string) => { const state = useAsync(async () => { @@ -28,54 +32,42 @@ const useFetch = (url: string) => { return state; }; -const addBaseUrl = (htmlString: string, baseUrl: string): string => { - const domParser = new DOMParser().parseFromString(htmlString, 'text/html'); - - const updateDom = ( - list: Array, - attributeName: string, - ): void => { - Array.from(list).forEach((elem: T) => { - const newUrl = new URL( - elem.getAttribute(attributeName)!, - baseUrl, - ).toString(); - elem.setAttribute(attributeName, newUrl); - }); - }; - - updateDom(Array.from(domParser.images), 'src'); - updateDom( - Array.from(domParser.links), - 'href', - ); - updateDom( - Array.from(domParser.querySelectorAll('link')), - 'href', - ); - - return domParser.body.parentElement?.outerHTML || htmlString; +const normalizeUrl = (path: string) => { + return path.replace(/\/\/index.html$/, '/index.html'); }; export const Reader = () => { + const location = useLocation(); + const { componentId, '*': path } = useParams(); const shadowDomRef = useShadowDom(); const state = useFetch( - 'https://techdocs-mock-sites.storage.googleapis.com/mkdocs/index.html', + normalizeUrl( + `${baseUrl}${location.pathname.replace('/docs', '')}/index.html`, + ), ); - + // https://techdocs-mock-sites.storage.googleapis.com/mkdocs/user-guide/configuration/custom-themes/index.html React.useEffect(() => { const divElement = shadowDomRef.current; if (divElement?.shadowRoot && state.value) { - divElement.shadowRoot.innerHTML = addBaseUrl( - state.value, - 'https://techdocs-mock-sites.storage.googleapis.com/mkdocs/', - ); + divElement.shadowRoot.innerHTML = transformer(state.value, [ + addBaseUrl({ + baseUrl, + componentId, + path, + }), + rewriteDocLinks({ + componentId, + }), + ]); } - }, [shadowDomRef, state]); + }, [shadowDomRef, state, componentId, path]); return ( <> -

Shadow DOM should be underneath

+
); diff --git a/plugins/techdocs/src/transformers/addBaseUrl.ts b/plugins/techdocs/src/transformers/addBaseUrl.ts new file mode 100644 index 0000000000..674e3ab33b --- /dev/null +++ b/plugins/techdocs/src/transformers/addBaseUrl.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +type AddBaseUrlOptions = { + baseUrl: string; + componentId: string; + path: string; +}; + +export const addBaseUrl = ({ + baseUrl, + componentId, + path, +}: AddBaseUrlOptions) => { + return (dom: Document): Document => { + const updateDom = ( + list: Array, + attributeName: string, + ): void => { + Array.from(list) + .filter(elem => !!elem.getAttribute(attributeName)) + .forEach((elem: T) => { + const newUrl = new URL( + elem.getAttribute(attributeName)!, + `${baseUrl}/${componentId}/${path}`, + ).toString(); + elem.setAttribute(attributeName, newUrl); + }); + }; + + updateDom(Array.from(dom.images), 'src'); + updateDom(Array.from(dom.scripts), 'src'); + updateDom( + Array.from(dom.querySelectorAll('link')), + 'href', + ); + + return dom; + }; +}; diff --git a/plugins/techdocs/src/transformers/index.ts b/plugins/techdocs/src/transformers/index.ts new file mode 100644 index 0000000000..b530840d08 --- /dev/null +++ b/plugins/techdocs/src/transformers/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './addBaseUrl'; +export * from './rewriteDocLinks'; + +type Transformer = (dom: Document) => Document; + +export default (html: string, transformers: Transformer[]): string => { + const dom = new DOMParser().parseFromString(html, 'text/html'); + + transformers.forEach(transformer => transformer(dom)); + + return dom.body.parentElement?.outerHTML ?? ''; +}; diff --git a/plugins/techdocs/src/transformers/rewriteDocLinks.ts b/plugins/techdocs/src/transformers/rewriteDocLinks.ts new file mode 100644 index 0000000000..459b7eda3c --- /dev/null +++ b/plugins/techdocs/src/transformers/rewriteDocLinks.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +type AddBaseUrlOptions = { + componentId: string; +}; + +export const rewriteDocLinks = ({ componentId }: AddBaseUrlOptions) => { + return (dom: Document): Document => { + const updateDom = ( + list: Array, + attributeName: string, + ): void => { + Array.from(list) + .filter(elem => !!elem.getAttribute(attributeName)) + .forEach((elem: T) => { + const newUrl = `${componentId}/${elem.getAttribute(attributeName)}`; + elem.setAttribute(attributeName, newUrl); + }); + }; + + updateDom(Array.from(dom.getElementsByTagName('a')), 'href'); + + return dom; + }; +};