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

Merge pull request #6277 from weseek/imprv/show-toc

Imprv/show toc
Yuki Takei 3 лет назад
Родитель
Сommit
800286395b

+ 9 - 9
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -62,7 +62,14 @@ const DisplaySwitcher = (): JSX.Element => {
     <>
       <TabContent activeTab={editorMode}>
         <TabPane tabId={EditorMode.View}>
-          <div className="d-flex flex-column flex-lg-row-reverse">
+          <div className="d-flex flex-column flex-lg-row">
+
+            <div className="flex-grow-1 flex-basis-0 mw-0">
+              { isUserPage && <UserInfo pageUser={pageUser} />}
+              { !isNotFound && <Page /> }
+              { isNotFound && !isNotCreatable && <NotFoundPage /> }
+              { isNotFound && isNotCreatable && <NotCreatablePage /> }
+            </div>
 
             { !isNotFound && !currentPage?.isEmpty && (
               <div className="grw-side-contents-container">
@@ -104,7 +111,7 @@ const DisplaySwitcher = (): JSX.Element => {
 
                   <div className="d-none d-lg-block">
                     <div id="revision-toc" className="revision-toc">
-                      {/* <TableOfContents /> */}
+                      <TableOfContents />
                     </div>
                     <ContentLinkButtons />
                   </div>
@@ -113,13 +120,6 @@ const DisplaySwitcher = (): JSX.Element => {
               </div>
             ) }
 
-            <div className="flex-grow-1 flex-basis-0 mw-0">
-              { isUserPage && <UserInfo pageUser={pageUser} />}
-              { !isNotFound && <Page /> }
-              { isNotFound && !isNotCreatable && <NotFoundPage /> }
-              { isNotFound && isNotCreatable && <NotCreatablePage /> }
-            </div>
-
           </div>
         </TabPane>
         { isEditable && (

+ 13 - 17
packages/app/src/components/TableOfContents.tsx

@@ -1,8 +1,11 @@
 import React, { useCallback, useEffect, useState } from 'react';
 
+import ReactMarkdown from 'react-markdown';
+
 import { blinkElem } from '~/client/util/blink-section-header';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
 import { useIsUserPage } from '~/stores/context';
+import { useTocOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
 
@@ -17,6 +20,8 @@ const TableOfContents = (): JSX.Element => {
 
   const [tocHtml, setTocHtml] = useState('');
 
+  const { data: rendererOptions } = useTocOptions();
+
   const calcViewHeight = useCallback(() => {
     // calculate absolute top of '#revision-toc' element
     const parentElem = document.querySelector('.grw-side-contents-container');
@@ -64,23 +69,14 @@ const TableOfContents = (): JSX.Element => {
       stickyElemSelector=".grw-side-contents-sticky-container"
       calcViewHeight={calcViewHeight}
     >
-      { tocHtml !== ''
-        ? (
-          <div
-            id="revision-toc-content"
-            className="revision-toc-content mb-3"
-            // eslint-disable-next-line react/no-danger
-            dangerouslySetInnerHTML={{ __html: tocHtml }}
-          />
-        )
-        : (
-          <div
-            id="revision-toc-content"
-            className="revision-toc-content mb-2"
-          >
-          </div>
-        ) }
-
+      <div
+        id="revision-toc-content"
+        className="revision-toc-content mb-3"
+      >
+        <ReactMarkdown {...rendererOptions}>
+          {''}
+        </ReactMarkdown>
+      </div>
     </StickyStretchableScroller>
   );
 

+ 1 - 1
packages/app/src/server/routes/apiv3/users.js

@@ -648,7 +648,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: data of delete user
    */
-  router.delete('/:id/remove', loginRequiredStrictly, adminRequired,  addActivity, async(req, res) => {
+  router.delete('/:id/remove', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
     const { id } = req.params;
 
     try {

+ 36 - 7
packages/app/src/services/renderer/renderer.tsx

@@ -1,6 +1,6 @@
 import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 import slug from 'rehype-slug';
-// import toc, { HtmlElementNode } from 'rehype-toc';
+import toc, { HtmlElementNode } from 'rehype-toc';
 import breaks from 'remark-breaks';
 import emoji from 'remark-emoji';
 import footnotes from 'remark-footnotes';
@@ -221,7 +221,10 @@ const generateCommonOptions: ReactMarkdownOptionsGenerator = (config: RendererCo
   };
 };
 
-export const generateViewOptions: ReactMarkdownOptionsGenerator = (config: RendererConfig): RendererOptions => {
+export const generateViewOptions = (
+    config: RendererConfig,
+    storeTocNode: (node: HtmlElementNode) => void,
+): RendererOptions => {
 
   const options = generateCommonOptions(config);
 
@@ -236,11 +239,13 @@ export const generateViewOptions: ReactMarkdownOptionsGenerator = (config: Rende
     }
   }
 
-  // add rehypePlugins
-  // rehypePlugins.push([toc, {
-  //   headings: ['h1', 'h2', 'h3'],
-  //   customizeTOC: storeTocNode,
-  // }]);
+  // store toc node
+  if (rehypePlugins != null) {
+    rehypePlugins.push([toc, {
+      headings: ['h1', 'h2', 'h3'],
+      customizeTOC: storeTocNode,
+    }]);
+  }
   // renderer.rehypePlugins.push([autoLinkHeadings, {
   //   behavior: 'append',
   // }]);
@@ -267,6 +272,30 @@ export const generateViewOptions: ReactMarkdownOptionsGenerator = (config: Rende
   return options;
 };
 
+export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
+
+  const options = generateCommonOptions(config);
+
+  const { remarkPlugins, rehypePlugins } = options;
+
+  // add remark plugins
+  if (remarkPlugins != null) {
+    remarkPlugins.push(emoji);
+  }
+  // set toc node
+  if (rehypePlugins != null) {
+    rehypePlugins.push([toc, {
+      headings: ['h1', 'h2', 'h3'],
+      customizeTOC: () => tocNode,
+    }]);
+  }
+  // renderer.rehypePlugins.push([autoLinkHeadings, {
+  //   behavior: 'append',
+  // }]);
+
+  return options;
+};
+
 export const generatePreviewOptions: ReactMarkdownOptionsGenerator = (config: RendererConfig): RendererOptions => {
   const options = generateCommonOptions(config);
 

+ 5 - 0
packages/app/src/stores/context.tsx

@@ -1,5 +1,6 @@
 import EventEmitter from 'events';
 
+import { HtmlElementNode } from 'rehype-toc';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -218,6 +219,10 @@ export const useRendererConfig = (initialData?: RendererConfig): SWRResponse<Ren
   return useStaticSWR('growiRendererConfig', initialData);
 };
 
+export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
+  return useStaticSWR('currentPageTocNode');
+};
+
 export const useIsBlinkedHeaderAtBoot = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useStaticSWR('isBlinkedAtBoot', initialData);
 };

+ 18 - 4
packages/app/src/stores/renderer.tsx

@@ -3,13 +3,17 @@ import useSWRImmutable from 'swr/immutable';
 
 import {
   ReactMarkdownOptionsGenerator, RendererOptions,
-  generateViewOptions, generatePreviewOptions, generateCommentPreviewOptions, generateOthersOptions,
+  generatePreviewOptions, generateCommentPreviewOptions, generateOthersOptions,
+  generateViewOptions, generateTocOptions,
 } from '~/services/renderer/renderer';
 
-import { useRendererConfig } from './context';
+
+import { useCurrentPageTocNode, useRendererConfig } from './context';
 
 // The base hook with common processes
-const _useOptionsBase = (rendererId: string, generator: ReactMarkdownOptionsGenerator): SWRResponse<RendererOptions, Error> => {
+const _useOptionsBase = (
+    rendererId: string, generator: ReactMarkdownOptionsGenerator,
+): SWRResponse<RendererOptions, Error> => {
   const { data: rendererConfig } = useRendererConfig();
 
   const isAllDataValid = rendererConfig != null;
@@ -31,7 +35,17 @@ const _useOptionsBase = (rendererId: string, generator: ReactMarkdownOptionsGene
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
   const key = 'viewOptions';
 
-  return _useOptionsBase(key, generateViewOptions);
+  const { mutate: storeTocNode } = useCurrentPageTocNode();
+
+  return _useOptionsBase(key, config => generateViewOptions(config, storeTocNode));
+};
+
+export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
+  const key = 'tocOptions';
+
+  const { data: tocNode } = useCurrentPageTocNode();
+
+  return _useOptionsBase(key, config => generateTocOptions(config, tocNode));
 };
 
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {