From d910ff118ed9c72bb78c627a37d57500020caa50 Mon Sep 17 00:00:00 2001 From: Alexey Pyltsyn Date: Sun, 17 May 2020 17:47:05 +0300 Subject: [PATCH] refactor(v2): add @theme-init alias to give access to initial components (#2464) --- .../src/theme/CodeBlock/index.js | 8 +- .../src/theme/hooks/usePrismTheme.js | 26 ++ .../src/theme/CodeBlock/index.js | 289 ++---------------- .../src/theme/CodeBlock/styles.module.css | 63 ---- .../src/theme/Playground/index.js | 2 +- .../src/server/themes/__tests__/index.ts | 1 + .../docusaurus/src/server/themes/index.ts | 5 + 7 files changed, 55 insertions(+), 339 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.js delete mode 100644 packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/styles.module.css diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.js b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.js index c65e9ea333..4aea707bb6 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.js +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.js @@ -10,11 +10,10 @@ import React, {useEffect, useState, useRef} from 'react'; import classnames from 'classnames'; import Highlight, {defaultProps} from 'prism-react-renderer'; -import defaultTheme from 'prism-react-renderer/themes/palenight'; import Clipboard from 'clipboard'; import rangeParser from 'parse-numeric-range'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import useThemeContext from '@theme/hooks/useThemeContext'; +import usePrismTheme from '@theme/hooks/usePrismTheme'; import styles from './styles.module.css'; @@ -113,10 +112,7 @@ export default ({children, className: languageClassName, metastring}) => { let highlightLines = []; let codeBlockTitle = ''; - const {isDarkTheme} = useThemeContext(); - const lightModeTheme = prism.theme || defaultTheme; - const darkModeTheme = prism.darkTheme || lightModeTheme; - const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme; + const prismTheme = usePrismTheme(); if (metastring && highlightLinesRangeRegex.test(metastring)) { const highlightLinesRange = metastring.match(highlightLinesRangeRegex)[1]; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.js b/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.js new file mode 100644 index 0000000000..bc3b28f6f3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import defaultTheme from 'prism-react-renderer/themes/palenight'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useThemeContext from '@theme/hooks/useThemeContext'; + +const usePrismTheme = () => { + const { + siteConfig: { + themeConfig: {prism = {}}, + }, + } = useDocusaurusContext(); + const {isDarkTheme} = useThemeContext(); + const lightModeTheme = prism.theme || defaultTheme; + const darkModeTheme = prism.darkTheme || lightModeTheme; + const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme; + + return prismTheme; +}; + +export default usePrismTheme; diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js index 4c6e9004e8..bb00038a81 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js @@ -5,281 +5,32 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ - -import React, {useEffect, useState, useRef} from 'react'; -import classnames from 'classnames'; -import Highlight, {defaultProps} from 'prism-react-renderer'; -import defaultTheme from 'prism-react-renderer/themes/palenight'; -import Clipboard from 'clipboard'; -import rangeParser from 'parse-numeric-range'; +import React from 'react'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import useThemeContext from '@theme/hooks/useThemeContext'; +import usePrismTheme from '@theme/hooks/usePrismTheme'; import Playground from '@theme/Playground'; +import CodeBlock from '@theme-init/CodeBlock'; -import styles from './styles.module.css'; +const withLiveEditor = (Component) => { + const WrappedComponent = (props) => { + const {isClient} = useDocusaurusContext(); + const prismTheme = usePrismTheme(); -const highlightLinesRangeRegex = /{([\d,-]+)}/; -const getHighlightDirectiveRegex = ( - languages = ['js', 'jsBlock', 'jsx', 'python', 'html'], -) => { - // supported types of comments - const comments = { - js: { - start: '\\/\\/', - end: '', - }, - jsBlock: { - start: '\\/\\*', - end: '\\*\\/', - }, - jsx: { - start: '\\{\\s*\\/\\*', - end: '\\*\\/\\s*\\}', - }, - python: { - start: '#', - end: '', - }, - html: { - start: '', - }, - }; - // supported directives - const directives = [ - 'highlight-next-line', - 'highlight-start', - 'highlight-end', - ].join('|'); - // to be more reliable, the opening and closing comment must match - const commentPattern = languages - .map( - (lang) => - `(?:${comments[lang].start}\\s*(${directives})\\s*${comments[lang].end})`, - ) - .join('|'); - // white space is allowed, but otherwise it should be on it's own line - return new RegExp(`^\\s*(?:${commentPattern})\\s*$`); -}; -// select comment styles based on language -const highlightDirectiveRegex = (lang) => { - switch (lang) { - case 'js': - case 'javascript': - case 'ts': - case 'typescript': - return getHighlightDirectiveRegex(['js', 'jsBlock']); - - case 'jsx': - case 'tsx': - return getHighlightDirectiveRegex(['js', 'jsBlock', 'jsx']); - - case 'html': - return getHighlightDirectiveRegex(['js', 'jsBlock', 'html']); - - case 'python': - case 'py': - return getHighlightDirectiveRegex(['python']); - - default: - // all comment types - return getHighlightDirectiveRegex(); - } -}; -const codeBlockTitleRegex = /title=".*"/; - -export default ({ - children, - className: languageClassName, - live, - metastring, - ...props -}) => { - const { - siteConfig: { - themeConfig: {prism = {}}, - }, - } = useDocusaurusContext(); - - const [showCopied, setShowCopied] = useState(false); - const [mounted, setMounted] = useState(false); - // The Prism theme on SSR is always the default theme but the site theme - // can be in a different mode. React hydration doesn't update DOM styles - // that come from SSR. Hence force a re-render after mounting to apply the - // current relevant styles. There will be a flash seen of the original - // styles seen using this current approach but that's probably ok. Fixing - // the flash will require changing the theming approach and is not worth it - // at this point. - useEffect(() => { - setMounted(true); - }, []); - - const target = useRef(null); - const button = useRef(null); - let highlightLines = []; - let codeBlockTitle = ''; - - const {isDarkTheme} = useThemeContext(); - const lightModeTheme = prism.theme || defaultTheme; - const darkModeTheme = prism.darkTheme || lightModeTheme; - const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme; - - if (metastring && highlightLinesRangeRegex.test(metastring)) { - const highlightLinesRange = metastring.match(highlightLinesRangeRegex)[1]; - highlightLines = rangeParser - .parse(highlightLinesRange) - .filter((n) => n > 0); - } - - if (metastring && codeBlockTitleRegex.test(metastring)) { - codeBlockTitle = metastring - .match(codeBlockTitleRegex)[0] - .split('title=')[1] - .replace(/"+/g, ''); - } - - useEffect(() => { - let clipboard; - - if (button.current) { - clipboard = new Clipboard(button.current, { - target: () => target.current, - }); + if (props.live) { + return ( + + ); } - return () => { - if (clipboard) { - clipboard.destroy(); - } - }; - }, [button.current, target.current]); - - if (live) { - return ( - - ); - } - - let language = - languageClassName && languageClassName.replace(/language-/, ''); - - if (!language && prism.defaultLanguage) { - language = prism.defaultLanguage; - } - - // only declaration OR directive highlight can be used for a block - let code = children.replace(/\n$/, ''); - if (highlightLines.length === 0 && language !== undefined) { - let range = ''; - const directiveRegex = highlightDirectiveRegex(language); - // go through line by line - const lines = children.replace(/\n$/, '').split('\n'); - let blockStart; - // loop through lines - for (let index = 0; index < lines.length; ) { - const line = lines[index]; - // adjust for 0-index - const lineNumber = index + 1; - const match = line.match(directiveRegex); - if (match !== null) { - const directive = match - .slice(1) - .reduce((final, item) => final || item, undefined); - switch (directive) { - case 'highlight-next-line': - range += `${lineNumber},`; - break; - - case 'highlight-start': - blockStart = lineNumber; - break; - - case 'highlight-end': - range += `${blockStart}-${lineNumber - 1},`; - break; - - default: - break; - } - lines.splice(index, 1); - } else { - // lines without directives are unchanged - index += 1; - } - } - highlightLines = rangeParser.parse(range); - code = lines.join('\n'); - } - - const handleCopyCode = () => { - window.getSelection().empty(); - setShowCopied(true); - - setTimeout(() => setShowCopied(false), 2000); + return ; }; - return ( - - {({className, style, tokens, getLineProps, getTokenProps}) => ( - <> - {codeBlockTitle && ( -
- {codeBlockTitle} -
- )} -
- -
-
- {tokens.map((line, i) => { - if (line.length === 1 && line[0].content === '') { - line[0].content = '\n'; // eslint-disable-line no-param-reassign - } - - const lineProps = getLineProps({line, key: i}); - - if (highlightLines.includes(i + 1)) { - lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`; - } - - return ( -
- {line.map((token, key) => ( - - ))} -
- ); - })} -
-
-
- - )} -
- ); + return WrappedComponent; }; + +export default withLiveEditor(CodeBlock); diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/styles.module.css deleted file mode 100644 index 9d5e4ad962..0000000000 --- a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/styles.module.css +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -.codeBlockContent { - position: relative; -} - -.codeBlockTitle { - border-top-left-radius: var(--ifm-global-radius); - border-top-right-radius: var(--ifm-global-radius); - border-bottom: 1px solid var(--ifm-color-emphasis-200); - font-family: var(--ifm-font-family-monospace); - font-weight: bold; - padding: 0.75rem var(--ifm-pre-padding); - width: 100%; -} - -.codeBlock { - overflow: auto; - border-radius: var(--ifm-global-radius); -} - -.codeBlockWithTitle { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.copyButton { - background: rgba(0, 0, 0, 0.3); - border: none; - border-radius: var(--ifm-global-radius); - color: var(--ifm-color-white); - cursor: pointer; - opacity: 0; - outline: none; - padding: 0.4rem 0.5rem; - position: absolute; - right: calc(var(--ifm-pre-padding) / 2); - top: calc(var(--ifm-pre-padding) / 2); - visibility: hidden; - transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out, - bottom 200ms ease-in-out; -} - -.codeBlockTitle:hover + .codeBlockContent .copyButton, -.codeBlockContent:hover > .copyButton { - visibility: visible; - opacity: 1; -} - -.codeBlockLines { - font-family: var(--ifm-font-family-monospace); - font-size: inherit; - line-height: var(--ifm-pre-line-height); - white-space: pre; - float: left; - min-width: 100%; - padding: var(--ifm-pre-padding); -} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js index 9a556103f3..1118177f14 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js @@ -14,7 +14,7 @@ import styles from './styles.module.css'; function Playground({children, theme, transformCode, ...props}) { return ( `${code};`)} theme={theme} {...props}> diff --git a/packages/docusaurus/src/server/themes/__tests__/index.ts b/packages/docusaurus/src/server/themes/__tests__/index.ts index 40032cc5ab..68f8eed256 100644 --- a/packages/docusaurus/src/server/themes/__tests__/index.ts +++ b/packages/docusaurus/src/server/themes/__tests__/index.ts @@ -16,6 +16,7 @@ describe('loadThemeAlias', () => { const alias = loadThemeAlias([theme1Path, theme2Path]); expect(alias).toEqual({ + '@theme-init/Layout': path.join(theme1Path, 'Layout.js'), // TODO: Write separate test case for this? '@theme/Footer': path.join(theme1Path, 'Footer/index.js'), '@theme-original/Footer': path.join(theme1Path, 'Footer/index.js'), '@theme/Navbar': path.join(theme2Path, 'Navbar.js'), diff --git a/packages/docusaurus/src/server/themes/index.ts b/packages/docusaurus/src/server/themes/index.ts index 20bb2c1a07..f196dd621b 100644 --- a/packages/docusaurus/src/server/themes/index.ts +++ b/packages/docusaurus/src/server/themes/index.ts @@ -17,6 +17,11 @@ export function loadThemeAlias( themePaths.forEach((themePath) => { const themeAliases = themeAlias(themePath); Object.keys(themeAliases).forEach((aliasKey) => { + if (aliasKey in aliases) { + const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1); + aliases[`@theme-init/${componentName}`] = aliases[aliasKey]; + } + aliases[aliasKey] = themeAliases[aliasKey]; }); });