Просмотр исходного кода

Merge branch 'support/apply-nextjs-2' into imprv/100207-next-toc

yuken 3 лет назад
Родитель
Сommit
31b1036be5

+ 5 - 0
packages/app/next.config.js

@@ -22,8 +22,13 @@ const setupWithTM = () => {
     'unified',
     'comma-separated-tokens',
     'decode-named-character-reference',
+    'html-void-elements',
+    'property-information',
     'space-separated-tokens',
     'trim-lines',
+    'web-namespaces',
+    'vfile',
+    'zwitch',
     'emoticon',
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'micromark-', 'unist-']),
   ];

+ 2 - 0
packages/app/package.json

@@ -154,6 +154,8 @@
     "react-multiline-clamp": "^2.0.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
+    "rehype-raw": "^6.1.1",
+    "rehype-sanitize": "^5.0.1",
     "rehype-slug": "^5.0.1",
     "rehype-toc": "^3.0.2",
     "remark-breaks": "^3.0.2",

+ 1 - 1
packages/app/resource/locales/en_US/welcome.md

@@ -1,7 +1,7 @@
 # :tada: Welcome to GROWI
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
 Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.

+ 1 - 1
packages/app/resource/locales/ja_JP/welcome.md

@@ -1,6 +1,6 @@
 # :tada: GROWI へようこそ
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。

+ 1 - 1
packages/app/resource/locales/zh_CN/welcome.md

@@ -1,7 +1,7 @@
 # :tada: 欢迎来到GROWI
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。

+ 23 - 23
packages/app/src/components/InstallerForm.jsx

@@ -1,10 +1,10 @@
 import React from 'react';
 
 import i18next from 'i18next';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
-import { localeMetadatas } from '~/client/util/i18n';
+// import { localeMetadatas } from '~/client/util/i18n';
 import { useCsrfToken } from '~/stores/context';
 
 class InstallerForm extends React.Component {
@@ -17,31 +17,31 @@ class InstallerForm extends React.Component {
       isSubmittingDisabled: false,
       selectedLang: {},
     };
-    // this.checkUserName = this.checkUserName.bind(this);
+    this.checkUserName = this.checkUserName.bind(this);
 
     this.submitHandler = this.submitHandler.bind(this);
   }
 
-  UNSAFE_componentWillMount() {
-    const meta = localeMetadatas.find(v => v.id === i18next.language);
-    if (meta == null) {
-      return this.setState({ selectedLang: localeMetadatas[0] });
-    }
-    this.setState({ selectedLang: meta });
-  }
-
-  // checkUserName(event) {
-  //   const axios = require('axios').create({
-  //     headers: {
-  //       'Content-Type': 'application/json',
-  //       'X-Requested-With': 'XMLHttpRequest',
-  //     },
-  //     responseType: 'json',
-  //   });
-  //   axios.get('/_api/v3/check-username', { params: { username: event.target.value } })
-  //     .then((res) => { return this.setState({ isValidUserName: res.data.valid }) });
+  // UNSAFE_componentWillMount() {
+  //   const meta = localeMetadatas.find(v => v.id === i18next.language);
+  //   if (meta == null) {
+  //     return this.setState({ selectedLang: localeMetadatas[0] });
+  //   }
+  //   this.setState({ selectedLang: meta });
   // }
 
