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

Find and transform code tags and backticks from html nodes to InlineCode nodes

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

+ 3 - 0
apps/app/src/features/openai/server/utils/convert-markdown-to-html.ts

@@ -9,6 +9,8 @@ import type * as RemarkRehype from 'remark-rehype';
 import type * as Unified from 'unified';
 import type * as UnistUtilVisit from 'unist-util-visit';
 
+import { remarkPlugin as inlineCodeRemarkPlugin } from 'src/services/renderer/remark-plugins/codeblock';
+
 interface ModuleCache {
   unified?: typeof Unified.unified;
   visit?: typeof UnistUtilVisit.visit;
@@ -88,6 +90,7 @@ export const convertMarkdownToHtml = async(revisionBody: string, args: ConvertMa
 
   const processor = unified()
     .use(remarkParse)
+    .use(inlineCodeRemarkPlugin)
     .use(sanitizeMarkdown)
     .use(remarkRehype)
     .use(rehypeMeta, {

+ 72 - 5
apps/app/src/services/renderer/remark-plugins/codeblock.ts

@@ -1,17 +1,84 @@
+import type { Properties } from 'hast';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-import type { InlineCode } from 'mdast';
+import type { InlineCode as MdastInlineCode, Html, Text } from 'mdast';
 import type { Plugin } from 'unified';
-import { visit } from 'unist-util-visit';
+import type { Node, Parent, Point } from 'unist';
+import type { visit as unistUtilVisit } from 'unist-util-visit';
 
 
+type InlineCode = MdastInlineCode & {
+  data?: {
+    hProperties?: Properties;
+    [key: string]: unknown;
+  };
+};
+
+let visit: typeof unistUtilVisit;
+(async() => {
+  const { visit: loadedVisit } = await import('unist-util-visit');
+  visit = loadedVisit;
+})();
+
 const SUPPORTED_CODE = ['inline'];
 
+/**
+ * Remark plugin to unify inline code handling.
+ *
+ * This plugin converts HTML `<code>...</code>` sequences in the Markdown AST
+ * into standard `inlineCode` nodes. It also ensures all `inlineCode` nodes
+ * (both converted and native Markdown backtick code) receive a `data.hProperties.inline: 'true'`
+ * property, crucial for `react-markdown` to pass the `inline` prop to custom code components.
+ *
+ * @returns {Plugin} A unified plugin.
+ */
+
 export const remarkPlugin: Plugin = () => {
-  return (tree) => {
+  return (tree: Node) => {
+    if (!visit) {
+      return tree;
+    }
+
+    const defaultPoint: Point = { line: 1, column: 1, offset: 0 };
+
+    visit(tree, 'html', (node: Html, index: number | undefined, parent: Parent | undefined) => {
+      // Find <code> tag
+      if (typeof node.value === 'string' && node.value.toLowerCase() === '<code>') {
+        if (parent && parent.children && index !== undefined) {
+          const contentNode = parent.children[index + 1] as Text | undefined;
+          const closingTagNode = parent.children[index + 2] as Html | undefined;
+
+          // Find closing tag
+          if (contentNode && contentNode.type === 'text'
+              && closingTagNode && closingTagNode.type === 'html'
+              && typeof closingTagNode.value === 'string' && closingTagNode.value.toLowerCase() === '</code>') {
+
+            // Create InlineCode node
+            const newInlineCodeNode: InlineCode = {
+              type: 'inlineCode',
+              value: contentNode.value,
+              position: {
+                start: contentNode.position?.start || node.position?.start || defaultPoint,
+                end: contentNode.position?.end || closingTagNode.position?.end || defaultPoint,
+              },
+              data: {
+                hProperties: { inline: 'true' },
+              },
+            };
+
+            parent.children.splice(index, 3, newInlineCodeNode);
+          }
+        }
+      }
+    }, true);
+
+    // Ensure all inlineCode nodes (including those from backticks) have the 'inline' property.
     visit(tree, 'inlineCode', (node: InlineCode) => {
-      const data = node.data || (node.data = {});
-      data.hProperties = { inline: 'true' }; // set 'true' explicitly because the empty string is evaluated as false for `if (inline) { ... }`
+      node.data = node.data || {};
+      node.data.hProperties = (node.data.hProperties || {}) as Properties;
+      node.data.hProperties.inline = 'true';
     });
+
+    return tree;
   };
 };