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"