瀏覽代碼

Merge pull request #6521 from weseek/feat/rehype-add-line-number-attribute

feat: Rehype add line number attribute
Yuki Takei 3 年之前
父節點
當前提交
07b3f06c5f

+ 3 - 2
packages/app/src/components/PageEditor.tsx

@@ -462,9 +462,10 @@ const PageEditor = React.memo((props: Props): JSX.Element => {
       </div>
       <div className="d-none d-lg-block page-editor-preview-container flex-grow-1 flex-basis-0 mw-0">
         <Preview
-          markdown={markdown}
-          rendererOptions={rendererOptions}
           ref={previewRef}
+          rendererOptions={rendererOptions}
+          markdown={markdown}
+          pagePath={currentPagePath}
           renderMathJaxOnInit={false}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />

+ 5 - 6
packages/app/src/components/PageEditor/Preview.tsx

@@ -2,19 +2,16 @@ import React, {
   SyntheticEvent, RefObject,
 } from 'react';
 
-import ReactMarkdown from 'react-markdown';
-
-
 import { RendererOptions } from '~/services/renderer/renderer';
 import { useEditorSettings } from '~/stores/editor';
 
-import RevisionBody from '../Page/RevisionBody';
+import RevisionRenderer from '../Page/RevisionRenderer';
 
 
 type Props = {
   rendererOptions: RendererOptions,
   markdown?: string,
-  pagePath?: string,
+  pagePath?: string | null,
   renderMathJaxOnInit?: boolean,
   onScroll?: (scrollTop: number) => void,
 }
@@ -39,7 +36,9 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
         }
       }}
     >
-      <ReactMarkdown {...rendererOptions} className='wiki'>{markdown || ''}</ReactMarkdown>
+      { markdown != null && pagePath != null && (
+        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} pagePath={pagePath}></RevisionRenderer>
+      ) }
     </div>
   );
 

+ 17 - 0
packages/app/src/services/renderer/rehype-plugins/add-line-number-attribute.ts

@@ -0,0 +1,17 @@
+import { Element } from 'hast-util-select';
+import { Plugin } from 'unified';
+import { visit } from 'unist-util-visit';
+
+const REGEXP_TARGET_TAGNAMES = new RegExp(/h1|h2|h3|h4|h5|h6|p|img|pre|blockquote|hr|ol|ul/);
+
+export const addLineNumberAttribute: Plugin = () => {
+  return (tree) => {
+    visit(tree, 'element', (node: Element) => {
+      if (REGEXP_TARGET_TAGNAMES.test(node.tagName as string)) {
+        if (node.properties != null) {
+          node.properties['data-line'] = node.position?.start.line;
+        }
+      }
+    });
+  };
+};

+ 39 - 6
packages/app/src/services/renderer/renderer.tsx

@@ -28,6 +28,7 @@ import { RendererConfig } from '~/interfaces/services/renderer';
 import loggerFactory from '~/utils/logger';
 
 import { addClass } from './rehype-plugins/add-class';
+import { addLineNumberAttribute } from './rehype-plugins/add-line-number-attribute';
 import { relativeLinks } from './rehype-plugins/relative-links';
 import { relativeLinksByPukiwikiLikeLinker } from './rehype-plugins/relative-links-by-pukiwiki-like-linker';
 import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
@@ -286,7 +287,6 @@ const generateCommonOptions = (pagePath: string|undefined, config: RendererConfi
       growiPlugin,
     ],
     rehypePlugins: [
-      slug,
       [relativeLinksByPukiwikiLikeLinker, { pagePath }],
       [relativeLinks, { pagePath }],
       raw,
@@ -323,6 +323,7 @@ export const generateViewOptions = (
 
   // add rehype plugins
   rehypePlugins.push(
+    slug,
     katex,
     [toc, {
       nav: false,
@@ -409,10 +410,7 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
   return options;
 };
 
-export const generatePreviewOptions = (config: RendererConfig): RendererOptions => {
-  const options = generateCommonOptions(undefined, config);
-  const { rehypePlugins } = options;
-
+export const generatePreviewOptions = (pagePath: string, config: RendererConfig): RendererOptions => {
   // // Add configurers for preview
   // renderer.addConfigurers([
   //   new FooternoteConfigurer(),
@@ -423,11 +421,46 @@ export const generatePreviewOptions = (config: RendererConfig): RendererOptions
   // renderer.setMarkdownSettings({ breaks: rendererSettings?.isEnabledLinebreaks });
   // renderer.configure();
 
+  const options = generateCommonOptions(pagePath, config);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    emoji,
+    math,
+    lsxGrowiPlugin.remarkPlugin,
+  );
+  if (config.isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
+  }
+
   // add rehype plugins
   rehypePlugins.push(
-    [sanitize, commonSanitizeOption],
+    katex,
+    [lsxGrowiPlugin.rehypePlugin, { pagePath }],
+    addLineNumberAttribute,
+    // [autoLinkHeadings, {
+    //   behavior: 'append',
+    // }]
   );
 
+  const sanitizeOption = deepmerge(
+    commonSanitizeOption,
+    lsxGrowiPlugin.sanitizeOption,
+    {
+      attributes: {
+        '*': ['data-line'],
+      },
+    },
+  );
+  rehypePlugins.push([sanitize, sanitizeOption]);
+
+  // add components
+  if (components != null) {
+    components.lsx = props => <Lsx {...props} />;
+  }
+
   verifySanitizePlugin(options);
   return options;
 };

+ 15 - 2
packages/app/src/stores/renderer.tsx

@@ -77,9 +77,22 @@ export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const key = 'previewOptions';
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { data: rendererConfig } = useRendererConfig();
+
+  const isAllDataValid = currentPagePath != null && rendererConfig != null;
+
+  const key = isAllDataValid
+    ? ['previewOptions', currentPagePath, rendererConfig]
+    : null;
 
-  return _useOptionsBase(key, generatePreviewOptions);
+  return useSWRImmutable<RendererOptions, Error>(
+    key,
+    (rendererId, currentPagePath, rendererConfig) => generatePreviewOptions(currentPagePath, rendererConfig),
+    {
+      fallbackData: isAllDataValid ? generatePreviewOptions(currentPagePath, rendererConfig) : undefined,
+    },
+  );
 };
 
 export const useCommentPreviewOptions = (): SWRResponse<RendererOptions, Error> => {