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

Merge branch 'support/apply-nextjs-PageComment-integrate' into support/isLoading-PageComment

jam411 3 лет назад
Родитель
Сommit
3795573012
33 измененных файлов с 256 добавлено и 229 удалено
  1. 2 2
      .github/workflows/reusable-app-prod.yml
  2. 0 0
      packages/app/_obsolete/src/client/boot.js
  3. 0 0
      packages/app/_obsolete/src/util/i18n.js
  4. 0 0
      packages/app/_obsolete/src/util/locale-utils.js
  5. 0 0
      packages/app/_obsolete/src/util/old-ios.js
  6. 0 21
      packages/app/next.config.js
  7. 6 4
      packages/app/package.json
  8. 3 1
      packages/app/src/components/Me/BasicInfoSettings.tsx
  9. 2 2
      packages/app/src/components/Navbar/GrowiSubNavigation.tsx
  10. 36 31
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  11. 8 6
      packages/app/src/components/Page.jsx
  12. 1 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  13. 2 4
      packages/app/src/components/PageAlert/PageGrantAlert.tsx
  14. 1 1
      packages/app/src/components/PageComment/Comment.tsx
  15. 8 12
      packages/app/src/components/PageComment/CommentControl.tsx
  16. 1 12
      packages/app/src/components/PrivateLegacyPages.tsx
  17. 22 0
      packages/app/src/components/ReactMarkdownComponents/CodeBlock.module.scss
  18. 33 0
      packages/app/src/components/ReactMarkdownComponents/CodeBlock.tsx
  19. 5 23
      packages/app/src/components/SearchPage.tsx
  20. 10 9
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  21. 2 6
      packages/app/src/components/SearchPage2/SearchPageBase.tsx
  22. 5 0
      packages/app/src/interfaces/theme.ts
  23. 6 8
      packages/app/src/pages/[[...path]].page.tsx
  24. 5 21
      packages/app/src/pages/_document.page.tsx
  25. 15 15
      packages/app/src/pages/_search.page.tsx
  26. 2 0
      packages/app/src/services/renderer/renderer.tsx
  27. 8 1
      packages/app/src/stores/page.tsx
  28. 1 1
      packages/app/src/styles/_search.scss
  29. 4 0
      packages/app/src/styles/_wiki.scss
  30. 0 11
      packages/app/src/styles/atoms/_code.scss
  31. 1 1
      packages/app/src/styles/style-next.scss
  32. 5 8
      packages/app/src/styles/theme/_apply-colors.scss
  33. 62 27
      yarn.lock

+ 2 - 2
.github/workflows/reusable-app-prod.yml

@@ -75,7 +75,7 @@ jobs:
     - name: Upload production files as artifact
       uses: actions/upload-artifact@v3
       with:
-        name: Production Files
+        name: Production Files (node${{ inputs.node-version }})
         path: ${{ steps.archive-prod-files.outputs.file }}
 
     - name: Upload report as artifact
@@ -155,7 +155,7 @@ jobs:
     - name: Download production files artifact
       uses: actions/download-artifact@v3
       with:
-        name: Production Files
+        name: Production Files (node${{ inputs.node-version }})
 
     - name: Extract procution files artifact
       run: |

+ 0 - 0
packages/app/src/client/boot.js → packages/app/_obsolete/src/client/boot.js


+ 0 - 0
packages/app/src/client/util/i18n.js → packages/app/_obsolete/src/util/i18n.js


+ 0 - 0
packages/app/src/client/util/locale-utils.js → packages/app/_obsolete/src/util/locale-utils.js


+ 0 - 0
packages/app/src/client/util/old-ios.js → packages/app/_obsolete/src/util/old-ios.js


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

@@ -9,12 +9,6 @@ const { withSuperjson } = require('next-superjson');
 const { PHASE_PRODUCTION_BUILD, PHASE_PRODUCTION_SERVER } = require('next/constants');
 
 
-// define additional entries
-const additionalWebpackEntries = {
-  boot: './src/client/boot',
-};
-
-
 const setupTranspileModules = () => {
   const eazyLogger = require('eazy-logger');
   const { listScopedPackages, listPrefixedPackages } = require('./src/utils/next.config.utils');
@@ -87,21 +81,6 @@ module.exports = async(phase, { defaultConfig }) => {
       config.externals.push('dtrace-provider');
       config.externals.push('mongoose');
 
-      // configure additional entries
-      const orgEntry = config.entry;
-      config.entry = () => {
-        return orgEntry().then((entry) => {
-          return { ...entry, ...additionalWebpackEntries };
-        });
-      };
-
-      const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
-      config.plugins.push(
-        new WebpackManifestPlugin({
-          fileName: 'custom-manifest.json',
-        }),
-      );
-
       // setup i18next-hmr
       if (!options.isServer && options.dev) {
         const { I18NextHMRPlugin } = require('i18next-hmr/plugin');

+ 6 - 4
packages/app/package.json

@@ -45,8 +45,9 @@
     "swagger-jsdoc": "swagger-jsdoc -o tmp/swagger.json -d config/swagger-definition.js",
     "openapi:v3": "yarn cross-env API_VERSION=3 yarn swagger-jsdoc -- \"src/server/routes/apiv3/**/*.js\" \"src/server/models/**/*.js\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
-    "resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
-    "resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
+    "resources:dummy": "true",
+    "// resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
+    "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
     "ts-node": "ts-node -r tsconfig-paths/register -r dotenv-flow/config --transpile-only"
   },
   "// comments for dependencies": {
@@ -143,6 +144,7 @@
     "passport-local": "^1.0.0",
     "passport-saml": "^3.2.0",
     "passport-twitter": "^1.0.4",
+    "prism-themes": "^1.9.0",
     "prom-client": "^13.0.0",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
@@ -154,6 +156,7 @@
     "react-image-crop": "^8.3.0",
     "react-markdown": "^8.0.3",
     "react-multiline-clamp": "^2.0.0",
+    "react-syntax-highlighter": "^15.5.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "rehype-katex": "^6.0.2",
@@ -250,7 +253,6 @@
     "ts-node": "^9.1.1",
     "ts-node-dev": "^2.0.0",
     "tsc-alias": "^1.2.9",
-    "unstated": "^2.1.1",
-    "webpack-manifest-plugin": "^5.0.0"
+    "unstated": "^2.1.1"
   }
 }

+ 3 - 1
packages/app/src/components/Me/BasicInfoSettings.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next';
 
 import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { localeMetadatas } from '~/client/util/i18n';
+// import { localeMetadatas } from '~/client/util/i18n';
 import { usePersonalSettings } from '~/stores/personal-settings';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
@@ -113,6 +113,7 @@ const BasicInfoSettings = (props: Props) => {
       <div className="form-group row">
         <label className="text-left text-md-right col-md-3 col-form-label">{t('Language')}</label>
         <div className="col-md-6">
+          {/*
           {
             localeMetadatas.map(meta => (
               <div key={meta.id} className="custom-control custom-radio custom-control-inline">
@@ -128,6 +129,7 @@ const BasicInfoSettings = (props: Props) => {
               </div>
             ))
           }
+          */}
         </div>
       </div>
       <div className="form-group row">

+ 2 - 2
packages/app/src/components/Navbar/GrowiSubNavigation.tsx

@@ -19,7 +19,7 @@ import AuthorInfoStyles from './AuthorInfo.module.scss';
 import styles from './GrowiSubNavigation.module.scss';
 
 
-type Props = {
+export type GrowiSubNavigationProps = {
   page: Partial<IPageHasId>,
 
   showDrawerToggler?: boolean,
@@ -37,7 +37,7 @@ type Props = {
   additionalClasses?: string[],
 }
 
-export const GrowiSubNavigation = (props: Props): JSX.Element => {
+export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element => {
 
   const TagLabels = dynamic(() => import('../Page/TagLabels'), {
     ssr: false,

+ 36 - 31
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -33,7 +33,12 @@ const GrowiSubNavigationSwitcher = (props) => {
   const [width, setWidth] = useState(null);
 
   const fixedContainerRef = useRef();
-  const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
+  /*
+  * Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
+  * The above err occurs when moving to admin page after rendering normal pages.
+  * This is because id "grw-subnav-sticky-trigger" does not exist on admin pages.
+  */
+  // const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
 
   const initWidth = useCallback(() => {
     const instance = fixedContainerRef.current;
@@ -48,18 +53,18 @@ const GrowiSubNavigationSwitcher = (props) => {
     setWidth(clientWidth);
   }, []);
 
-  const initVisible = useCallback(() => {
-    const elements = stickyEvents.stickyElements;
+  // const initVisible = useCallback(() => {
+  //   const elements = stickyEvents.stickyElements;
 
-    for (const elem of elements) {
-      const bool = stickyEvents.isSticking(elem);
-      if (bool) {
-        setVisible(bool);
-        break;
-      }
-    }
+  //   for (const elem of elements) {
+  //     const bool = stickyEvents.isSticking(elem);
+  //     if (bool) {
+  //       setVisible(bool);
+  //       break;
+  //     }
+  //   }
 
-  }, [stickyEvents]);
+  // }, [stickyEvents]);
 
   // setup effect by resizing event
   useEffect(() => {
@@ -78,19 +83,19 @@ const GrowiSubNavigationSwitcher = (props) => {
     setVisible(event.detail.isSticky);
   }, []);
 
-  // setup effect by sticky event
-  useEffect(() => {
-    // sticky
-    // See: https://github.com/ryanwalters/sticky-events
-    const { stickySelector } = stickyEvents;
-    const elem = document.querySelector(stickySelector);
-    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+  // // setup effect by sticky event
+  // useEffect(() => {
+  //   // sticky
+  //   // See: https://github.com/ryanwalters/sticky-events
+  //   const { stickySelector } = stickyEvents;
+  //   const elem = document.querySelector(stickySelector);
+  //   elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
 
-    // return clean up handler
-    return () => {
-      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-    };
-  }, [stickyChangeHandler, stickyEvents]);
+  //   // return clean up handler
+  //   return () => {
+  //     elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+  //   };
+  // }, [stickyChangeHandler, stickyEvents]);
 
   // update width when sidebar collapsing changed
   useEffect(() => {
@@ -99,16 +104,16 @@ const GrowiSubNavigationSwitcher = (props) => {
     }
   }, [isSidebarCollapsed, initWidth]);
 
-  // initialize
-  useEffect(() => {
-    initWidth();
+  // // initialize
+  // useEffect(() => {
+  //   initWidth();
 
-    // check sticky state several times
-    setTimeout(initVisible, 100);
-    setTimeout(initVisible, 300);
-    setTimeout(initVisible, 2000);
+  //   // check sticky state several times
+  //   setTimeout(initVisible, 100);
+  //   setTimeout(initVisible, 300);
+  //   setTimeout(initVisible, 2000);
 
-  }, [initWidth, initVisible]);
+  // }, [initWidth, initVisible]);
 
   // ${styles['grw-subnav-switcher']}
 

+ 8 - 6
packages/app/src/components/Page.jsx

@@ -1,14 +1,13 @@
 import React, {
-  useCallback, useEffect, useMemo, useRef, useState,
+  useEffect, useRef, useState,
 } from 'react';
 
 import dynamic from 'next/dynamic';
 import PropTypes from 'prop-types';
-import { debounce } from 'throttle-debounce';
+// import { debounce } from 'throttle-debounce';
 
-import MarkdownTable from '~/client/models/MarkdownTable';
 import { blinkSectionHeaderAtBoot } from '~/client/util/blink-section-header';
-import { getOptionsToSave } from '~/client/util/editor';
+// import { getOptionsToSave } from '~/client/util/editor';
 import {
   useIsGuestUser, useIsBlinkedHeaderAtBoot,
 } from '~/stores/context';
@@ -23,8 +22,11 @@ import {
 import loggerFactory from '~/utils/logger';
 
 import RevisionRenderer from './Page/RevisionRenderer';
-import mdu from './PageEditor/MarkdownDrawioUtil';
-import mtu from './PageEditor/MarkdownTableUtil';
+
+// TODO: import dynamically
+// import MarkdownTable from '~/client/models/MarkdownTable';
+// import mdu from './PageEditor/MarkdownDrawioUtil';
+// import mtu from './PageEditor/MarkdownTableUtil';
 
 const logger = loggerFactory('growi:Page');
 

+ 1 - 2
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { TabContent, TabPane } from 'reactstrap';
 
-import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
+// import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound, useIsNotCreatable,
 } from '~/stores/context';
@@ -15,7 +15,6 @@ import { EditorMode, useEditorMode } from '~/stores/ui';
 
 import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
-import NotFoundPage from '../NotFoundPage';
 import { Page } from '../Page';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 import TableOfContents from '../TableOfContents';

+ 2 - 4
packages/app/src/components/PageAlert/PageGrantAlert.tsx

@@ -3,15 +3,13 @@ import React from 'react';
 import { useTranslation } from 'react-i18next';
 
 import { useSWRxCurrentPage } from '~/stores/page';
-import { useXss } from '~/stores/xss';
 
 
 export const PageGrantAlert = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: pageData } = useSWRxCurrentPage();
-  const { data: xss } = useXss();
 
-  if (pageData == null || pageData.grant == null || pageData.grant === 1 || xss == null) {
+  if (pageData == null || pageData.grant == null || pageData.grant === 1) {
     return <></>;
   }
 
@@ -34,7 +32,7 @@ export const PageGrantAlert = (): JSX.Element => {
       if (pageData.grant === 5) {
         return (
           <>
-            <i className="icon-fw icon-organization"></i><strong>{xss.process(pageData.grantedGroup.name)} only</strong>
+            <i className="icon-fw icon-organization"></i><strong>{pageData.grantedGroup.name} only</strong>
           </>
         );
       }

+ 1 - 1
packages/app/src/components/PageComment/Comment.tsx

@@ -14,7 +14,7 @@ import HistoryIcon from '../Icons/HistoryIcon';
 import RevisionRenderer from '../Page/RevisionRenderer';
 import Username from '../User/Username';
 
-import CommentControl from './CommentControl';
+import { CommentControl } from './CommentControl';
 import { CommentEditor } from './CommentEditor';
 
 type CommentProps = {

+ 8 - 12
packages/app/src/components/PageComment/CommentControl.jsx → packages/app/src/components/PageComment/CommentControl.tsx

@@ -1,25 +1,21 @@
 import React from 'react';
 
-import PropTypes from 'prop-types';
+type CommentControlProps = {
+  onClickEditBtn: () => void,
+  onClickDeleteBtn: () => void,
+}
 
+export const CommentControl = (props: CommentControlProps): JSX.Element => {
+  const { onClickEditBtn, onClickDeleteBtn } = props;
 
-const CommentControl = (props) => {
   return (
     <div className="page-comment-control">
-      <button type="button" className="btn btn-link p-2" onClick={props.onClickEditBtn}>
+      <button type="button" className="btn btn-link p-2" onClick={onClickEditBtn}>
         <i className="ti ti-pencil"></i>
       </button>
-      <button type="button" className="btn btn-link p-2 mr-2" onClick={props.onClickDeleteBtn}>
+      <button type="button" className="btn btn-link p-2 mr-2" onClick={onClickDeleteBtn}>
         <i className="ti ti-close"></i>
       </button>
     </div>
   );
 };
-
-CommentControl.propTypes = {
-
-  onClickEditBtn: PropTypes.func.isRequired,
-  onClickDeleteBtn: PropTypes.func.isRequired,
-};
-
-export default CommentControl;

+ 1 - 12
packages/app/src/components/PrivateLegacyPages.tsx

@@ -8,7 +8,6 @@ import {
 } from 'reactstrap';
 
 import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
@@ -190,21 +189,12 @@ ConvertByPathModal.displayName = 'ConvertByPathModal';
  * LegacyPage
  */
 
-type Props = {
-  appContainer: AppContainer,
-}
-
-const PrivateLegacyPages = (props: Props): JSX.Element => {
+const PrivateLegacyPages = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: currentUser } = useCurrentUser();
 
   const isAdmin = currentUser?.admin;
 
-  const {
-    appContainer,
-  } = props;
-
-
   const [keyword, setKeyword] = useState<string>(initQ);
   const [offset, setOffset] = useState<number>(0);
   const [limit, setLimit] = useState<number>(INITIAL_PAGING_SIZE);
@@ -442,7 +432,6 @@ const PrivateLegacyPages = (props: Props): JSX.Element => {
     <>
       <SearchPageBase
         ref={searchPageBaseRef}
-        appContainer={appContainer}
         pages={data?.data}
         onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
         forceHideMenuItems={[MenuItemType.BOOKMARK, MenuItemType.RENAME, MenuItemType.DUPLICATE, MenuItemType.REVERT, MenuItemType.PATH_RECOVERY]}

+ 22 - 0
packages/app/src/components/ReactMarkdownComponents/CodeBlock.module.scss

@@ -0,0 +1,22 @@
+@use '~/styles/variables' as var;
+@use '~/styles/bootstrap/init' as bs;
+
+.code-inline {
+  padding: 2px 4px;
+  font-family: var.$font-family-monospace-not-strictly;
+  border: 1px solid;
+  border-radius: bs.$border-radius;
+}
+
+.code-highlighted-title {
+  position: absolute;
+  top: 0;
+  right: 0.5em;
+  padding: 0 4px;
+  font-style: normal;
+  font-weight: bold;
+  color: bs.$gray-900;
+  background: bs.$gray-300;
+  border-radius: bs.$border-radius;
+  opacity: 0.6;
+}

+ 33 - 0
packages/app/src/components/ReactMarkdownComponents/CodeBlock.tsx

@@ -0,0 +1,33 @@
+import { CodeComponent } from 'react-markdown/lib/ast-to-react';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
+
+import styles from './CodeBlock.module.scss';
+
+export const CodeBlock: CodeComponent = ({ inline, className, children }) => {
+  if (inline) {
+    return <code className={`code-inline ${styles['code-inline']} ${className ?? ''}`}>{children}</code>;
+  }
+
+  // TODO: set border according to the value of 'customize:highlightJsStyleBorder'
+
+  const match = /language-(\w+)(:?.+)?/.exec(className || '');
+  const lang = match && match[1] ? match[1] : '';
+  const name = match && match[2] ? match[2].slice(1) : null;
+
+  return (
+    <>
+      {name != null && (
+        <cite className={`code-highlighted-title ${styles['code-highlighted-title']}`}>{name}</cite>
+      )}
+      <SyntaxHighlighter
+        className="code-highlighted"
+        PreTag="div"
+        style={oneLight}
+        language={lang}
+      >
+        {String(children).replace(/\n$/, '')}
+      </SyntaxHighlighter>
+    </>
+  );
+};

+ 5 - 23
packages/app/src/components/SearchPage.tsx

@@ -2,13 +2,12 @@ import React, {
   useCallback, useEffect, useMemo, useRef, useState,
 } from 'react';
 
-import { parse as parseQuerystring } from 'querystring';
 
 import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
 
 
 import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import AppContainer from '~/client/services/AppContainer';
 import { IFormattedSearchResult } from '~/interfaces/search';
 import { useIsSearchServiceReachable } from '~/stores/context';
 import { ISearchConditions, ISearchConfigurations, useSWRxSearch } from '~/stores/search';
@@ -88,29 +87,13 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
 SearchResultListHead.displayName = 'SearchResultListHead';
 
 
-/**
- * SearchPage
- */
-
-const getParsedUrlQuery = () => {
-  const search = window.location.search || '?';
-  return parseQuerystring(search.slice(1)); // remove heading '?' and parse
-};
-
-type Props = {
-  appContainer: AppContainer,
-}
-
-export const SearchPage = (props: Props): JSX.Element => {
+export const SearchPage = (): JSX.Element => {
   const { t } = useTranslation();
-
-  const {
-    appContainer,
-  } = props;
+  const router = useRouter();
 
   // parse URL Query
-  const parsedQueries = getParsedUrlQuery().q;
-  const initQ = (Array.isArray(parsedQueries) ? parsedQueries.join(' ') : parsedQueries) ?? '';
+  const queries = router.query.q;
+  const initQ = (Array.isArray(queries) ? queries.join(' ') : queries) ?? '';
 
   const [keyword, setKeyword] = useState<string>(initQ);
   const [offset, setOffset] = useState<number>(0);
@@ -272,7 +255,6 @@ export const SearchPage = (props: Props): JSX.Element => {
   return (
     <SearchPageBase
       ref={searchPageBaseRef}
-      appContainer={appContainer}
       pages={data?.data}
       searchingKeyword={keyword}
       onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}

+ 10 - 9
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -3,6 +3,7 @@ import React, {
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
 import { DropdownItem } from 'reactstrap';
 
 import { exportAsMarkdown } from '~/client/services/page-operation';
@@ -19,13 +20,9 @@ import { useSearchResultOptions } from '~/stores/renderer';
 import { useFullTextSearchTermManager } from '~/stores/search';
 
 
-import AppContainer from '../../client/services/AppContainer';
-import { AdditionalMenuItemsRendererProps, ForceHideMenuItems, MenuItemType } from '../Common/Dropdown/PageItemControl';
-import { GrowiSubNavigation } from '../Navbar/GrowiSubNavigation';
-import { SubNavButtons } from '../Navbar/SubNavButtons';
-import RevisionLoader from '../Page/RevisionLoader';
-import { PageComment } from '../PageComment';
-import { PageContentFooter } from '../PageContentFooter';
+import { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
+import { GrowiSubNavigationProps } from '../Navbar/GrowiSubNavigation';
+import { SubNavButtonsProps } from '../Navbar/SubNavButtons';
 
 
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
@@ -54,7 +51,6 @@ const SCROLL_OFFSET_TOP = 175; // approximate height of (navigation + subnavigat
 const MUTATION_OBSERVER_CONFIG = { childList: true, subtree: true };
 
 type Props ={
-  appContainer: AppContainer,
   pageWithMeta : IPageWithSearchMeta,
   highlightKeywords?: string[],
   showPageControlDropdown?: boolean,
@@ -81,6 +77,12 @@ const generateObserverCallback = (doScroll: ()=>void) => {
 };
 
 export const SearchResultContent: FC<Props> = (props: Props) => {
+  const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
+  const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
+  const RevisionLoader = dynamic(() => import('../Page/RevisionLoader'), { ssr: false });
+  const PageComment = dynamic(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
+  const PageContentFooter = dynamic(() => import('../PageContentFooter'), { ssr: false });
+
   const scrollElementRef = useRef(null);
 
   // for mutation
@@ -106,7 +108,6 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   // *******************************  end  *******************************
 
   const {
-    appContainer,
     pageWithMeta,
     highlightKeywords,
     showPageControlDropdown,

+ 2 - 6
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -3,9 +3,9 @@ import React, {
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
 
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { IFormattedSearchResult, IPageWithSearchMeta } from '~/interfaces/search';
 import { OnDeletedFunction } from '~/interfaces/ui';
@@ -14,7 +14,6 @@ import { usePageDeleteModal } from '~/stores/modal';
 import { usePageTreeTermManager } from '~/stores/page-listing';
 
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
-import { SearchResultContent } from '../SearchPage/SearchResultContent';
 import { SearchResultList } from '../SearchPage/SearchResultList';
 
 
@@ -28,8 +27,6 @@ export interface IReturnSelectedPageIds {
 
 
 type Props = {
-  appContainer: AppContainer,
-
   pages?: IPageWithSearchMeta[],
   searchingKeyword?: string,
 
@@ -43,8 +40,8 @@ type Props = {
 }
 
 const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturnSelectedPageIds, Props> = (props:Props, ref) => {
+  const SearchResultContent = dynamic(import('../SearchPage/SearchResultContent').then(mod => mod.SearchResultContent), { ssr: false });
   const {
-    appContainer,
     pages,
     searchingKeyword,
     forceHideMenuItems,
@@ -203,7 +200,6 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
         <div className="mw-0 flex-grow-1 flex-basis-0 d-none d-lg-block search-result-content">
           { selectedPageWithMeta != null && (
             <SearchResultContent
-              appContainer={appContainer}
               pageWithMeta={selectedPageWithMeta}
               highlightKeywords={highlightKeywords}
               showPageControlDropdown={!isGuestUser}

+ 5 - 0
packages/app/src/interfaces/theme.ts

@@ -16,3 +16,8 @@ export const GrowiThemes = {
   WOOD: 'wood',
 } as const;
 export type GrowiThemes = typeof GrowiThemes[keyof typeof GrowiThemes];
+
+export const PrismThemes = {
+  OneLight: 'one-light',
+} as const;
+export type PrismThemes = typeof PrismThemes[keyof typeof PrismThemes];

+ 6 - 8
packages/app/src/pages/[[...path]].page.tsx

@@ -7,7 +7,7 @@ import {
   IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision, isClient, isIPageInfoForEntity, isServer, IUser, IUserHasId, pagePathUtils, pathUtils,
 } from '@growi/core';
 import ExtensibleCustomError from 'extensible-custom-error';
-import mongoose from 'mongoose';
+import { model as mongooseModel } from 'mongoose';
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
@@ -33,8 +33,7 @@ import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { IUserUISettings } from '~/interfaces/user-ui-settings';
 import { PageModel, PageDocument } from '~/server/models/page';
 import { PageRedirectModel } from '~/server/models/page-redirect';
-import UserUISettings from '~/server/models/user-ui-settings';
-import Xss from '~/services/xss';
+import { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import {
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth, useSelectedGrant,
@@ -47,10 +46,8 @@ import loggerFactory from '~/utils/logger';
 // import GrowiSubNavigation from '../client/js/components/Navbar/GrowiSubNavigation';
 // import GrowiSubNavigationSwitcher from '../client/js/components/Navbar/GrowiSubNavigationSwitcher';
 import { DescendantsPageListModal } from '../components/DescendantsPageListModal';
-import ForbiddenPage from '../components/ForbiddenPage';
 import { BasicLayout } from '../components/Layout/BasicLayout';
 import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSubNavigation';
-import { NotCreatablePage } from '../components/NotCreatablePage';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 // import { serializeUserSecurely } from '../server/models/serializers/user-serializer';
 // import PageStatusAlert from '../client/js/components/PageStatusAlert';
@@ -66,7 +63,6 @@ import {
   useIsSlackConfigured, useIsBlinkedHeaderAtBoot, useRendererConfig, useEditingMarkdown,
   useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage,
 } from '../stores/context';
-import { useXss } from '../stores/xss';
 
 import {
   CommonProps, getNextI18NextConfig, getServerSideCommonProps, useCustomTitle,
@@ -171,6 +167,8 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // const { t } = useTranslation();
   const router = useRouter();
 
+  const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
+  const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
   const UnsavedAlertDialog = dynamic(() => import('./UnsavedAlertDialog'), { ssr: false });
   const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
 
@@ -182,7 +180,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   }
 
   // commons
-  useXss(new Xss());
   useEditorConfig(props.editorConfig);
   useCsrfToken(props.csrfToken);
 
@@ -368,7 +365,7 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
   const { revisionId } = req.query;
 
   const Page = crowi.model('Page') as PageModel;
-  const PageRedirect = mongoose.model('PageRedirect') as PageRedirectModel;
+  const PageRedirect = mongooseModel('PageRedirect') as PageRedirectModel;
   const { pageService } = crowi;
 
   let currentPathname = props.currentPathname;
@@ -412,6 +409,7 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
 async function injectUserUISettings(context: GetServerSidePropsContext, props: Props): Promise<void> {
   const req = context.req as CrowiRequest<IUserHasId & any>;
   const { user } = req;
+  const UserUISettings = mongooseModel('UserUISettings') as UserUISettingsModel;
 
   const userUISettings = user == null ? null : await UserUISettings.findOne({ user: user._id }).exec();
   if (userUISettings != null) {

+ 5 - 21
packages/app/src/pages/_document.page.tsx

@@ -1,45 +1,29 @@
 import React from 'react';
 
-import fs from 'fs';
-
 import Document, {
   DocumentContext, DocumentInitialProps,
   Html, Head, Main, NextScript,
 } from 'next/document';
 
-// import { renderScriptTagsByGroup, renderStyleTagsByGroup } from '~/service/cdn-resources-loader';
-import { resolveFromRoot } from '~/utils/project-dir-utils';
 
-interface GrowiDocumentProps {
-  bootJsPath: string;
-}
-declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
+// type GrowiDocumentProps = {};
+// declare type GrowiDocumentInitialProps = GrowiDocumentProps & DocumentInitialProps;
+declare type GrowiDocumentInitialProps = DocumentInitialProps;
 
-async function importCustomManifest(): Promise<any> {
-  const customManifestStr: string = await fs.readFileSync(resolveFromRoot('.next/custom-manifest.json'), 'utf-8');
-  return JSON.parse(customManifestStr);
-}
 
-class GrowiDocument extends Document<GrowiDocumentProps> {
+class GrowiDocument extends Document {
 
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
 
-    const customManifest: any = await importCustomManifest();
-    const bootJsPath = customManifest['boot.js'];
-
-    return { ...initialProps, bootJsPath };
+    return initialProps;
   }
 
   override render(): JSX.Element {
 
-    const { bootJsPath } = this.props;
-
     return (
       <Html>
         <Head>
-          {/* eslint-disable-next-line @next/next/no-sync-scripts */}
-          <script src={bootJsPath}></script>
           {/*
           {renderScriptTagsByGroup('basis')}
           {renderStyleTagsByGroup('basis')}

+ 15 - 15
packages/app/src/pages/_search.page.tsx

@@ -21,7 +21,8 @@ import {
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed,
   useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
-import { useXss } from '~/stores/xss';
+
+import { SearchPage } from '../components/SearchPage';
 
 import {
   CommonProps, getNextI18NextConfig, getServerSideCommonProps, useCustomTitle,
@@ -44,11 +45,10 @@ type Props = CommonProps & {
 
 };
 
-const SearchPage: NextPage<Props> = (props: Props) => {
+const SearchResultPage: NextPage<Props> = (props: Props) => {
   const { userUISettings } = props;
 
   // commons
-  useXss(new Xss());
   useCsrfToken(props.csrfToken);
 
   useCurrentUser(props.currentUser ?? null);
@@ -87,20 +87,20 @@ const SearchPage: NextPage<Props> = (props: Props) => {
         {renderScriptTagByName('highlight-addons')}
         */}
       </Head>
-      <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
-
-        <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
-        <div id="main" className="main search-page mt-0">
+      <div className="on-search">
+        <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
 
-          <div id="search-page">
-            Search Result Page
-            {/* render SearchPage component here */}
-          </div>
+          <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
+          <div id="main" className="main search-page mt-0">
 
-        </div>
-        <PutbackPageModal />
-      </BasicLayout>
+            <div id="search-page">
+              <SearchPage />
+            </div>
 
+          </div>
+          <PutbackPageModal />
+        </BasicLayout>
+      </div>
     </>
   );
 };
@@ -184,4 +184,4 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   };
 };
 
-export default SearchPage;
+export default SearchResultPage;

+ 2 - 0
packages/app/src/services/renderer/renderer.tsx

@@ -9,6 +9,7 @@ import emoji from 'remark-emoji';
 import gfm from 'remark-gfm';
 import math from 'remark-math';
 
+import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import { RendererConfig } from '~/interfaces/services/renderer';
@@ -234,6 +235,7 @@ const generateCommonOptions: ReactMarkdownOptionsGenerator = (config: RendererCo
     ],
     components: {
       a: NextLink,
+      code: CodeBlock,
     },
   };
 };

+ 8 - 1
packages/app/src/stores/page.tsx

@@ -81,11 +81,18 @@ export const useSWRxPageInfo = (
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   const fixedShareLinkId = shareLinkId ?? null;
 
-  return useSWRImmutable<IPageInfo | IPageInfoForOperation, Error>(
+  const swrResult = useSWRImmutable<IPageInfo | IPageInfoForOperation, Error>(
     pageId != null ? ['/page/info', pageId, fixedShareLinkId] : null,
     (endpoint, pageId, shareLinkId) => apiv3Get(endpoint, { pageId, shareLinkId }).then(response => response.data),
     { fallbackData: initialData },
   );
+
+  // use mutate because fallbackData does not work
+  if (initialData != null) {
+    swrResult.mutate(initialData);
+  }
+
+  return swrResult;
 };
 
 export const useSWRxPageRevisions = (

+ 1 - 1
packages/app/src/styles/_search.scss

@@ -124,7 +124,7 @@
 
 
 // style to apply when displaying search page
-.growi.on-search {
+.on-search {
   // set sidebar height shown in search page
   $search-page-sidebar-height: calc(100vh - ($grw-navbar-height + $grw-navbar-border-width));
 

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

@@ -113,6 +113,10 @@
     }
   }
 
+  pre {
+    position: relative; // for absolute positioned .code-highlighted-title
+  }
+
   .task-list {
     .task-list-item {
       margin: 0 0.2em 0.25em -1.6em;

+ 0 - 11
packages/app/src/styles/atoms/_code.scss

@@ -1,11 +0,0 @@
-/*
- * style of inline-code
- */
-:not(pre) {
-  > code {
-    padding: 2px 4px;
-    font-family: $font-family-monospace-not-strictly;
-    border: 1px solid;
-    border-radius: $border-radius;
-  }
-}

+ 1 - 1
packages/app/src/styles/style-next.scss

@@ -63,7 +63,7 @@
 // @import 'page-presentation';
 // @import 'page-history';
 // @import 'recent-changes';
-// @import 'search';
+@import 'search';
 // @import 'shortcuts';
 // @import 'sidebar';
 // @import 'sidebar-wiki';

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

@@ -48,16 +48,13 @@ $nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
 // determine variables with bootstrap function (These variables can be used after importing bootstrap above)
 $color-modal-header: color-yiq($primary) !default;
 
-:not(pre) {
-  > code {
-    color: $color-inline-code;
-    background-color: $bgcolor-inline-code;
-    border-color: $bordercolor-inline-code;
-  }
+.code-inline {
+  color: $color-inline-code;
+  background-color: $bgcolor-inline-code;
+  border-color: $bordercolor-inline-code;
 }
 
-pre:not(.hljs):not(.CodeMirror-line) {
-  background-color: $bgcolor-inline-code;
+.code-highlighted {
   border-color: $bordercolor-inline-code;
 }
 

+ 62 - 27
yarn.lock

@@ -1689,6 +1689,13 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.3.1":
+  version "7.18.9"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
+  integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.6.3":
   version "7.7.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf"
@@ -10996,6 +11003,17 @@ hastscript@^5.0.0:
     property-information "^5.0.0"
     space-separated-tokens "^1.0.0"
 
+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"
+
 hastscript@^7.0.0:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.0.2.tgz#d811fc040817d91923448a28156463b2e40d590a"
@@ -11025,7 +11043,7 @@ highlight.js@11.2.0:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.2.0.tgz#a7e3b8c1fdc4f0538b93b2dc2ddd53a40c6ab0f0"
   integrity sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==
 
-highlight.js@^10.7.1:
+highlight.js@^10.4.1, highlight.js@^10.7.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==
@@ -13607,6 +13625,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.1.x:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
@@ -16961,6 +16987,21 @@ printj@~1.1.0:
   resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
   integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
 
+prism-themes@^1.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/prism-themes/-/prism-themes-1.9.0.tgz#19c034f3205f1e28d75d89728e54ccd745f7e3dd"
+  integrity sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw==
+
+prismjs@^1.27.0:
+  version "1.28.0"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6"
+  integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==
+
+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==
+
 process-nextick-args@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@@ -17583,6 +17624,17 @@ react-scrolllock@^1.0.9:
     create-react-class "^15.5.2"
     prop-types "^15.5.10"
 
+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-transition-group@^2.2.1, react-transition-group@^2.3.1:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@@ -17937,6 +17989,15 @@ reflect-metadata@^0.1.13:
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
   integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
 
+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"
+
 reftools@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.0.8.tgz#ec26941f780044420c1d1bb48836112f199e520b"
@@ -19501,11 +19562,6 @@ sort-keys@^4.0.0:
   dependencies:
     is-plain-obj "^2.0.0"
 
-source-list-map@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
-  integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-
 "source-map-js@>=0.6.2 <2.0.0":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -20305,11 +20361,6 @@ table@^6.7.5:
     string-width "^4.2.3"
     strip-ansi "^6.0.1"
 
-tapable@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
-  integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
-
 tapable@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
@@ -22029,22 +22080,6 @@ webpack-bundle-analyzer@4.3.0:
     sirv "^1.0.7"
     ws "^7.3.1"
 
-webpack-manifest-plugin@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz#084246c1f295d1b3222d36e955546433ca8df803"
-  integrity sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==
-  dependencies:
-    tapable "^2.0.0"
-    webpack-sources "^2.2.0"
-
-webpack-sources@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd"
-  integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==
-  dependencies:
-    source-list-map "^2.0.1"
-    source-map "^0.6.1"
-
 whatwg-encoding@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"