+  checkUserName(event) {
+    const axios = require('axios').create({
+      headers: {
+        'Content-Type': 'application/json',
+        'X-Requested-With': 'XMLHttpRequest',
+      },
+      responseType: 'json',
+    });
+    axios.get('/_api/v3/check-username', { params: { username: event.target.value } })
+      .then((res) => { return this.setState({ isValidUserName: res.data.valid }) });
+  }
+
   changeLanguage(meta) {
     i18next.changeLanguage(meta.id);
     this.setState({ selectedLang: meta });
@@ -97,7 +97,7 @@ class InstallerForm extends React.Component {
                   value={this.state.selectedLang.id}
                   name="registerForm[app:globalLang]"
                 />
-                <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
+                {/* <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
                   {
                     localeMetadatas.map(meta => (
                       <button
@@ -111,7 +111,7 @@ class InstallerForm extends React.Component {
                       </button>
                     ))
                   }
-                </div>
+                </div> */}
               </div>
             </div>
 

+ 10 - 3
packages/app/src/components/Layout/RawLayout.tsx

@@ -1,9 +1,9 @@
-import React, { ReactNode } from 'react';
+import React, { ReactNode, useEffect, useState } from 'react';
 
-import { useTheme } from 'next-themes';
 import Head from 'next/head';
 
 import { useGrowiTheme } from '~/stores/context';
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
 
 import { ThemeProvider } from '../Theme/utils/ThemeProvider';
 
@@ -22,7 +22,14 @@ export const RawLayout = ({ children, title, className }: Props): JSX.Element =>
   const { data: growiTheme } = useGrowiTheme();
 
   // get color scheme from next-themes
-  const { resolvedTheme: colorScheme } = useTheme();
+  const { resolvedTheme } = useNextThemes();
+
+  const [colorScheme, setColorScheme] = useState<Themes|undefined>(undefined);
+
+  // set colorScheme in CSR
+  useEffect(() => {
+    setColorScheme(resolvedTheme as Themes);
+  }, [resolvedTheme]);
 
   return (
     <>

+ 5 - 12
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -18,19 +18,12 @@ import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 import { HasChildren } from '../../interfaces/common';
 import GrowiLogo from '../Icons/GrowiLogo';
 
+import { GlobalSearchProps } from './GlobalSearch';
 import PersonalDropdown from './PersonalDropdown';
 
 import styles from './GrowiNavbar.module.scss';
-import { GlobalSearchProps } from './GlobalSearch';
 
 
-const ShowSkeltonInSSR = memo(({ children }: HasChildren): JSX.Element => {
-  return isServer()
-    ? <></>
-    : <>{children}</>;
-});
-ShowSkeltonInSSR.displayName = 'ShowSkeltonInSSR';
-
 const NavbarRight = memo((): JSX.Element => {
   const { t } = useTranslation();
 
@@ -53,7 +46,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
       <>
         <li className="nav-item">
-          <ShowSkeltonInSSR><InAppNotificationDropdown /></ShowSkeltonInSSR>
+          <InAppNotificationDropdown />
         </li>
 
         <li className="nav-item d-none d-md-block">
@@ -70,11 +63,11 @@ const NavbarRight = memo((): JSX.Element => {
         </li>
 
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
 
         <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-          <ShowSkeltonInSSR><PersonalDropdown /></ShowSkeltonInSSR>
+          <PersonalDropdown />
         </li>
       </>
     );
@@ -84,7 +77,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
       <>
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
 
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;

+ 89 - 0
packages/app/src/pages/installer.page.tsx

@@ -0,0 +1,89 @@
+import React from 'react';
+
+import { pagePathUtils } from '@growi/core';
+import {
+  NextPage, GetServerSideProps, GetServerSidePropsContext,
+} from 'next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+
+import { RawLayout } from '~/components/Layout/RawLayout';
+
+import InstallerForm from '../components/InstallerForm';
+import {
+  useCurrentPagePath, useCsrfToken,
+  useAppTitle, useSiteUrl, useConfidential,
+} from '../stores/context';
+
+
+import {
+  CommonProps, getNextI18NextConfig, getServerSideCommonProps, useCustomTitle,
+} from './commons';
+
+
+const { isTrashPage: _isTrashPage } = pagePathUtils;
+
+async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+  props._nextI18Next = nextI18NextConfig._nextI18Next;
+}
+
+type Props = CommonProps & {
+
+  pageWithMetaStr: string,
+};
+
+const InstallerPage: NextPage<Props> = (props: Props) => {
+
+  // commons
+  useAppTitle(props.appTitle);
+  useSiteUrl(props.siteUrl);
+  useConfidential(props.confidential);
+  useCsrfToken(props.csrfToken);
+
+  // page
+  useCurrentPagePath(props.currentPathname);
+
+  const classNames: string[] = [];
+
+  return (
+    <>
+      <RawLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
+        <div id="page-wrapper">
+          <div className="main container-fluid">
+
+            <div className="row">
+              <div className="col-md-12">
+                <div className="login-header mx-auto">
+                  <h1 className="my-3">GROWI</h1>
+                </div>
+              </div>
+              <div className="col-md-12">
+                <InstallerForm />
+              </div>
+            </div>
+          </div>
+        </div>
+      </RawLayout>
+    </>
+  );
+};
+
+export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+  const result = await getServerSideCommonProps(context);
+
+  // check for presence
+  // see: https://github.com/vercel/next.js/issues/19271#issuecomment-730006862
+  if (!('props' in result)) {
+    throw new Error('invalid getSSP result');
+  }
+
+  const props: Props = result.props as Props;
+
+  injectNextI18NextConfigurations(context, props, ['translation']);
+
+  return {
+    props,
+  };
+};
+
+export default InstallerPage;

+ 1 - 1
packages/app/src/server/routes/index.js

@@ -94,7 +94,7 @@ module.exports = function(crowi, app) {
   // installer
   if (!isInstalled) {
     const installer = require('./installer')(crowi);
-    app.get('/installer'              , applicationNotInstalled , installer.index);
+    app.get('/installer'              , applicationNotInstalled, next.delegateToNext);
     app.post('/installer'             , applicationNotInstalled , registerFormValidator.registerRules(), registerFormValidator.registerValidation, csrfProtection, addActivity, installer.install);
     return;
   }

+ 13 - 1
packages/app/src/services/renderer/renderer.tsx

@@ -1,4 +1,6 @@
 import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import raw from 'rehype-raw';
+import sanitize, { defaultSchema } from 'rehype-sanitize';
 import slug from 'rehype-slug';
 import toc, { HtmlElementNode } from 'rehype-toc';
 import breaks from 'remark-breaks';
@@ -214,7 +216,17 @@ export interface ReactMarkdownOptionsGenerator {
 const generateCommonOptions: ReactMarkdownOptionsGenerator = (config: RendererConfig): RendererOptions => {
   return {
     remarkPlugins: [gfm],
-    rehypePlugins: [slug],
+    rehypePlugins: [
+      slug,
+      raw,
+      [sanitize, {
+        ...defaultSchema,
+        attributes: {
+          ...defaultSchema.attributes,
+          '*': ['className', 'class'],
+        },
+      }],
+    ],
     components: {
       a: NextLink,
     },

+ 1 - 1
packages/app/src/stores/ui.tsx

@@ -223,7 +223,7 @@ type PreferDrawerModeByUserUtils = {
   update: (preferDrawerMode: boolean) => void
 }
 
-export const usePreferDrawerModeByUser = (initialData?: boolean): SWRResponseWithUtils<SWRResponse, PreferDrawerModeByUserUtils> => {
+export const usePreferDrawerModeByUser = (initialData?: boolean): SWRResponseWithUtils<PreferDrawerModeByUserUtils, boolean> => {
   const { data: isGuestUser } = useIsGuestUser();
   const { scheduleToPut } = useUserUISettings();
 

+ 0 - 4
packages/app/src/styles/_layout.scss

@@ -1,10 +1,6 @@
 @use './variables' as var;
 @use './bootstrap/init' as bs;
 
-:root {
-  font-size: var.$font-size-root;
-}
-
 body {
   overflow-y: scroll !important;
   overscroll-behavior-y: none;

+ 0 - 2
packages/app/src/styles/_variables.scss

@@ -1,5 +1,3 @@
-$font-size-root: 14px;
-
 //== GROWI Official Color
 $growi-green: #74bc46;
 $growi-blue: #175fa5;

+ 1 - 1
packages/app/src/styles/atoms/_buttons.scss

@@ -39,7 +39,7 @@
 }
 
 // fill button style
-:root .btn.btn-fill {
+.btn.btn-fill {
   position: relative;
   display: flex;
   justify-content: space-between;

+ 4 - 7
packages/app/src/styles/bootstrap/_apply.scss

@@ -3,10 +3,7 @@
 @import '~bootstrap/scss/utilities';
 @import '~bootstrap/scss/root';
 
-// increase specificity with ':root' for GROWI theming
-:root {
-  // import bootstrap
-  @import '~bootstrap/scss/bootstrap';
-  // override
-  @import './override';
-}
+// import bootstrap
+@import '~bootstrap/scss/bootstrap';
+// override
+@import './override';

+ 4 - 0
packages/app/src/styles/bootstrap/_init.scss

@@ -2,4 +2,8 @@
 
 @import '~bootstrap/scss/functions';
 @import '~bootstrap/scss/variables';
+
+// merge $colors to $theme-colors
+$theme-colors: map-merge($theme-colors, $colors);
+
 @import '~bootstrap/scss/mixins';

+ 1 - 0
packages/app/src/styles/bootstrap/_variables.scss

@@ -70,6 +70,7 @@ $font-family-serif: Georgia, 'Times New Roman', Times, serif;
 $font-family-monospace: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
 $font-family-base: $font-family-sans-serif;
 
+$font-size-base: 0.875rem;  // 16px -> 14px
 $line-height-base: 1.42857;
 
 $blockquote-small-color: $gray-500;

+ 9 - 9
packages/app/src/styles/theme/_apply-colors.scss

@@ -65,15 +65,15 @@ pre:not(.hljs):not(.CodeMirror-line) {
 //== Apply to Bootstrap Elements
 //
 
-// // Alert link
-// @each $color, $value in $theme-colors {
-//   .alert.alert-#{$color} {
-//     a,
-//     a:hover {
-//       color: theme-color-level($color, $alert-color-level - 2);
-//     }
-//   }
-// }
+// Alert link
+@each $color, $value in $theme-colors {
+  .alert.alert-#{$color} {
+    a,
+    a:hover {
+      color: theme-color-level($color, $alert-color-level - 2);
+    }
+  }
+}
 
 // Dropdown
 .grw-apperance-mode-dropdown {

+ 1 - 7
packages/app/src/styles/theme/_reboot-bootstrap-colors.scss

@@ -14,14 +14,8 @@
 // 3. Set an explicit initial text-align value so that we can later use
 //    the `inherit` value on things like `<th>` elements.
 
-body {
-  // margin: 0; // 1
-  // font-family: $font-family-base;
-  // @include font-size($font-size-base);
-  // font-weight: $font-weight-base;
-  // line-height: $line-height-base;
+& {
   color: $body-color;
-  // text-align: left; // 3
   background-color: $body-bg; // 2
 
   svg {

+ 9 - 11
packages/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss

@@ -1,8 +1,6 @@
 @use '../bootstrap/init' as *;
 @use '../mixins';
 
-$theme-colors: map-merge($theme-colors, $colors);
-
 @each $color, $value in $theme-colors {
   @include bg-variant('.bg-#{$color}', $value);
 }
@@ -64,15 +62,15 @@ $theme-colors: map-merge($theme-colors, $colors);
   }
 }
 
-// @each $color, $value in $theme-colors {
-//   .alert-#{$color} {
-//     @include alert-variant(
-//       theme-color-level($color, $alert-bg-level),
-//       theme-color-level($color, $alert-border-level),
-//       theme-color-level($color, $alert-color-level)
-//     );
-//   }
-// }
+@each $color, $value in $theme-colors {
+  .alert-#{$color} {
+    @include alert-variant(
+      theme-color-level($color, $alert-bg-level),
+      theme-color-level($color, $alert-border-level),
+      theme-color-level($color, $alert-color-level)
+    );
+  }
+}
 
 @each $color, $value in $theme-colors {
   .badge-#{$color} {

+ 2 - 2
packages/core/src/utils/with-utils.ts

@@ -1,7 +1,7 @@
 import { SWRResponse } from 'swr';
 
-export type SWRResponseWithUtils<R extends SWRResponse, U> = R & U;
+export type SWRResponseWithUtils<U, D = any, E = any> = SWRResponse<D, E> & U;
 
-export const withUtils = <R extends SWRResponse, U>(response: R, utils: U): SWRResponseWithUtils<R, U> => {
+export const withUtils = <U, D = any, E = any>(response: SWRResponse<D, E>, utils: U): SWRResponseWithUtils<U, D, E> => {
   return Object.assign(response, utils);
 };

+ 123 - 1
yarn.lock

@@ -4196,6 +4196,11 @@
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
+"@types/parse5@^6.0.0":
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
+  integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
+
 "@types/pixelmatch@^5.2.2":
   version "5.2.4"
   resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6"
@@ -10444,6 +10449,19 @@ hash-stream-validation@^0.2.2:
   resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512"
   integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==
 
+hast-to-hyperscript@^10.0.0:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz#3decd7cb4654bca8883f6fcbd4fb3695628c4296"
+  integrity sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    comma-separated-tokens "^2.0.0"
+    property-information "^6.0.0"
+    space-separated-tokens "^2.0.0"
+    style-to-object "^0.3.0"
+    unist-util-is "^5.0.0"
+    web-namespaces "^2.0.0"
+
 hast-util-from-parse5@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz#3089dc0ee2ccf6ec8bc416919b51a54a589e097c"
@@ -10455,6 +10473,20 @@ hast-util-from-parse5@^5.0.0:
     web-namespaces "^1.1.2"
     xtend "^4.0.1"
 
+hast-util-from-parse5@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz#c129dd3a24dd8a867ab8a029ca47e27aa54864b7"
+  integrity sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/parse5" "^6.0.0"
+    "@types/unist" "^2.0.0"
+    hastscript "^7.0.0"
+    property-information "^6.0.0"
+    vfile "^5.0.0"
+    vfile-location "^4.0.0"
+    web-namespaces "^2.0.0"
+
 hast-util-has-property@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-2.0.0.tgz#c15cd6180f3e535540739fcc9787bcffb5708cae"
@@ -10472,6 +10504,49 @@ hast-util-parse-selector@^2.0.0:
   resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
   integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
 
+hast-util-parse-selector@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz#a519e27e8b61bd5a98fad494ed06131ce68d9c3f"
+  integrity sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==
+  dependencies:
+    "@types/hast" "^2.0.0"
+
+hast-util-raw@^7.2.0:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.1.tgz#6e964cee098dbdd93d1b77cf180b5827d48048ab"
+  integrity sha512-wgtppqXVdXzkDXDFclLLdAyVUJSKMYYi6LWIAbA8oFqEdwksYIcPGM3RkKV1Dfn5GElvxhaOCs0jmCOMayxd3A==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/parse5" "^6.0.0"
+    hast-util-from-parse5 "^7.0.0"
+    hast-util-to-parse5 "^7.0.0"
+    html-void-elements "^2.0.0"
+    parse5 "^6.0.0"
+    unist-util-position "^4.0.0"
+    unist-util-visit "^4.0.0"
+    vfile "^5.0.0"
+    web-namespaces "^2.0.0"
+    zwitch "^2.0.0"
+
+hast-util-sanitize@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz#71a02ca2e50d04b852a5500846418070ca364f60"
+  integrity sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==
+  dependencies:
+    "@types/hast" "^2.0.0"
+
+hast-util-to-parse5@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.0.0.tgz#a39808e69005d10afeed1866029a1fb137df3f7c"
+  integrity sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/parse5" "^6.0.0"
+    hast-to-hyperscript "^10.0.0"
+    property-information "^6.0.0"
+    web-namespaces "^2.0.0"
+    zwitch "^2.0.0"
+
 hast-util-to-string@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a"
@@ -10494,6 +10569,17 @@ hastscript@^5.0.0:
     property-information "^5.0.0"
     space-separated-tokens "^1.0.0"
 
+hastscript@^7.0.0:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.0.2.tgz#d811fc040817d91923448a28156463b2e40d590a"
+  integrity sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    comma-separated-tokens "^2.0.0"
+    hast-util-parse-selector "^3.0.0"
+    property-information "^6.0.0"
+    space-separated-tokens "^2.0.0"
+
 header-case@^2.0.3, header-case@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063"
@@ -10584,6 +10670,11 @@ html-tags@^3.1.0:
   resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
   integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
 
+html-void-elements@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
+  integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
+
 htmlparser2@3.8.x:
   version "3.8.3"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
@@ -15869,7 +15960,7 @@ parse5-htmlparser2-tree-adapter@^6.0.0:
   dependencies:
     parse5 "^6.0.1"
 
-parse5@6.0.1, parse5@^6.0.1:
+parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
   integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
@@ -17544,6 +17635,24 @@ rehype-parse@^6.0.1:
     parse5 "^5.0.0"
     xtend "^4.0.0"
 
+rehype-raw@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-6.1.1.tgz#81bbef3793bd7abacc6bf8335879d1b6c868c9d4"
+  integrity sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    hast-util-raw "^7.2.0"
+    unified "^10.0.0"
+
+rehype-sanitize@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-5.0.1.tgz#dac01a7417bdd329260c74c74449697b4be5eb56"
+  integrity sha512-da/jIOjq8eYt/1r9GN6GwxIR3gde7OZ+WV8pheu1tL8K0D9KxM2AyMh+UEfke+FfdM3PvGHeYJU0Td5OWa7L5A==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    hast-util-sanitize "^4.0.0"
+    unified "^10.0.0"
+
 rehype-slug@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-5.0.1.tgz#6e732d0c55b3b1e34187e74b7363fb53229e5f52"
@@ -21171,6 +21280,14 @@ vfile-location@^2.0.0:
   resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e"
   integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==
 
+vfile-location@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.0.1.tgz#06f2b9244a3565bef91f099359486a08b10d3a95"
+  integrity sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    vfile "^5.0.0"
+
 vfile-message@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
@@ -21275,6 +21392,11 @@ web-namespaces@^1.1.2:
   resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
   integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
 
+web-namespaces@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
+  integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
+
 webidl-conversions@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"