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

Add rehype plugin for converting code tags to inline

arvid-e 8 месяцев назад
Родитель
Сommit
4c69b43bf0

+ 3 - 1
apps/app/package.json

@@ -336,6 +336,8 @@
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.7.5",
     "source-map-loader": "^4.0.1",
-    "swagger2openapi": "^7.0.8"
+    "swagger2openapi": "^7.0.8",
+    "unist-util-is": "^6.0.0",
+    "unist-util-visit-parents": "^6.0.0"
   }
 }

+ 27 - 8
apps/app/src/components/ReactMarkdownComponents/CodeBlock.tsx

@@ -14,11 +14,11 @@ Object.entries<object>(oneDark).forEach(([key, value]) => {
 
 
 type InlineCodeBlockProps = {
- children: ReactNode,
- className?: string,
+  children: ReactNode,
+  className?: string,
 }
 
-export const InlineCodeBlockSubstance = (props: InlineCodeBlockProps): JSX.Element => {
+const InlineCodeBlockSubstance = (props: InlineCodeBlockProps): JSX.Element => {
   const { children, className, ...rest } = props;
   return <code className={`code-inline ${className ?? ''}`} {...rest}>{children}</code>;
 };
@@ -50,8 +50,22 @@ function extractChildrenToIgnoreReactNode(children: ReactNode): ReactNode {
 }
 
 function CodeBlockSubstance({ lang, children }: { lang: string, children: ReactNode }): JSX.Element {
-  // SIMPLIFIED: Now that this function is only for block-level code,
-  // the conditional check for isSimpleString is no longer necessary.
+  // return alternative element
+  //   in order to fix "CodeBlock string is be [object Object] if searched"
+  // see: https://github.com/weseek/growi/pull/7484
+  //
+  // Note: You can also remove this code if the user requests to see the code highlighted in Prism as-is.
+
+  const isSimpleString = typeof children === 'string' || (Array.isArray(children) && children.length === 1 && typeof children[0] === 'string');
+  if (!isSimpleString) {
+    return (
+      <div style={oneDark['pre[class*="language-"]']}>
+        <code className={`language-${lang}`} style={oneDark['code[class*="language-"]']}>
+          {children}
+        </code>
+      </div>
+    );
+  }
 
   return (
     <PrismAsyncLight
@@ -65,13 +79,18 @@ function CodeBlockSubstance({ lang, children }: { lang: string, children: ReactN
 }
 
 type CodeBlockProps = {
- children: ReactNode,
- className?: string,
+  children: ReactNode,
+  className?: string,
+  inline?: true,
 }
 
 export const CodeBlock = (props: CodeBlockProps): JSX.Element => {
+
   // TODO: set border according to the value of 'customize:highlightJsStyleBorder'
-  const { className, children } = props;
+  const { className, children, inline } = props;
+  if (inline) {
+    return <InlineCodeBlockSubstance className={`code-inline ${className ?? ''}`}>{children}</InlineCodeBlockSubstance>;
+  }
 
   const match = /language-(\w+)(:?.+)?/.exec(className || '');
   const lang = match && match[1] ? match[1] : '';

+ 33 - 0
apps/app/src/services/renderer/rehype-plugins/add-inline-code-property.ts

@@ -0,0 +1,33 @@
+import type { Element, Root } from 'hast';
+import type { Plugin } from 'unified';
+import { is } from 'unist-util-is';
+import { visitParents } from 'unist-util-visit-parents';
+
+const isInlineCodeTag = (node: Element, parent: Element | Root | null): boolean => {
+  if (node.tagName !== 'code') {
+    return false;
+  }
+
+  if (parent && is(parent, { type: 'element', tagName: 'pre' })) {
+    return false;
+  }
+
+  return true;
+};
+
+export const rehypePlugin: Plugin = () => {
+  return (tree) => {
+    visitParents(tree, 'element', (node: Element, ancestors) => {
+      // The immediate parent is the last item in the ancestors array
+      const parent = ancestors[ancestors.length - 1] || null;
+
+      // Check if the current element is an inline <code> tag
+      if (isInlineCodeTag(node, parent)) {
+        node.properties = {
+          ...node.properties,
+          inline: true,
+        };
+      }
+    });
+  };
+};

+ 4 - 4
apps/app/src/services/renderer/renderer.tsx

@@ -12,8 +12,7 @@ import math from 'remark-math';
 import deepmerge from 'ts-deepmerge';
 import type { Pluggable, PluginTuple } from 'unified';
 
-
-import { CodeBlock, InlineCodeBlockSubstance } from '~/components/ReactMarkdownComponents/CodeBlock';
+import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import type { RendererOptions } from '~/interfaces/renderer-options';
 import { RehypeSanitizeType } from '~/interfaces/services/rehype-sanitize';
@@ -22,6 +21,7 @@ import loggerFactory from '~/utils/logger';
 
 import { tagNames as recommendedTagNames, attributes as recommendedAttributes } from './recommended-whitelist';
 import * as addClass from './rehype-plugins/add-class';
+import * as addInlineProperty from './rehype-plugins/add-inline-code-property';
 import { relativeLinks } from './rehype-plugins/relative-links';
 import { relativeLinksByPukiwikiLikeLinker } from './rehype-plugins/relative-links-by-pukiwiki-like-linker';
 import * as codeBlock from './remark-plugins/codeblock';
@@ -113,11 +113,11 @@ export const generateCommonOptions = (pagePath: string|undefined): RendererOptio
       [addClass.rehypePlugin, {
         table: 'table table-bordered',
       }],
+      addInlineProperty.rehypePlugin,
     ],
     components: {
       a: NextLink,
-      pre: CodeBlock,
-      code: InlineCodeBlockSubstance,
+      code: CodeBlock,
     },
   };
 };

+ 10 - 0
pnpm-lock.yaml

@@ -1001,6 +1001,12 @@ importers:
       swagger2openapi:
         specifier: ^7.0.8
         version: 7.0.8(encoding@0.1.13)
+      unist-util-is:
+        specifier: ^6.0.0
+        version: 6.0.0
+      unist-util-visit-parents:
+        specifier: ^6.0.0
+        version: 6.0.1
 
   apps/pdf-converter:
     dependencies:
@@ -3114,6 +3120,7 @@ packages:
 
   '@handsontable/react@2.1.0':
     resolution: {integrity: sha512-Du73MFU2y1Bfe9m7mvxY70lB2R/VigFSpOwWZjDnUt/HwNPbNr+UQcY40w6u7acllQeee45H7jRdEExzsrvDKw==}
+    deprecated: Handsontable for React is now available as @handsontable/react-wrapper.
     peerDependencies:
       handsontable: '>=6.0.0'
 
@@ -3932,6 +3939,7 @@ packages:
   '@opentelemetry/instrumentation-redis-4@0.49.0':
     resolution: {integrity: sha512-i+Wsl7M2LXEDA2yXouNJ3fttSzzb5AhlehvSBVRIFuinY51XrrKSH66biO0eox+pYQMwAlPxJ778XcMQffN78A==}
     engines: {node: ^18.19.0 || >=20.6.0}
+    deprecated: Use "@opentelemetry/instrumentation-redis", which (as of v0.50.0) includes support for instrumenting redis v4.
     peerDependencies:
       '@opentelemetry/api': ^1.3.0
 
@@ -13526,6 +13534,7 @@ packages:
   superagent@10.2.1:
     resolution: {integrity: sha512-O+PCv11lgTNJUzy49teNAWLjBZfc+A1enOwTpLlH6/rsvKcTwcdTT8m9azGkVqM7HBl5jpyZ7KTPhHweokBcdg==}
     engines: {node: '>=14.18.0'}
+    deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net
 
   superjson@1.13.3:
     resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==}
@@ -13534,6 +13543,7 @@ packages:
   supertest@7.1.1:
     resolution: {integrity: sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==}
     engines: {node: '>=14.18.0'}
+    deprecated: Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net
 
   supports-color@10.0.0:
     resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}