From 4e7a96c5a831882463802cdd4f84fe1464969cb0 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 23 Mar 2022 12:29:43 +0100 Subject: refactor: use formatjs swc plugin I'm not able to configure SWC plugins in Next.js so to make it works, all translation must have an id. --- src/components/MDX/CodeBlock/CodeBlock.tsx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/components/MDX/CodeBlock/CodeBlock.tsx') diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx index b5cb30e..69f0124 100644 --- a/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -35,22 +35,27 @@ const CodeBlock = ({ const copyText = intl.formatMessage({ defaultMessage: 'Copy', description: 'Prism: copy button text (no clicked)', + id: '/ly3AC', }); const copiedText = intl.formatMessage({ defaultMessage: 'Copied!', description: 'Prism: copy button text (clicked)', + id: 'OV9r1K', }); const errorText = intl.formatMessage({ defaultMessage: 'Use Ctrl+c to copy', description: 'Prism: error text', + id: 'z9qkcQ', }); const darkTheme = intl.formatMessage({ defaultMessage: 'Dark Theme 🌙', description: 'Prism: toggle dark theme button text', + id: 'nFMdWI', }); const lightTheme = intl.formatMessage({ defaultMessage: 'Light Theme 🌞', description: 'Prism: toggle light theme button text', + id: 'Ua2g2p', }); return ( -- cgit v1.2.3 From 9226671f49b507ce6f71e6e2c3621014f05f74e9 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 23 Mar 2022 22:05:30 +0100 Subject: refactor: load prism plugins without babel --- .babelrc | 52 ------- src/components/MDX/CodeBlock/CodeBlock.tsx | 81 +++++++--- .../Settings/PrismThemeToggle/PrismThemeToggle.tsx | 2 +- src/content | 2 +- src/pages/_app.tsx | 2 +- src/pages/article/[slug].tsx | 58 ++++++-- src/pages/projet/[slug].tsx | 1 + src/styles/vendors/_prism.scss | 28 +--- src/ts/types/prism.ts | 51 +++++++ src/utils/helpers/prism.ts | 21 ++- src/utils/providers/prism-theme.tsx | 165 +++++++++++++++++++++ src/utils/providers/prism.tsx | 161 -------------------- 12 files changed, 338 insertions(+), 286 deletions(-) delete mode 100644 .babelrc create mode 100644 src/ts/types/prism.ts create mode 100644 src/utils/providers/prism-theme.tsx delete mode 100644 src/utils/providers/prism.tsx (limited to 'src/components/MDX/CodeBlock/CodeBlock.tsx') diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 657e285..0000000 --- a/.babelrc +++ /dev/null @@ -1,52 +0,0 @@ -{ - "presets": ["next/babel"], - "plugins": [ - [ - "prismjs", - { - "languages": [ - "apacheconf", - "bash", - "css", - "diff", - "docker", - "editorconfig", - "ejs", - "git", - "html", - "ignore", - "ini", - "javascript", - "jsdoc", - "json", - "jsx", - "makefile", - "markup", - "php", - "phpdoc", - "regex", - "scss", - "shell-session", - "smarty", - "tcl", - "toml", - "tsx", - "twig", - "yaml" - ], - "plugins": [ - "command-line", - "copy-to-clipboard", - "diff-highlight", - "inline-color", - "line-highlight", - "line-numbers", - "match-braces", - "normalize-whitespace", - "show-language", - "toolbar" - ] - } - ] - ] -} diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx index 69f0124..c330063 100644 --- a/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -1,28 +1,25 @@ +import { + PrismDefaultPlugins, + PrismLanguages, + PrismPlugins, +} from '@ts/types/prism'; +import { usePrismTheme } from '@utils/providers/prism-theme'; import { useRouter } from 'next/router'; import Prism from 'prismjs'; -import { ReactChildren, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useIntl } from 'react-intl'; -import '@utils/plugins/prism-color-scheme'; -import { usePrismTheme } from '@utils/providers/prism'; const CodeBlock = ({ - className, - children, + code, + language, + plugins, }: { - className: string; - children: ReactChildren; + code: string; + language: PrismLanguages; + plugins: PrismPlugins[]; }) => { - const classNames = className.split('+'); - const languageClass = classNames.find((name: string) => - name.startsWith('language-') - ); const intl = useIntl(); const router = useRouter(); - - useEffect(() => { - Prism.highlightAll(); - }, []); - const { setCodeBlocks } = usePrismTheme(); useEffect(() => { @@ -32,6 +29,46 @@ const CodeBlock = ({ setCodeBlocks(allPre); }, [setCodeBlocks, router.asPath]); + const defaultPlugins: PrismDefaultPlugins[] = useMemo( + () => [ + 'autoloader', + 'toolbar', + 'show-language', + 'copy-to-clipboard', + 'color-scheme', + 'match-braces', + 'normalize-whitespace', + ], + [] + ); + + const loadPrismPlugins = useCallback( + async (prismPlugins: (PrismDefaultPlugins | PrismPlugins)[]) => { + for (const plugin of prismPlugins) { + try { + if (plugin === 'color-scheme') { + await import(`@utils/plugins/prism-${plugin}`); + } else { + await import(`prismjs/plugins/${plugin}/prism-${plugin}.min.js`); + + if (plugin === 'autoloader') + Prism.plugins.autoloader.languages_path = '/prism/'; + } + } catch (error) { + console.error('CodeBlock: an error occurred with Prism.'); + console.error(error); + } + } + }, + [] + ); + + useEffect(() => { + loadPrismPlugins([...defaultPlugins, ...plugins]).then(() => { + Prism.highlightAll(); + }); + }, [loadPrismPlugins, defaultPlugins, plugins]); + const copyText = intl.formatMessage({ defaultMessage: 'Copy', description: 'Prism: copy button text (no clicked)', @@ -58,18 +95,20 @@ const CodeBlock = ({ id: 'Ua2g2p', }); + const defaultPluginsClasses = 'match-braces'; + const pluginsClasses = plugins.join(' '); + return ( -
-
-        {children}
-      
-
+ {code} + ); }; diff --git a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx b/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx index 9707097..20ad267 100644 --- a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx +++ b/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx @@ -1,7 +1,7 @@ import { Toggle } from '@components/FormElements'; import { MoonIcon, SunIcon } from '@components/Icons'; import Spinner from '@components/Spinner/Spinner'; -import { usePrismTheme } from '@utils/providers/prism'; +import { usePrismTheme } from '@utils/providers/prism-theme'; import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; diff --git a/src/content b/src/content index b5aa522..52c97a4 160000 --- a/src/content +++ b/src/content @@ -1 +1 @@ -Subproject commit b5aa522476547db4efa2d6c8e774ca0422ef6547 +Subproject commit 52c97a48f39ef0de9a61d2cf120fae2c70790555 diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e8c00de..84c2469 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,7 +1,7 @@ import { AppPropsWithLayout } from '@ts/types/app'; import { settings } from '@utils/config'; import { AckeeProvider } from '@utils/providers/ackee'; -import { PrismThemeProvider } from '@utils/providers/prism'; +import { PrismThemeProvider } from '@utils/providers/prism-theme'; import { ThemeProvider } from 'next-themes'; import { useRouter } from 'next/router'; import { IntlProvider } from 'react-intl'; diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 41b84b6..27a6f7b 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -14,21 +14,20 @@ import { import styles from '@styles/pages/Page.module.scss'; import { NextPageWithLayout } from '@ts/types/app'; import { ArticleMeta, ArticleProps } from '@ts/types/articles'; +import { PrismDefaultPlugins, PrismPlugins } from '@ts/types/prism'; import { settings } from '@utils/config'; import { getFormattedPaths } from '@utils/helpers/format'; import { loadTranslation } from '@utils/helpers/i18n'; import { addPrismClasses } from '@utils/helpers/prism'; -import { usePrismTheme } from '@utils/providers/prism'; import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; -import { highlightAll } from 'prismjs'; +import Script from 'next/script'; +import Prism from 'prismjs'; import { ParsedUrlQuery } from 'querystring'; -import { useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useIntl } from 'react-intl'; import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts'; -import '@utils/plugins/prism-color-scheme'; -import Script from 'next/script'; const SingleArticle: NextPageWithLayout = ({ comments, @@ -37,19 +36,48 @@ const SingleArticle: NextPageWithLayout = ({ const intl = useIntl(); const router = useRouter(); - useEffect(() => { - addPrismClasses(); - highlightAll(); - }); + const loadPrismPlugins = useCallback( + async (prismPlugins: (PrismDefaultPlugins | PrismPlugins)[]) => { + for (const plugin of prismPlugins) { + try { + if (plugin === 'color-scheme') { + await import(`@utils/plugins/prism-${plugin}`); + } else { + await import(`prismjs/plugins/${plugin}/prism-${plugin}.min.js`); - const { setCodeBlocks } = usePrismTheme(); + if (plugin === 'autoloader') + Prism.plugins.autoloader.languages_path = '/prism/'; + } + } catch (error) { + console.error('Article: an error occurred with Prism.'); + console.error(error); + } + } + }, + [] + ); + + const plugins: (PrismDefaultPlugins | PrismPlugins)[] = useMemo( + () => [ + 'autoloader', + 'toolbar', + 'show-language', + 'copy-to-clipboard', + 'color-scheme', + 'command-line', + 'line-numbers', + 'match-braces', + 'normalize-whitespace', + ], + [] + ); useEffect(() => { - const allPre: NodeListOf = document.querySelectorAll( - 'pre[data-prismjs-color-scheme-current]' - ); - setCodeBlocks(allPre); - }, [setCodeBlocks, router.asPath]); + loadPrismPlugins(plugins).then(() => { + addPrismClasses(); + Prism.highlightAll(); + }); + }, [plugins, loadPrismPlugins]); if (router.isFallback) return ; diff --git a/src/pages/projet/[slug].tsx b/src/pages/projet/[slug].tsx index b9a8f39..1f09fed 100644 --- a/src/pages/projet/[slug].tsx +++ b/src/pages/projet/[slug].tsx @@ -41,6 +41,7 @@ const Project: NextPageWithLayout = ({ }; const components: NestedMDXComponents = { + CodeBlock: (props) => CodeBlock(props), Gallery: (props) => Gallery(props), Image: (props) => ResponsiveImage({ caption: props.caption, ...props }), Link: (props) => Link(props), diff --git a/src/styles/vendors/_prism.scss b/src/styles/vendors/_prism.scss index 2882835..7c05c9f 100644 --- a/src/styles/vendors/_prism.scss +++ b/src/styles/vendors/_prism.scss @@ -43,18 +43,6 @@ } .toolbar-item:nth-child(1) { - grid-column: 2; - grid-row: 1; - margin: 0 var(--spacing-2xs); - - @include mix.media("screen") { - @include mix.dimensions("2xs") { - order: 2; - } - } - } - - .toolbar-item:nth-child(2) { grid-column: 1; grid-row: 1 / 3; margin-right: auto; @@ -64,24 +52,18 @@ color: var(--color-primary-darker); font-size: var(--font-size-sm); font-weight: 600; + } - @include mix.media("screen") { - @include mix.dimensions("2xs") { - order: 1; - } - } + .toolbar-item:nth-child(2) { + grid-column: 2; + grid-row: 1; + margin: 0 var(--spacing-2xs); } .toolbar-item:nth-child(3) { grid-column: 2; grid-row: 2; margin: 0 var(--spacing-2xs); - - @include mix.media("screen") { - @include mix.dimensions("2xs") { - order: 3; - } - } } } diff --git a/src/ts/types/prism.ts b/src/ts/types/prism.ts new file mode 100644 index 0000000..663bc08 --- /dev/null +++ b/src/ts/types/prism.ts @@ -0,0 +1,51 @@ +export type PrismLanguages = + | 'apacheconf' + | 'bash' + | 'css' + | 'diff' + | 'docker' + | 'editorconfig' + | 'ejs' + | 'git' + | 'graphql' + | 'html' + | 'ignore' + | 'ini' + | 'javascript' + | 'jsdoc' + | 'json' + | 'jsx' + | 'makefile' + | 'markup' + | 'php' + | 'phpdoc' + | 'regex' + | 'scss' + | 'shell-session' + | 'smarty' + | 'tcl' + | 'toml' + | 'tsx' + | 'twig' + | 'yaml'; + +export type PrismDefaultPlugins = + | 'autoloader' + | 'color-scheme' + | 'copy-to-clipboard' + | 'match-braces' + | 'normalize-whitespace' + | 'show-language' + | 'toolbar'; + +export type PrismPlugins = + | 'command-line' + | 'diff-highlight' + | 'inline-color' + | 'line-highlight' + | 'line-numbers'; + +export type PrismProviderProps = { + language: PrismLanguages; + plugins: PrismPlugins[]; +}; diff --git a/src/utils/helpers/prism.ts b/src/utils/helpers/prism.ts index bc84c91..a5f5787 100644 --- a/src/utils/helpers/prism.ts +++ b/src/utils/helpers/prism.ts @@ -17,19 +17,18 @@ export const addPrismClasses = () => { const preTags = document.getElementsByTagName('pre'); Array.from(preTags).forEach((preTag) => { - if ( - isLanguageBlock(preTag.classList) && - !preTag.classList.contains('command-line') && - !preTag.classList.contains('language-diff') - ) { - preTag.classList.add('line-numbers', 'match-braces'); - } + if (!isLanguageBlock(preTag.classList)) return; + + preTag.classList.add('match-braces'); - if ( - preTag.classList.contains('command-line') && - preTag.classList.contains('filter-output') - ) { + if (preTag.classList.contains('filter-output')) { preTag.setAttribute('data-filter-output', '#output#'); } + + if (preTag.classList.contains('language-bash')) { + preTag.classList.add('command-line'); + } else if (!preTag.classList.contains('language-diff')) { + preTag.classList.add('line-numbers'); + } }); }; diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx new file mode 100644 index 0000000..2ed8454 --- /dev/null +++ b/src/utils/providers/prism-theme.tsx @@ -0,0 +1,165 @@ +import { LocalStorage } from '@services/local-storage'; +import { + createContext, + FC, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; + +export type PrismTheme = 'dark' | 'light' | 'system'; +export type ResolvedPrismTheme = 'dark' | 'light'; + +export type UsePrismThemeProps = { + themes: PrismTheme[]; + theme?: PrismTheme; + setTheme: (theme: PrismTheme) => void; + resolvedTheme?: ResolvedPrismTheme; + codeBlocks?: NodeListOf; + setCodeBlocks: (codeBlocks: NodeListOf) => void; +}; + +export type PrismThemeProviderProps = { + attribute?: string; + storageKey?: string; + themes?: PrismTheme[]; +}; + +export const PrismThemeContext = createContext({ + themes: ['dark', 'light', 'system'], + setTheme: (_) => { + // This is intentional. + }, + setCodeBlocks: (_) => { + // This is intentional. + }, +}); + +export const usePrismTheme = () => useContext(PrismThemeContext); + +const prefersDarkScheme = () => { + if (typeof window === 'undefined') return; + + return ( + window.matchMedia && + window.matchMedia('(prefers-color-scheme: dark)').matches + ); +}; + +const isValidTheme = (theme: string): boolean => { + return theme === 'dark' || theme === 'light' || theme === 'system'; +}; + +const getTheme = (key: string): PrismTheme | undefined => { + if (typeof window === 'undefined') return undefined; + const storageValue = LocalStorage.get(key); + + return storageValue && isValidTheme(storageValue) + ? (storageValue as PrismTheme) + : undefined; +}; + +export const PrismThemeProvider: FC = ({ + attribute = 'data-prismjs-color-scheme-current', + storageKey = 'prismjs-color-scheme', + themes = ['dark', 'light', 'system'], + children, +}) => { + const getThemeFromSystem = useCallback(() => { + return prefersDarkScheme() ? 'dark' : 'light'; + }, []); + + const [prismTheme, setPrismTheme] = useState( + getTheme(storageKey) || 'system' + ); + + const updateTheme = (theme: PrismTheme) => { + setPrismTheme(theme); + }; + + useEffect(() => { + LocalStorage.set(storageKey, prismTheme); + }, [prismTheme, storageKey]); + + const [resolvedTheme, setResolvedTheme] = useState(); + + useEffect(() => { + if (prismTheme === 'dark' || prismTheme === 'light') { + setResolvedTheme(prismTheme); + } else { + setResolvedTheme(getThemeFromSystem()); + } + }, [prismTheme, getThemeFromSystem]); + + const updateResolvedTheme = useCallback(() => { + setResolvedTheme(getThemeFromSystem()); + }, [getThemeFromSystem]); + + useEffect(() => { + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', updateResolvedTheme); + + return () => + window + .matchMedia('(prefers-color-scheme: dark)') + .removeEventListener('change', updateResolvedTheme); + }, [updateResolvedTheme]); + + const [preTags, setPreTags] = useState>(); + + const updatePreTags = useCallback((tags: NodeListOf) => { + setPreTags(tags); + }, []); + + const updatePreTagsAttribute = useCallback(() => { + preTags?.forEach((pre) => { + pre.setAttribute(attribute, prismTheme); + }); + }, [attribute, preTags, prismTheme]); + + useEffect(() => { + updatePreTagsAttribute(); + }, [updatePreTagsAttribute, prismTheme]); + + const listenAttributeChange = useCallback( + (pre: HTMLPreElement) => { + var observer = new MutationObserver(function (mutations) { + mutations.forEach((record) => { + var mutatedPre = record.target as HTMLPreElement; + var newTheme = mutatedPre.getAttribute(attribute) as PrismTheme; + setPrismTheme(newTheme); + }); + }); + observer.observe(pre, { + attributes: true, + attributeFilter: [attribute], + }); + }, + [attribute] + ); + + useEffect(() => { + if (!preTags) return; + + preTags.forEach((pre) => { + listenAttributeChange(pre); + }); + }, [preTags, listenAttributeChange]); + + return ( + + {children} + + ); +}; diff --git a/src/utils/providers/prism.tsx b/src/utils/providers/prism.tsx deleted file mode 100644 index 7a4221d..0000000 --- a/src/utils/providers/prism.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { LocalStorage } from '@services/local-storage'; -import { - createContext, - FC, - useCallback, - useContext, - useEffect, - useState, -} from 'react'; - -export type PrismTheme = 'dark' | 'light' | 'system'; -export type ResolvedPrismTheme = 'dark' | 'light'; - -export type UsePrismThemeProps = { - themes: PrismTheme[]; - theme?: PrismTheme; - setTheme: (theme: PrismTheme) => void; - resolvedTheme?: ResolvedPrismTheme; - codeBlocks?: NodeListOf; - setCodeBlocks: (codeBlocks: NodeListOf) => void; -}; - -export type PrismThemeProviderProps = { - attribute?: string; - storageKey?: string; - themes?: PrismTheme[]; -}; - -export const PrismThemeContext = createContext({ - themes: ['dark', 'light', 'system'], - setTheme: (_) => {}, - setCodeBlocks: (_) => {}, -}); - -export const usePrismTheme = () => useContext(PrismThemeContext); - -const prefersDarkScheme = () => { - if (typeof window === 'undefined') return; - - return ( - window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches - ); -}; - -const isValidTheme = (theme: string): boolean => { - return theme === 'dark' || theme === 'light' || theme === 'system'; -}; - -const getTheme = (key: string): PrismTheme | undefined => { - if (typeof window === 'undefined') return undefined; - const storageValue = LocalStorage.get(key); - - return storageValue && isValidTheme(storageValue) - ? (storageValue as PrismTheme) - : undefined; -}; - -export const PrismThemeProvider: FC = ({ - attribute = 'data-prismjs-color-scheme-current', - storageKey = 'prismjs-color-scheme', - themes = ['dark', 'light', 'system'], - children, -}) => { - const getThemeFromSystem = useCallback(() => { - return prefersDarkScheme() ? 'dark' : 'light'; - }, []); - - const [prismTheme, setPrismTheme] = useState( - getTheme(storageKey) || 'system' - ); - - const updateTheme = (theme: PrismTheme) => { - setPrismTheme(theme); - }; - - useEffect(() => { - LocalStorage.set(storageKey, prismTheme); - }, [prismTheme, storageKey]); - - const [resolvedTheme, setResolvedTheme] = useState(); - - useEffect(() => { - if (prismTheme === 'dark' || prismTheme === 'light') { - setResolvedTheme(prismTheme); - } else { - setResolvedTheme(getThemeFromSystem()); - } - }, [prismTheme, getThemeFromSystem]); - - const updateResolvedTheme = useCallback(() => { - setResolvedTheme(getThemeFromSystem()); - }, [getThemeFromSystem]); - - useEffect(() => { - window - .matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', updateResolvedTheme); - - return () => - window - .matchMedia('(prefers-color-scheme: dark)') - .removeEventListener('change', updateResolvedTheme); - }, [updateResolvedTheme]); - - const [preTags, setPreTags] = useState>(); - - const updatePreTags = useCallback((tags: NodeListOf) => { - setPreTags(tags); - }, []); - - const updatePreTagsAttribute = useCallback(() => { - preTags?.forEach((pre) => { - pre.setAttribute(attribute, prismTheme); - }); - }, [attribute, preTags, prismTheme]); - - useEffect(() => { - updatePreTagsAttribute(); - }, [updatePreTagsAttribute, prismTheme]); - - const listenAttributeChange = useCallback( - (pre: HTMLPreElement) => { - var observer = new MutationObserver(function (mutations) { - mutations.forEach((record) => { - var mutatedPre = record.target as HTMLPreElement; - var newTheme = mutatedPre.getAttribute(attribute) as PrismTheme; - setPrismTheme(newTheme); - }); - }); - observer.observe(pre, { - attributes: true, - attributeFilter: [attribute], - }); - }, - [attribute] - ); - - useEffect(() => { - if (!preTags) return; - - preTags.forEach((pre) => { - listenAttributeChange(pre); - }); - }, [preTags, listenAttributeChange]); - - return ( - - {children} - - ); -}; -- cgit v1.2.3