diff --git a/helper/Helper.php b/helper/Helper.php index 98643ea..c1f1a58 100644 --- a/helper/Helper.php +++ b/helper/Helper.php @@ -17,6 +17,14 @@ * @since 1.0.0 */ class Helper { + + /** + * Name of flag environment. + * + * @var string $env_option_name + */ + public static $env_option_name = 'mr_feature_flags_env'; + /** * Flag search helper. * @@ -27,9 +35,11 @@ class Helper { * @since 1.0.0 */ public static function search_flag( $flags, $field, $flag ) { - foreach ( $flags as $key => $value ) { - if ( $value[ $field ] === $flag ) { - return $value; + if ( is_array( $flags ) ) { + foreach ( $flags as $key => $value ) { + if ( $value[ $field ] === $flag && true === $value['enabled'] ) { + return true; + } } } return false; diff --git a/includes/Api/FlagOptions.php b/includes/Api/FlagOptions.php index 8b2f6c3..86641e1 100644 --- a/includes/Api/FlagOptions.php +++ b/includes/Api/FlagOptions.php @@ -25,6 +25,13 @@ class FlagOptions { */ public static $option_name = 'mr_feature_flags'; + /** + * Name of flag environment. + * + * @var string $env_option_name + */ + public static $env_option_name = 'mr_feature_flags_env'; + /** * Register feature flag endpoints. * @@ -52,6 +59,18 @@ function () { ] ); + register_rest_route( + 'feature-flags/v1', + 'flags/env', + [ + [ + 'methods' => \WP_REST_SERVER::READABLE, + 'callback' => [ $this, 'get_flag_env' ], + 'permission_callback' => '__return_true', + ], + ] + ); + } ); } @@ -65,7 +84,7 @@ public function get_all_flags() { $flags = get_option( self::$option_name ); if ( empty( $flags ) ) { - return new \WP_Error( 'no_flags', 'Flags not found', array( 'status' => 404 ) ); + return rest_ensure_response( [] ); } return rest_ensure_response( $flags ); @@ -74,6 +93,8 @@ public function get_all_flags() { /** * Insert / Update flags in options table. * + * @param WP_Request $request API request. + * * @return mixed List of flags. */ public function post_flags( $request ) { @@ -93,6 +114,21 @@ public function post_flags( $request ) { } } + /** + * Get Feature Flag environment. + * + * @return mixed List of flags. + */ + public function get_flag_env() { + $env = get_option( self::$env_option_name ); + + if ( empty( $env ) ) { + return rest_ensure_response( [ 'env' => 'prod' ] ); + } + + return rest_ensure_response( $env ); + } + /** * Register settings action method. * diff --git a/includes/FeatureFlags.php b/includes/FeatureFlags.php index 7913646..b121689 100644 --- a/includes/FeatureFlags.php +++ b/includes/FeatureFlags.php @@ -25,6 +25,7 @@ class FeatureFlags { */ public static $option_name = 'mr_feature_flags'; + /** * Check if given feature is enabled or not. * @@ -42,42 +43,4 @@ public static function is_enabled( string $flag ): bool { return false; } - /** - * Adds provided flag if it does not exists. - * - * @param string $flag name of the flag. - * @return bool - * @throws \Error Throws error if flag already exists. - * @since 1.0.0 - */ - public static function add_flag( string $flag ): bool { - - $flags = get_option( self::$option_name ); - - if ( is_array( $flags ) && Helper::search_flag( $flags, 'name', $flag ) ) { - throw new \Error( "Flag \"{$flag}\" already exists" ); - } - - $flag_key = 1; - - if ( is_array( $flags ) && count( $flags ) ) { - $flag_key = count( $flags ) + 1; - } - - // $flag_key = count( $flags ) ? count( $flags ) + 1 : 1; - // ddd( $flag_key ); - $new_flag = [ - 'id' => $flag_key, - 'name' => $flag, - 'enabled' => false, - ]; - - if ( $flags ) { - array_push( $flags, $new_flag ); - return update_option( self::$option_name, $flags ); - } - - return add_option( self::$option_name, [ $new_flag ] ); - - } } diff --git a/includes/Settings.php b/includes/Settings.php index 585e496..9e057a5 100644 --- a/includes/Settings.php +++ b/includes/Settings.php @@ -53,3 +53,4 @@ public function render_page() { echo '
'; } } + diff --git a/package.json b/package.json index b674c83..1e1f5fb 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,14 @@ "author": "Mohan Raj ", "license": "ISC", "dependencies": { + "@types/react-syntax-highlighter": "^15.5.6", "@types/wordpress__components": "^23.0.1", "@wordpress/api-fetch": "^6.26.0", "@wordpress/components": "^23.6.0", "@wordpress/data": "^8.6.0", "@wordpress/i18n": "^4.29.0", "@wordpress/notices": "^3.29.0", + "react-syntax-highlighter": "^15.5.0", "ts-loader": "^9.4.2", "typescript": "^5.0.2" } diff --git a/plugin.php b/plugin.php index 0ca718d..c557c75 100644 --- a/plugin.php +++ b/plugin.php @@ -51,11 +51,18 @@ function(): void { ); + $feature_flag_meta = get_option( FeatureFlags::$option_name ); + $flags_list = []; + if(is_array($feature_flag_meta)) { + $flags_list = $feature_flag_meta; + } + + wp_localize_script( 'mr-feature-flags-script', 'mrFeatureFlags', [ - 'flags' => get_option( FeatureFlags::$option_name ), + 'flags' => $flags_list, ] ); @@ -90,14 +97,6 @@ function load_settings_scripts(): void { wp_enqueue_style( 'wp-edit-blocks' ); - wp_localize_script( - 'mr-feature-flags', - 'mrFeatureFlags', - [ - 'flags' => get_option( FeatureFlags::$option_name ), - ] - ); - wp_enqueue_style( 'mr-feature-flags', $plugin_url . 'build/settings.css', @@ -121,13 +120,18 @@ function(string $page): void { true ); + $feature_flag_meta = get_option( FeatureFlags::$option_name ); + $flags_list = []; + if(is_array($feature_flag_meta)) { + $flags_list = $feature_flag_meta; + } wp_localize_script( 'mr-feature-flags-script', 'mrFeatureFlags', [ - 'flags' => get_option( FeatureFlags::$option_name ), + 'flags' => $flags_list, ] ); @@ -141,15 +145,6 @@ function(string $page): void { $mr_feature_flags_register_api->register_flags_endpoints(); -// add_action ('init', function() { - -// $request = new \WP_REST_Request( 'GET', '/feature-flags/v1/flags' ); -// $request->set_query_params( [] ); -// $response = rest_do_request( $request ); -// ddd(rest_get_server()->response_to_data( $response, false )); - -// }); - add_filter( 'plugin_action_links_mr-feature-flags/plugin.php', function ( $links ) { @@ -172,4 +167,5 @@ function(string $page): void { } ); -// FeatureFlags::add_flag('Registration'); + +// update_option( 'mr_feature_flags', [ ["id" => 1, "name" => "login", "enabled" => false],["id" => 2, "name" => "Reg", "enabled" => false]] ); diff --git a/src/components/DeleteModal.tsx b/src/components/DeleteModal.tsx new file mode 100644 index 0000000..72033b5 --- /dev/null +++ b/src/components/DeleteModal.tsx @@ -0,0 +1,25 @@ +import { Modal, Button } from '@wordpress/components'; +const DeleteModal = (props: any): JSX.Element => { + const { closeModal, item, handleDeleteFlag } = props; + return ( + +

+ Are you sure want to delete flag "{item.name} + "? +

+ + +
+ ); +}; + +export default DeleteModal; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..6a75094 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,19 @@ +import { Flex, FlexItem } from '@wordpress/components'; +export default function (): JSX.Element { + return ( + + +

Flag Name

+
+ +

Status

+
+ +

SDK Settings

+
+ +

Delete Flag

+
+
+ ); +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 1e92cdf..9e61a9b 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -4,6 +4,7 @@ import LineItem from './LineItem'; import { Flag } from '../../types'; import SubmitControls from './SubmitControls'; import { getFlags } from '../utils'; +import Header from './Header'; const Layout = (): JSX.Element => { const [flags, setFlags] = useState(undefined); @@ -11,11 +12,13 @@ const Layout = (): JSX.Element => { useEffect(() => { const logFlags = async () => { - const result = await getFlags(); - if (Array.isArray(result)) { - setFlags(result); - setIsLoading(false); + const fetchedFlags = await getFlags(); + + if (fetchedFlags) { + setFlags(fetchedFlags); } + + setIsLoading(false); }; logFlags(); }, [getFlags, setFlags, setIsLoading]); @@ -24,23 +27,12 @@ const Layout = (): JSX.Element => { const lastFlag = flags?.at(-1)?.id || 0; - if (!lastFlag && !isLoading) { - return ( - <> -

- Welcome to feature flag dashboard. You can add new flags - `Add flags` action. -

- - - ); - } return ( <>

Feature Flags settings

-

Manage all feature flags.

+ {lastFlag ?
: ''} {isLoading ? (
diff --git a/src/components/LineItem.tsx b/src/components/LineItem.tsx index ab2cf37..f851220 100644 --- a/src/components/LineItem.tsx +++ b/src/components/LineItem.tsx @@ -4,32 +4,54 @@ import { Flex, FlexItem, Button, - Modal, BaseControl, } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { useState, useRef, useEffect } from '@wordpress/element'; import { Flag } from '../../types'; +import DeleteModal from './DeleteModal'; +import SdkModal from './SdkModal'; + +interface LineItemProps { + flags: Flag[]; + setFlags: (flags: Flag[]) => void; + item: Flag; + setDisableSave: (toggle: boolean) => void; +} const LineItem = ({ flags, setFlags, item, setDisableSave, -}: any): JSX.Element => { +}: LineItemProps): JSX.Element => { const [isOpen, setOpen] = useState(false); + const [isSdkOpen, setIsSdkOpen] = useState(false); + const [hasError, setHasError] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current && '' === item.name) { + inputRef.current.focus(); + } + }, [inputRef, item]); + const handleDeleteFlag = (flagId: number) => { const updatedFlags = flags.filter((flag: Flag) => flag.id !== flagId); setFlags(updatedFlags); closeModal(); }; - const handleFlagToggle = (flagId: number) => { + const handleFlagToggle = (flagId: number, flagEnv = 'prod') => { const updatedFlags = flags.map((flag: Flag) => { if (flag.id === flagId) { - flag.enabled = !flag.enabled; + if (flagEnv === 'prod') { + flag.enabled = !flag.enabled; + } else { + flag.preProdEnabled = !flag.preProdEnabled; + } } return flag; }); @@ -58,6 +80,12 @@ const LineItem = ({ }; const closeModal = () => setOpen(false); + const openSdkModal = () => { + setIsSdkOpen(true); + }; + + const closeSdkModal = () => setIsSdkOpen(false); + const handleDeleteModal = (flag: Flag) => { if (flag.name) { openModal(); @@ -66,23 +94,40 @@ const LineItem = ({ handleDeleteFlag(flag.id); }; + const handleSdkModal = () => { + openSdkModal(); + }; + return ( <>
handleFlagEdit(value, item.id)} /> - + handleFlagToggle(item.id)} /> - + + + + +
{isOpen && ( - -

- Are you sure want to delete flag "{item.name} - "? -

- - -
+ + )} + {isSdkOpen && ( + )} ); diff --git a/src/components/SdkModal.tsx b/src/components/SdkModal.tsx new file mode 100644 index 0000000..bf1fc1f --- /dev/null +++ b/src/components/SdkModal.tsx @@ -0,0 +1,104 @@ +import { Modal, Button } from '@wordpress/components'; +import Snippet from './Snippet'; +import { useMemo, useState, useEffect } from '@wordpress/element'; +import { useCopyToClipboard } from '@wordpress/compose'; + +const SdkModal = (props: any): JSX.Element => { + const { item, closeSdkModal } = props; + const [hasJsCopied, setHasJsCopied] = useState(false); + const [hasPhpCopied, setHasPhpCopied] = useState(false); + + useEffect(() => { + let jsTimeout: number | null = null; + let phpTimeout: number | null = null; + + if (hasJsCopied) { + jsTimeout = setTimeout(() => { + setHasJsCopied(false); + }, 4000); + } + + if (hasPhpCopied) { + phpTimeout = setTimeout(() => { + setHasPhpCopied(false); + }, 4000); + } + return () => { + if (jsTimeout) { + window.clearTimeout(jsTimeout); + } + if (phpTimeout) { + window.clearTimeout(phpTimeout); + } + }; + }, [hasJsCopied, hasPhpCopied]); + + const jsSnippet = useMemo(() => { + return `import domReady from '@wordpress/dom-ready'; +domReady(function () { + if (window.mrFeatureFlags.isEnabled('${item.name}')) { + // js code goes here... + } + });`; + }, [item.name]); + + const phpSnippet = useMemo(() => { + return `if ( MR\\FeatureFlags\\FeatureFlags::is_enabled( '${item.name}' ) ) { + // php code goes here... +}`; + }, [item.name]); + + const jsRef = useCopyToClipboard(jsSnippet, onJsCopy); + + function onJsCopy() { + setHasJsCopied(true); + } + + const phpRef = useCopyToClipboard(phpSnippet, onPhpCopy); + + function onPhpCopy() { + setHasPhpCopied(true); + } + + return ( + +
+

PHP Snippet

+
+
+

JS Snippet

+
+
+ ); +}; + +export default SdkModal; diff --git a/src/components/Snippet.tsx b/src/components/Snippet.tsx new file mode 100644 index 0000000..6af55ae --- /dev/null +++ b/src/components/Snippet.tsx @@ -0,0 +1,13 @@ +import SyntaxHighlighter from 'react-syntax-highlighter'; +import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'; + +const Snippet = (props: any) => { + const { data } = props; + return ( + + {data} + + ); +}; + +export default Snippet; diff --git a/src/components/SubmitControls.tsx b/src/components/SubmitControls.tsx index 982da5b..838beaf 100644 --- a/src/components/SubmitControls.tsx +++ b/src/components/SubmitControls.tsx @@ -10,9 +10,21 @@ const SubmitControls = (props: any): JSX.Element => { const [isSaving, setIsSaving] = useState(false); const handleNewFlag = () => { - const newFlag = { id: lastFlag + 1, name: '', enabled: false }; - const clonedFlags = [...flags, newFlag]; - setFlags(clonedFlags); + const newFlag = { + id: lastFlag + 1, + name: '', + enabled: false, + }; + + let latestFlags = []; + + if (flags?.length) { + latestFlags = [...flags, newFlag]; + } else { + latestFlags = [newFlag]; + } + + setFlags(latestFlags); }; const handleSave = async () => { @@ -20,7 +32,8 @@ const SubmitControls = (props: any): JSX.Element => { const cleanFlags: Flag[] = flags.filter( (item: Flag) => item.name !== '' ); - await updateFlags(cleanFlags); + + await updateFlags({ ...cleanFlags }); setIsSaving(false); diff --git a/src/index.ts b/src/index.ts index 93216f0..ff873d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,11 @@ const { mrFeatureFlags } = window; +import { Flag } from '../types'; mrFeatureFlags.isEnabled = (flag: string) => { - const isFlagExist = mrFeatureFlags.flags.find( - (item: { name: string; enabled: boolean }) => - item.name === flag && item.enabled === true + const isFlagExist: Flag | undefined = mrFeatureFlags.flags.find( + (item: Flag) => { + return item.name === flag && item.enabled === true; + } ); if (isFlagExist) return true; diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 6ec52ff..3b90bab 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -17,3 +17,7 @@ height: 20px; } } + +#mr-feature-flag-submit-controls { + margin-top: 20px; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index cf051e4..f478286 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,18 +1,16 @@ import apiFetch from '@wordpress/api-fetch'; import { FEATURE_FLAG_NAMESPACE, FEATURE_FLAG_ROUTE } from '../constants'; -import { Flag } from '../../types'; -export const getFlags = async (): Promise => { - const result: Flag[] | Error = await apiFetch({ +export const getFlags = async (): Promise => { + const result: any = await apiFetch({ method: 'GET', path: `${FEATURE_FLAG_NAMESPACE}/${FEATURE_FLAG_ROUTE}`, }); - return result; }; export const updateFlags = async ( - flags: Flag[] + flags: any ): Promise<{ status: number; success: boolean } | Error> => { const result: { status: number; success: boolean } | Error = await apiFetch( { diff --git a/types/index.ts b/types/index.ts index 5d20de7..c97faa3 100644 --- a/types/index.ts +++ b/types/index.ts @@ -2,4 +2,10 @@ export interface Flag { id: number; name: string; enabled: boolean; + preProdEnabled?: boolean; +} + +export interface FlagProps { + env: string; + flags: Flag[]; } diff --git a/types/window.d.ts b/types/window.d.ts index 133b4e6..29ce5d3 100644 --- a/types/window.d.ts +++ b/types/window.d.ts @@ -1,7 +1,19 @@ -export {}; +declare namespace mrFeatureFlag { + interface flags { + id: number; + name: string; + enabled: boolean; + } + export interface FeatureFlagProps { + isEnabled: (flag: string) => boolean; + flags: flags[]; + } +} declare global { interface Window { - mrFeatureFlags: any; + mrFeatureFlags: mrFeatureFlag.FeatureFlagProps; } } + +export {}; diff --git a/yarn.lock b/yarn.lock index 0f6d7be..070478f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,7 +996,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -1866,6 +1866,13 @@ dependencies: "@types/node" "*" +"@types/hast@^2.0.0": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + dependencies: + "@types/unist" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -1961,6 +1968,13 @@ dependencies: "@types/react" "*" +"@types/react-syntax-highlighter@^15.5.6": + version "15.5.6" + resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz#77c95e6b74d2be23208fcdcf187b93b47025f1b1" + integrity sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.0.21": version "18.0.28" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" @@ -3899,6 +3913,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5259,6 +5278,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" + integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== + dependencies: + format "^0.2.0" + faye-websocket@~0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -5428,6 +5454,11 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" @@ -5814,6 +5845,22 @@ has@^1.0.0, has@^1.0.3: dependencies: function-bind "^1.1.1" +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + header-case@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" @@ -5832,6 +5879,11 @@ highlight-words-core@^1.2.2: resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa" integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg== +highlight.js@^10.4.1, highlight.js@~10.7.0: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -7312,6 +7364,14 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowlight@^1.17.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" + integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== + dependencies: + fault "^1.0.0" + highlight.js "~10.7.0" + lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -8641,6 +8701,16 @@ pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" +prismjs@^1.27.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +prismjs@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== + progress@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" @@ -8668,6 +8738,13 @@ prop-types@^15.7.0, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + proxy-compare@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.3.0.tgz#ac9633ae52918ff9c9fcc54dfe6316c7a02d20ee" @@ -8841,6 +8918,17 @@ react-shallow-renderer@^16.13.1: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0 || ^18.0.0" +react-syntax-highlighter@^15.5.0: + version "15.5.0" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + react-test-renderer@^17.0.0: version "17.0.2" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" @@ -8970,6 +9058,15 @@ redux@^4.1.0, redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" +refractor@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" + integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.27.0" + regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" @@ -9579,6 +9676,11 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + spawnd@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/spawnd/-/spawnd-5.0.0.tgz#ea72200bdc468998e84e1c3e7b914ce85fc1c32c" @@ -10835,6 +10937,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"