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

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',
     'unified',
     'comma-separated-tokens',
     'comma-separated-tokens',
     'decode-named-character-reference',
     'decode-named-character-reference',
+    'html-void-elements',
+    'property-information',
     'space-separated-tokens',
     'space-separated-tokens',
     'trim-lines',
     'trim-lines',
+    'web-namespaces',
+    'vfile',
+    'zwitch',
     'emoticon',
     'emoticon',
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'micromark-', 'unist-']),
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'micromark-', 'unist-']),
   ];
   ];

+ 2 - 0
packages/app/package.json

@@ -154,6 +154,8 @@
     "react-multiline-clamp": "^2.0.0",
     "react-multiline-clamp": "^2.0.0",
     "reconnecting-websocket": "^4.4.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "redis": "^3.0.2",
+    "rehype-raw": "^6.1.1",
+    "rehype-sanitize": "^5.0.1",
     "rehype-slug": "^5.0.1",
     "rehype-slug": "^5.0.1",
     "rehype-toc": "^3.0.2",
     "rehype-toc": "^3.0.2",
     "remark-breaks": "^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
 # :tada: Welcome to GROWI
 
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![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.
 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.
 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 へようこそ
 # :tada: GROWI へようこそ
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![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 | ナレッジベースツールです。  
 GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。

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

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

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

@@ -1,10 +1,10 @@
 import React from 'react';
 import React from 'react';
 
 
 import i18next from 'i18next';
 import i18next from 'i18next';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 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';
 import { useCsrfToken } from '~/stores/context';
 
 
 class InstallerForm extends React.Component {
 class InstallerForm extends React.Component {
@@ -17,31 +17,31 @@ class InstallerForm extends React.Component {
       isSubmittingDisabled: false,
       isSubmittingDisabled: false,
       selectedLang: {},
       selectedLang: {},
     };
     };
-    // this.checkUserName = this.checkUserName.bind(this);
+    this.checkUserName = this.checkUserName.bind(this);
 
 
     this.submitHandler = this.submitHandler.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) {
   changeLanguage(meta) {
     i18next.changeLanguage(meta.id);
     i18next.changeLanguage(meta.id);
     this.setState({ selectedLang: meta });
     this.setState({ selectedLang: meta });
@@ -97,7 +97,7 @@ class InstallerForm extends React.Component {
                   value={this.state.selectedLang.id}
                   value={this.state.selectedLang.id}
                   name="registerForm[app:globalLang]"
                   name="registerForm[app:globalLang]"
                 />
                 />
-                <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
+                {/* <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
                   {
                   {
                     localeMetadatas.map(meta => (
                     localeMetadatas.map(meta => (
                       <button
                       <button
@@ -111,7 +111,7 @@ class InstallerForm extends React.Component {
                       </button>
                       </button>
                     ))
                     ))
                   }
                   }
-                </div>
+                </div> */}
               </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 Head from 'next/head';
 
 
 import { useGrowiTheme } from '~/stores/context';
 import { useGrowiTheme } from '~/stores/context';
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
 
 
 import { ThemeProvider } from '../Theme/utils/ThemeProvider';
 import { ThemeProvider } from '../Theme/utils/ThemeProvider';
 
 
@@ -22,7 +22,14 @@ export const RawLayout = ({ children, title, className }: Props): JSX.Element =>
   const { data: growiTheme } = useGrowiTheme();
   const { data: growiTheme } = useGrowiTheme();
 
 
   // get color scheme from next-themes
   // 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 (
   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 { HasChildren } from '../../interfaces/common';
 import GrowiLogo from '../Icons/GrowiLogo';
 import GrowiLogo from '../Icons/GrowiLogo';
 
 
+import { GlobalSearchProps } from './GlobalSearch';
 import PersonalDropdown from './PersonalDropdown';
 import PersonalDropdown from './PersonalDropdown';
 
 
 import styles from './GrowiNavbar.module.scss';
 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 NavbarRight = memo((): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
@@ -53,7 +46,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
     return (
       <>
       <>
         <li className="nav-item">
         <li className="nav-item">
-          <ShowSkeltonInSSR><InAppNotificationDropdown /></ShowSkeltonInSSR>
+          <InAppNotificationDropdown />
         </li>
         </li>
 
 
         <li className="nav-item d-none d-md-block">
         <li className="nav-item d-none d-md-block">
@@ -70,11 +63,11 @@ const NavbarRight = memo((): JSX.Element => {
         </li>
         </li>
 
 
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
         </li>
 
 
         <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
         <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-          <ShowSkeltonInSSR><PersonalDropdown /></ShowSkeltonInSSR>
+          <PersonalDropdown />
         </li>
         </li>
       </>
       </>
     );
     );
@@ -84,7 +77,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
     return (
       <>
       <>
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
         </li>
 
 
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></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
   // installer
   if (!isInstalled) {
   if (!isInstalled) {
     const installer = require('./installer')(crowi);
     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);
     app.post('/installer'             , applicationNotInstalled , registerFormValidator.registerRules(), registerFormValidator.registerValidation, csrfProtection, addActivity, installer.install);
     return;
     return;
   }
   }

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

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

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

@@ -223,7 +223,7 @@ type PreferDrawerModeByUserUtils = {
   update: (preferDrawerMode: boolean) => void
   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 { data: isGuestUser } = useIsGuestUser();
   const { scheduleToPut } = useUserUISettings();
   const { scheduleToPut } = useUserUISettings();
 
 

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

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

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

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

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

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

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

@@ -3,10 +3,7 @@
 @import '~bootstrap/scss/utilities';
 @import '~bootstrap/scss/utilities';
 @import '~bootstrap/scss/root';
 @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/functions';
 @import '~bootstrap/scss/variables';
 @import '~bootstrap/scss/variables';
+
+// merge $colors to $theme-colors
+$theme-colors: map-merge($theme-colors, $colors);
+
 @import '~bootstrap/scss/mixins';
 @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-monospace: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
 $font-family-base: $font-family-sans-serif;
 $font-family-base: $font-family-sans-serif;
 
 
+$font-size-base: 0.875rem;  // 16px -> 14px
 $line-height-base: 1.42857;
 $line-height-base: 1.42857;
 
 
 $blockquote-small-color: $gray-500;
 $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
 //== 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
 // Dropdown
 .grw-apperance-mode-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
 // 3. Set an explicit initial text-align value so that we can later use
 //    the `inherit` value on things like `<th>` elements.
 //    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;
   color: $body-color;
-  // text-align: left; // 3
   background-color: $body-bg; // 2
   background-color: $body-bg; // 2
 
 
   svg {
   svg {

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

@@ -1,8 +1,6 @@
 @use '../bootstrap/init' as *;
 @use '../bootstrap/init' as *;
 @use '../mixins';
 @use '../mixins';
 
 
-$theme-colors: map-merge($theme-colors, $colors);
-
 @each $color, $value in $theme-colors {
 @each $color, $value in $theme-colors {
   @include bg-variant('.bg-#{$color}', $value);
   @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 {
 @each $color, $value in $theme-colors {
   .badge-#{$color} {
   .badge-#{$color} {

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

@@ -1,7 +1,7 @@
 import { SWRResponse } from 'swr';
 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);
   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"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
   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":
 "@types/pixelmatch@^5.2.2":
   version "5.2.4"
   version "5.2.4"
   resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6"
   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"
   resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512"
   integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==
   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:
 hast-util-from-parse5@^5.0.0:
   version "5.0.3"
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz#3089dc0ee2ccf6ec8bc416919b51a54a589e097c"
   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"
     web-namespaces "^1.1.2"
     xtend "^4.0.1"
     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:
 hast-util-has-property@^2.0.0:
   version "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"
   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"
   resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
   integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
   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:
 hast-util-to-string@^2.0.0:
   version "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"
   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"
     property-information "^5.0.0"
     space-separated-tokens "^1.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:
 header-case@^2.0.3, header-case@^2.0.4:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063"
   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"
   resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
   integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
   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:
 htmlparser2@3.8.x:
   version "3.8.3"
   version "3.8.3"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
@@ -15869,7 +15960,7 @@ parse5-htmlparser2-tree-adapter@^6.0.0:
   dependencies:
   dependencies:
     parse5 "^6.0.1"
     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"
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
   integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
   integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
@@ -17544,6 +17635,24 @@ rehype-parse@^6.0.1:
     parse5 "^5.0.0"
     parse5 "^5.0.0"
     xtend "^4.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:
 rehype-slug@^5.0.1:
   version "5.0.1"
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-5.0.1.tgz#6e732d0c55b3b1e34187e74b7363fb53229e5f52"
   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"
   resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e"
   integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==
   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:
 vfile-message@^1.0.0:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
   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"
   resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
   integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
   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:
 webidl-conversions@^3.0.0:
   version "3.0.1"
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"