فهرست منبع

Merge pull request #8995 from weseek/imprv/148445-15079-update-dependencies

imprv: Update dependencies due to unified package upgrade
Yuki Takei 1 سال پیش
والد
کامیت
7d0a6fac92
29فایلهای تغییر یافته به همراه241 افزوده شده و 197 حذف شده
  1. 1 1
      apps/app/src/client/components/Page/SlideRenderer.tsx
  2. 1 1
      apps/app/src/client/components/PagePresentationModal.tsx
  3. 3 4
      apps/app/src/client/components/ReactMarkdownComponents/Header.tsx
  4. 5 5
      apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx
  5. 10 5
      apps/app/src/client/services/renderer/renderer.tsx
  6. 10 5
      apps/app/src/components/ReactMarkdownComponents/CodeBlock.tsx
  7. 5 5
      apps/app/src/features/mermaid/services/mermaid.ts
  8. 2 5
      apps/app/src/interfaces/renderer-options.ts
  9. 2 3
      apps/app/src/services/renderer/rehype-plugins/add-class.ts
  10. 1 1
      apps/app/src/services/renderer/rehype-plugins/add-line-number-attribute.ts
  11. 2 2
      apps/app/src/services/renderer/rehype-plugins/relative-links-by-pukiwiki-like-linker.spec.ts
  12. 1 1
      apps/app/src/services/renderer/rehype-plugins/relative-links.spec.ts
  13. 1 1
      apps/app/src/services/renderer/rehype-plugins/relative-links.ts
  14. 8 10
      apps/app/src/services/renderer/rehype-plugins/relocate-toc.ts
  15. 11 11
      apps/app/src/services/renderer/remark-plugins/attachment.ts
  16. 23 0
      apps/app/src/services/renderer/remark-plugins/codeblock.ts
  17. 9 10
      apps/app/src/services/renderer/remark-plugins/xsv-to-table.ts
  18. 3 1
      apps/app/src/services/renderer/renderer.tsx
  19. 1 2
      apps/app/src/stores/renderer.tsx
  20. 11 7
      packages/presentation/src/client/components/RichSlideSection.tsx
  21. 1 1
      packages/presentation/src/client/consts/index.ts
  22. 1 1
      packages/presentation/src/client/services/renderer/extract-sections.ts
  23. 5 4
      packages/presentation/src/services/use-slides-by-frontmatter.ts
  24. 5 4
      packages/remark-attachment-refs/src/client/services/renderer/refs.ts
  25. 17 10
      packages/remark-drawio/src/services/renderer/remark-drawio.ts
  26. 50 45
      packages/remark-growi-directive/src/mdast-util-growi-directive/index.js
  27. 19 20
      packages/remark-growi-directive/src/remark-growi-directive.js
  28. 28 28
      packages/remark-growi-directive/test/mdast-util-growi-directive.test.js
  29. 5 4
      packages/remark-lsx/src/client/services/renderer/lsx.ts

+ 1 - 1
apps/app/src/client/components/Page/SlideRenderer.tsx

@@ -1,4 +1,4 @@
-import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import type { Options as ReactMarkdownOptions } from 'react-markdown';
 
 import { usePresentationViewOptions } from '~/stores/renderer';
 

+ 1 - 1
apps/app/src/client/components/PagePresentationModal.tsx

@@ -5,7 +5,7 @@ import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useFullScreen } from '@growi/ui/dist/utils';
 import dynamic from 'next/dynamic';
-import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import type { Options as ReactMarkdownOptions } from 'react-markdown';
 import {
   Modal, ModalBody,
 } from 'reactstrap';

+ 3 - 4
apps/app/src/client/components/ReactMarkdownComponents/Header.tsx

@@ -2,8 +2,8 @@ import { useCallback, useEffect, useState } from 'react';
 
 import type EventEmitter from 'events';
 
+import type { Element } from 'hast';
 import { useRouter } from 'next/router';
-import type { Element } from 'react-markdown/lib/rehype-filter';
 
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import {
@@ -54,13 +54,12 @@ const EditLink = (props: EditLinkProps): JSX.Element => {
 type HeaderProps = {
   children: React.ReactNode,
   node: Element,
-  level: number,
   id?: string,
 }
 
 export const Header = (props: HeaderProps): JSX.Element => {
   const {
-    node, id, children, level,
+    node, id, children,
   } = props;
 
   const { data: isGuestUser } = useIsGuestUser();
@@ -73,7 +72,7 @@ export const Header = (props: HeaderProps): JSX.Element => {
 
   const [isActive, setActive] = useState(false);
 
-  const CustomTag = `h${level}` as keyof JSX.IntrinsicElements;
+  const CustomTag = node.tagName as keyof JSX.IntrinsicElements;
 
   const activateByHash = useCallback((url: string) => {
     try {

+ 5 - 5
apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx

@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
 
 import type EventEmitter from 'events';
 
-import type { Element } from 'react-markdown/lib/rehype-filter';
+import type { Element } from 'hast';
 
 import {
   useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId,
@@ -23,8 +23,7 @@ type TableWithEditButtonProps = {
   className?: string
 }
 
-export const TableWithEditButton = React.memo((props: TableWithEditButtonProps): JSX.Element => {
-
+const TableWithEditButtonNoMemorized = (props: TableWithEditButtonProps): JSX.Element => {
   const { children, node, className } = props;
 
   const { data: isGuestUser } = useIsGuestUser();
@@ -61,5 +60,6 @@ export const TableWithEditButton = React.memo((props: TableWithEditButtonProps):
       </table>
     </div>
   );
-});
-TableWithEditButton.displayName = 'TableWithEditButton';
+};
+TableWithEditButtonNoMemorized.displayName = 'TableWithEditButton';
+export const TableWithEditButton = React.memo(TableWithEditButtonNoMemorized) as typeof TableWithEditButtonNoMemorized;

+ 10 - 5
apps/app/src/client/services/renderer/renderer.tsx

@@ -26,6 +26,7 @@ import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-
 import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
 import * as relocateToc from '~/services/renderer/rehype-plugins/relocate-toc';
 import * as attachment from '~/services/renderer/remark-plugins/attachment';
+import * as codeBlock from '~/services/renderer/remark-plugins/codeblock';
 import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
 import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
 import {
@@ -71,7 +72,7 @@ export const generateViewOptions = (
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       getCommonSanitizeOption(config),
       presentation.sanitizeOption,
@@ -80,6 +81,7 @@ export const generateViewOptions = (
       attachment.sanitizeOption,
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
+      codeBlock.sanitizeOption,
     )]
     : () => {};
 
@@ -129,9 +131,10 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
   // add remark plugins
   // remarkPlugins.push();
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       getCommonSanitizeOption(config),
+      codeBlock.sanitizeOption,
     )]
     : () => {};
 
@@ -176,7 +179,7 @@ export const generateSimpleViewOptions = (
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       getCommonSanitizeOption(config),
       presentation.sanitizeOption,
@@ -185,6 +188,7 @@ export const generateSimpleViewOptions = (
       attachment.sanitizeOption,
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
+      codeBlock.sanitizeOption,
     )]
     : () => {};
 
@@ -227,7 +231,7 @@ export const generatePresentationViewOptions = (
   const { rehypePlugins } = options;
 
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       addLineNumberAttribute.sanitizeOption,
     )]
@@ -265,7 +269,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       getCommonSanitizeOption(config),
       drawio.sanitizeOption,
@@ -274,6 +278,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
       addLineNumberAttribute.sanitizeOption,
+      codeBlock.sanitizeOption,
     )]
     : () => {};
 

+ 10 - 5
apps/app/src/components/ReactMarkdownComponents/CodeBlock.tsx

@@ -1,6 +1,5 @@
 import type { ReactNode } from 'react';
 
-import type { CodeComponent, CodeProps } from 'react-markdown/lib/ast-to-react';
 import { PrismAsyncLight } from 'react-syntax-highlighter';
 import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
 
@@ -67,13 +66,19 @@ function CodeBlockSubstance({ lang, children }: { lang: string, children: ReactN
   );
 }
 
-export const CodeBlock: CodeComponent = ({ inline, className, children }: CodeProps) => {
+type CodeBlockProps = {
+  children: JSX.Element,
+  className?: string,
+  inline?: string, // "" or undefined
+}
 
-  if (inline) {
-    return <code className={`code-inline ${className ?? ''}`}>{children}</code>;
-  }
+export const CodeBlock = (props: CodeBlockProps): JSX.Element => {
 
   // TODO: set border according to the value of 'customize:highlightJsStyleBorder'
+  const { className, children, inline } = props;
+  if (inline != null) {
+    return <code className={`code-inline ${className ?? ''}`}>{children}</code>;
+  }
 
   const match = /language-(\w+)(:?.+)?/.exec(className || '');
   const lang = match && match[1] ? match[1] : '';

+ 5 - 5
apps/app/src/features/mermaid/services/mermaid.ts

@@ -1,9 +1,9 @@
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-import { Plugin } from 'unified';
-import { Node } from 'unist';
+import type { Code } from 'mdast';
+import type { Plugin } from 'unified';
 import { visit } from 'unist-util-visit';
 
-function rewriteNode(node: Node) {
+function rewriteNode(node: Code) {
   // replace node
   const data = node.data ?? (node.data = {});
   data.hName = 'mermaid';
@@ -11,8 +11,8 @@ function rewriteNode(node: Node) {
 
 export const remarkPlugin: Plugin = function() {
   return (tree) => {
-    visit(tree, (node) => {
-      if (node.type === 'code' && node.lang === 'mermaid') {
+    visit(tree, 'code', (node: Code) => {
+      if (node.lang === 'mermaid') {
         rewriteNode(node);
       }
     });

+ 2 - 5
apps/app/src/interfaces/renderer-options.ts

@@ -1,8 +1,6 @@
 import type { ComponentType } from 'react';
 
-import type { SpecialComponents } from 'react-markdown/lib/ast-to-react';
-import type { NormalComponents } from 'react-markdown/lib/complex-types';
-import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import type { Options as ReactMarkdownOptions, Components } from 'react-markdown';
 import type { PluggableList } from 'unified';
 
 export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehypePlugins' | 'components' | 'children'> & {
@@ -10,8 +8,7 @@ export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehy
   rehypePlugins: PluggableList,
   components?:
     | Partial<
-        Omit<NormalComponents, keyof SpecialComponents>
-        & SpecialComponents
+        Components
         & {
           [elem: string]: ComponentType<any>,
         }

+ 2 - 3
apps/app/src/services/renderer/rehype-plugins/add-class.ts

@@ -1,9 +1,8 @@
 // See: https://github.com/martypdx/rehype-add-classes for the original implementation.
 // Re-implemeted in TypeScript.
+import type { Nodes as HastNode, Element, Properties } from 'hast';
 import { selectAll } from 'hast-util-select';
-import type { Node as HastNode, Element } from 'hast-util-select/lib/types';
-import { Properties } from 'hast-util-select/lib/types';
-import { Plugin } from 'unified';
+import type { Plugin } from 'unified';
 
 export type SelectorName = string; // e.g. 'h1'
 export type ClassName = string; // e.g. 'header'

+ 1 - 1
apps/app/src/services/renderer/rehype-plugins/add-line-number-attribute.ts

@@ -1,5 +1,5 @@
+import type { Element } from 'hast';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-import type { Element } from 'hast-util-select/lib/types';
 import type { Plugin } from 'unified';
 import { visit, EXIT, CONTINUE } from 'unist-util-visit';
 

+ 2 - 2
apps/app/src/services/renderer/rehype-plugins/relative-links-by-pukiwiki-like-linker.spec.ts

@@ -1,5 +1,5 @@
+import type { Nodes as HastNode, Text } from 'hast';
 import { select } from 'hast-util-select';
-import type { Node as HastNode } from 'hast-util-select/lib/types';
 import parse from 'remark-parse';
 import rehype from 'remark-rehype';
 import { unified } from 'unified';
@@ -45,7 +45,7 @@ describe('relativeLinksByPukiwikiLikeLinker', () => {
 
       expect(anchorElement?.children[0]).not.toBeNull();
       expect(anchorElement?.children[0].type).toEqual('text');
-      expect(anchorElement?.children[0].value).toEqual(expectedValue);
+      expect((anchorElement?.children[0] as HastNode as Text).value).toEqual(expectedValue);
 
     });
   });

+ 1 - 1
apps/app/src/services/renderer/rehype-plugins/relative-links.spec.ts

@@ -1,6 +1,6 @@
 
+import type { Nodes as HastNode } from 'hast';
 import { select } from 'hast-util-select';
-import type { Node as HastNode } from 'hast-util-select/lib/types';
 import parse from 'remark-parse';
 import remarkRehype from 'remark-rehype';
 import { unified } from 'unified';

+ 1 - 1
apps/app/src/services/renderer/rehype-plugins/relative-links.ts

@@ -1,7 +1,7 @@
 import assert from 'assert';
 
+import type { Nodes as HastNode, Element } from 'hast';
 import { selectAll } from 'hast-util-select';
-import type { Node as HastNode, Element } from 'hast-util-select/lib/types';
 import isAbsolute from 'is-absolute-url';
 import type { Plugin } from 'unified';
 

+ 8 - 10
apps/app/src/services/renderer/rehype-plugins/relocate-toc.ts

@@ -1,6 +1,7 @@
-import rehypeToc, { type HtmlElementNode } from 'rehype-toc';
+import rehypeToc from 'rehype-toc';
+import type { HtmlElementNode } from 'rehype-toc';
 import type { Plugin } from 'unified';
-import type { Node } from 'unist';
+import { visit } from 'unist-util-visit';
 
 type StoreTocPluginParams = {
   storeTocNode: (toc: HtmlElementNode) => void,
@@ -22,13 +23,10 @@ export const rehypePluginStore: Plugin<[StoreTocPluginParams]> = (options) => {
 
 
 // method for replace <ol> to <ul>
-const replaceOlToUl = (children: Node[]) => {
-  children.forEach((child) => {
-    if (child.type === 'element' && child.tagName === 'ol') {
-      child.tagName = 'ul';
-    }
-    if (child.children != null) {
-      replaceOlToUl(child.children as Node[]);
+const replaceOlToUl = (tree: HtmlElementNode) => {
+  visit(tree, 'element', (node: HtmlElementNode) => {
+    if (node.tagName === 'ol') {
+      node.tagName = 'ul';
     }
   });
 };
@@ -44,7 +42,7 @@ export const rehypePluginRestore: Plugin<[RestoreTocPluginParams]> = (options) =
     headings: ['h1', 'h2', 'h3'],
     customizeTOC: () => {
       if (tocNode != null) {
-        replaceOlToUl([tocNode]); // replace <ol> to <ul>
+        replaceOlToUl(tocNode); // replace <ol> to <ul>
 
         // restore toc
         return tocNode;

+ 11 - 11
apps/app/src/services/renderer/remark-plugins/attachment.ts

@@ -1,8 +1,8 @@
 import path from 'path';
 
-import { Schema as SanitizeOption } from 'hast-util-sanitize';
-import { Plugin } from 'unified';
-import { Node } from 'unist';
+import type { Schema as SanitizeOption } from 'hast-util-sanitize';
+import type { Link } from 'mdast';
+import type { Plugin } from 'unified';
 import { visit } from 'unist-util-visit';
 
 const SUPPORTED_ATTRIBUTES = ['attachmentId', 'url', 'attachmentName'];
@@ -13,25 +13,25 @@ const isAttachmentLink = (url: string): boolean => {
   return attachmentUrlFormat.test(url);
 };
 
-const rewriteNode = (node: Node) => {
-  const attachmentId = path.basename(node.url as string);
+const rewriteNode = (node: Link) => {
+  const attachmentId = path.basename(node.url);
+  const attachmentName = node.children[0] != null && node.children[0].type === 'text' ? node.children[0].value : '';
+
   const data = node.data ?? (node.data = {});
   data.hName = 'attachment';
   data.hProperties = {
     attachmentId,
     url: node.url,
-    attachmentName: (node.children as any)[0]?.value,
+    attachmentName,
   };
 };
 
 
 export const remarkPlugin: Plugin = () => {
   return (tree) => {
-    visit(tree, (node) => {
-      if (node.type === 'link') {
-        if (isAttachmentLink(node.url as string)) {
-          rewriteNode(node);
-        }
+    visit(tree, 'link', (node: Link) => {
+      if (isAttachmentLink(node.url)) {
+        rewriteNode(node);
       }
     });
   };

+ 23 - 0
apps/app/src/services/renderer/remark-plugins/codeblock.ts

@@ -0,0 +1,23 @@
+import type { Schema as SanitizeOption } from 'hast-util-sanitize';
+import type { InlineCode } from 'mdast';
+import type { Plugin } from 'unified';
+import { visit } from 'unist-util-visit';
+
+
+const SUPPORTED_CODE = ['inline'];
+
+export const remarkPlugin: Plugin = () => {
+  return (tree) => {
+    visit(tree, 'inlineCode', (node: InlineCode) => {
+      const data = node.data || (node.data = {});
+      data.hProperties = { inline: true };
+    });
+  };
+};
+
+export const sanitizeOption: SanitizeOption = {
+  tagNames: ['code'],
+  attributes: {
+    code: SUPPORTED_CODE,
+  },
+};

+ 9 - 10
apps/app/src/services/renderer/remark-plugins/xsv-to-table.ts

@@ -1,4 +1,5 @@
 import csvToMarkdownTable from 'csv-to-markdown-table';
+import type { Code, Table } from 'mdast';
 import { fromMarkdown } from 'mdast-util-from-markdown';
 import { gfmTableFromMarkdown } from 'mdast-util-gfm-table';
 import { gfmTable } from 'micromark-extension-gfm-table';
@@ -8,12 +9,12 @@ import { visit } from 'unist-util-visit';
 
 type Lang = 'csv' | 'csv-h' | 'tsv' | 'tsv-h';
 
-function isXsv(lang: unknown): lang is Lang {
+function isXsv(lang?: string | null | undefined): lang is Lang {
   return /^(csv|csv-h|tsv|tsv-h)$/.test(lang as string);
 }
 
 function rewriteNode(node: Node, lang: Lang) {
-  const tableContents = node.value as string;
+  const tableContents = (node as Code).value;
 
   const tableDoc = csvToMarkdownTable(
     tableContents,
@@ -21,24 +22,22 @@ function rewriteNode(node: Node, lang: Lang) {
     lang === 'csv-h' || lang === 'tsv-h',
   );
   const tableTree = fromMarkdown(tableDoc, {
-    extensions: [gfmTable],
-    mdastExtensions: [gfmTableFromMarkdown],
+    extensions: [gfmTable()],
+    mdastExtensions: [gfmTableFromMarkdown()],
   });
 
   // replace node
   if (tableTree.children[0] != null) {
     node.type = 'table';
-    node.children = tableTree.children[0].children;
+    (node as Table).children = (tableTree.children[0] as Table).children;
   }
 }
 
 export const remarkPlugin: Plugin = function() {
   return (tree) => {
-    visit(tree, (node) => {
-      if (node.type === 'code') {
-        if (isXsv(node.lang)) {
-          rewriteNode(node, node.lang);
-        }
+    visit(tree, 'code', (node: Code) => {
+      if (isXsv(node.lang)) {
+        rewriteNode(node, node.lang);
       }
     });
   };

+ 3 - 1
apps/app/src/services/renderer/renderer.tsx

@@ -25,6 +25,7 @@ import { tagNames as recommendedTagNames, attributes as recommendedAttributes }
 import * as addClass from './rehype-plugins/add-class';
 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';
 import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
 import * as xsvToTable from './remark-plugins/xsv-to-table';
 
@@ -96,6 +97,7 @@ export const generateCommonOptions = (pagePath: string|undefined): RendererOptio
       pukiwikiLikeLinker,
       growiDirective,
       remarkFrontmatter,
+      codeBlock.remarkPlugin,
     ],
     remarkRehypeOptions: {
       clobberPrefix: '', // remove clobber prefix
@@ -137,7 +139,7 @@ export const generateSSRViewOptions = (
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       getCommonSanitizeOption(config),
     )]

+ 1 - 2
apps/app/src/stores/renderer.tsx

@@ -5,11 +5,10 @@ import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr';
 
 import { getGrowiFacade } from '~/features/growi-plugin/client/utils/growi-facade-utils';
 import type { RendererOptions } from '~/interfaces/renderer-options';
-
-
 import {
   useRendererConfig,
 } from '~/stores-universal/context';
+
 import { useCurrentPagePath } from './page';
 import { useCurrentPageTocNode } from './ui';
 

+ 11 - 7
packages/presentation/src/client/components/RichSlideSection.tsx

@@ -1,7 +1,8 @@
-import React, { ReactNode } from 'react';
+import type { ReactNode } from 'react';
+import React from 'react';
 
 type RichSlideSectionProps = {
-  children: ReactNode,
+  children?: ReactNode,
   presentation?: boolean,
 }
 
@@ -13,7 +14,7 @@ const OriginalRichSlideSection = React.memo((props: RichSlideSectionProps): JSX.
       <svg data-marpit-svg="" viewBox="0 0 1280 720">
         <foreignObject width="1280" height="720">
           <section>
-            {children}
+            {children ?? <></>}
           </section>
         </foreignObject>
       </svg>
@@ -21,7 +22,8 @@ const OriginalRichSlideSection = React.memo((props: RichSlideSectionProps): JSX.
   );
 });
 
-export const RichSlideSection = React.memo((props: RichSlideSectionProps): JSX.Element => {
+
+const RichSlideSectionNoMemorized = (props: RichSlideSectionProps): JSX.Element => {
   const { children } = props;
 
   return (
@@ -29,10 +31,11 @@ export const RichSlideSection = React.memo((props: RichSlideSectionProps): JSX.E
       {children}
     </OriginalRichSlideSection>
   );
-});
+};
+export const RichSlideSection = React.memo(RichSlideSectionNoMemorized) as typeof RichSlideSectionNoMemorized;
 
 
-export const PresentationRichSlideSection = React.memo((props: RichSlideSectionProps): JSX.Element => {
+const PresentationRichSlideSectionNoMemorized = (props: RichSlideSectionProps): JSX.Element => {
   const { children } = props;
 
   return (
@@ -40,4 +43,5 @@ export const PresentationRichSlideSection = React.memo((props: RichSlideSectionP
       {children}
     </OriginalRichSlideSection>
   );
-});
+};
+export const PresentationRichSlideSection = React.memo(PresentationRichSlideSectionNoMemorized) as typeof PresentationRichSlideSectionNoMemorized;

+ 1 - 1
packages/presentation/src/client/consts/index.ts

@@ -1,4 +1,4 @@
-import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+import type { Options as ReactMarkdownOptions } from 'react-markdown';
 import type { Options as RevealOptions } from 'reveal.js';
 
 export type PresentationOptions = {

+ 1 - 1
packages/presentation/src/client/services/renderer/extract-sections.ts

@@ -5,7 +5,7 @@ import { findAfter } from 'unist-util-find-after';
 import { visit } from 'unist-util-visit';
 
 
-function wrapWithSection(parentNode: Parent, startElem: Node, endElem: Node | null, isDarkMode?: boolean): void {
+function wrapWithSection(parentNode: Parent, startElem: Node, endElem?: Node | null, isDarkMode?: boolean): void {
   const siblings = parentNode.children;
 
   const startIndex = siblings.indexOf(startElem);

+ 5 - 4
packages/presentation/src/services/use-slides-by-frontmatter.ts

@@ -1,5 +1,6 @@
 import { useEffect, useState } from 'react';
 
+import type { Parent, Root } from 'mdast';
 import type { Processor } from 'unified';
 
 type ParseResult = {
@@ -39,11 +40,11 @@ const generateFrontmatterProcessor = async(opts?: ProcessorOpts) => {
   const remarkStringify = (await import('remark-stringify')).default;
   const unified = (await import('unified')).unified;
 
-  return unified()
+  return (unified()
     .use(remarkParse)
     .use(remarkStringify)
     .use(remarkFrontmatter, ['yaml'])
-    .use(() => ((obj) => {
+    .use(() => ((obj: Parent) => {
       if (obj.children[0]?.type === 'yaml') {
         const result = parseSlideFrontmatter(obj.children[0]?.value);
         opts?.onParsed?.(result);
@@ -51,7 +52,7 @@ const generateFrontmatterProcessor = async(opts?: ProcessorOpts) => {
       else {
         opts?.onSkipped?.();
       }
-    }));
+    })));
 };
 
 export type UseSlide = {
@@ -65,7 +66,7 @@ export type UseSlide = {
  */
 export const useSlidesByFrontmatter = (markdown?: string, isEnabledMarp?: boolean): UseSlide | undefined => {
 
-  const [processor, setProcessor] = useState<Processor|undefined>();
+  const [processor, setProcessor] = useState<Processor<Root, undefined, undefined, Root, string>|undefined>();
   const [parseResult, setParseResult] = useState<UseSlide|undefined>();
 
   useEffect(() => {

+ 5 - 4
packages/remark-attachment-refs/src/client/services/renderer/refs.ts

@@ -1,9 +1,9 @@
 import { pathUtils } from '@growi/core/dist/utils';
 import { remarkGrowiDirectivePluginType } from '@growi/remark-growi-directive';
-import { Schema as SanitizeOption } from 'hast-util-sanitize';
+import type { Nodes as HastNode } from 'hast';
+import type { Schema as SanitizeOption } from 'hast-util-sanitize';
 import { selectAll } from 'hast-util-select';
-import type { Node as HastNode } from 'hast-util-select/lib/types';
-import { Plugin } from 'unified';
+import type { Plugin } from 'unified';
 import { visit } from 'unist-util-visit';
 
 import loggerFactory from '../../../utils/logger';
@@ -24,7 +24,8 @@ type DirectiveAttributes = Record<string, string>
 
 export const remarkPlugin: Plugin = function() {
   return (tree) => {
-    visit(tree, (node) => {
+    visit(tree, (node: any) => {
+      // TODO: growi-directive types
       if (node.type === remarkGrowiDirectivePluginType.Text || node.type === remarkGrowiDirectivePluginType.Leaf) {
         if (typeof node.name !== 'string') {
           return;

+ 17 - 10
packages/remark-drawio/src/services/renderer/remark-drawio.ts

@@ -1,21 +1,30 @@
+import type { Properties } from 'hast';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
+import type {
+  Code, Node, Paragraph,
+} from 'mdast';
 import type { Plugin } from 'unified';
-import type { Node } from 'unist';
 import { visit } from 'unist-util-visit';
 
 const SUPPORTED_ATTRIBUTES = ['diagramIndex', 'bol', 'eol'];
 
+interface Data {
+  hName?: string,
+  hProperties?: Properties,
+}
+
 type Lang = 'drawio';
 
-function isDrawioBlock(lang: unknown): lang is Lang {
-  return /^drawio$/.test(lang as string);
+function isDrawioBlock(lang?: string | null): lang is Lang {
+  return /^drawio$/.test(lang ?? '');
 }
 
 function rewriteNode(node: Node, index: number) {
-  const data = node.data ?? (node.data = {});
 
   node.type = 'paragraph';
-  node.children = [{ type: 'text', value: node.value }];
+  (node as Paragraph).children = [{ type: 'text', value: (node as Code).value }];
+
+  const data: Data = node.data ?? (node.data = {});
   data.hName = 'drawio';
   data.hProperties = {
     diagramIndex: index,
@@ -27,11 +36,9 @@ function rewriteNode(node: Node, index: number) {
 
 export const remarkPlugin: Plugin = function() {
   return (tree) => {
-    visit(tree, (node, index) => {
-      if (node.type === 'code') {
-        if (isDrawioBlock(node.lang)) {
-          rewriteNode(node, index ?? 0);
-        }
+    visit(tree, 'code', (node: Code, index) => {
+      if (isDrawioBlock(node.lang)) {
+        rewriteNode(node, index ?? 0);
       }
     });
   };

+ 50 - 45
packages/remark-growi-directive/src/mdast-util-growi-directive/index.js

@@ -27,54 +27,59 @@ const shortcut = /^[^\t\n\r "#'.<=>`}]+$/;
 handleDirective.peek = peekDirective;
 
 /** @type {FromMarkdownExtension} */
-export const directiveFromMarkdown = {
-  canContainEols: [DirectiveType.Text],
-  enter: {
-    directiveLeaf: enterLeaf,
-    directiveLeafAttributes: enterAttributes,
-
-    directiveText: enterText,
-    directiveTextAttributes: enterAttributes,
-  },
-  exit: {
-    directiveLeaf: exit,
-    directiveLeafAttributeName: exitAttributeName,
-    directiveLeafAttributeValue: exitAttributeValue,
-    directiveLeafAttributes: exitAttributes,
-    directiveLeafName: exitName,
-
-    directiveText: exit,
-    directiveTextAttributeName: exitAttributeName,
-    directiveTextAttributeValue: exitAttributeValue,
-    directiveTextAttributes: exitAttributes,
-    directiveTextName: exitName,
-  },
-};
-
-/** @type {ToMarkdownExtension} */
-export const directiveToMarkdown = {
-  unsafe: [
-    {
-      character: '\r',
-      inConstruct: [DirectiveType.Leaf],
+export function directiveFromMarkdown() {
+  return {
+    canContainEols: [DirectiveType.Text],
+    enter: {
+      directiveLeaf: enterLeaf,
+      directiveLeafAttributes: enterAttributes,
+
+      directiveText: enterText,
+      directiveTextAttributes: enterAttributes,
     },
-    {
-      character: '\n',
-      inConstruct: [DirectiveType.Leaf],
+    exit: {
+      directiveLeaf: exit,
+      directiveLeafAttributeName: exitAttributeName,
+      directiveLeafAttributeValue: exitAttributeValue,
+      directiveLeafAttributes: exitAttributes,
+      directiveLeafName: exitName,
+
+      directiveText: exit,
+      directiveTextAttributeName: exitAttributeName,
+      directiveTextAttributeValue: exitAttributeValue,
+      directiveTextAttributes: exitAttributes,
+      directiveTextName: exitName,
+
     },
-    {
-      before: '[^$]',
-      character: '$',
-      after: '[A-Za-z]',
-      inConstruct: ['phrasing'],
+  };
+}
+
+/** @type {ToMarkdownExtension} */
+export function directiveToMarkdown() {
+  return {
+    unsafe: [
+      {
+        character: '\r',
+        inConstruct: [DirectiveType.Leaf],
+      },
+      {
+        character: '\n',
+        inConstruct: [DirectiveType.Leaf],
+      },
+      {
+        before: '[^$]',
+        character: '$',
+        after: '[A-Za-z]',
+        inConstruct: ['phrasing'],
+      },
+      { atBreak: true, character: '$', after: '$' },
+    ],
+    handlers: {
+      [DirectiveType.Leaf]: handleDirective,
+      [DirectiveType.Text]: handleDirective,
     },
-    { atBreak: true, character: '$', after: '$' },
-  ],
-  handlers: {
-    [DirectiveType.Leaf]: handleDirective,
-    [DirectiveType.Text]: handleDirective,
-  },
-};
+  };
+}
 
 /** @type {FromMarkdownHandle} */
 function enterLeaf(token) {

+ 19 - 20
packages/remark-growi-directive/src/remark-growi-directive.js

@@ -1,7 +1,6 @@
 /**
  * @typedef {import('mdast').Root} Root
- *
- * @typedef {import('mdast-util-directive')} DoNotTouchAsThisImportIncludesDirectivesInTree
+ * @typedef {import('unified').Processor<Root>} Processor
  */
 
 import { directiveFromMarkdown, directiveToMarkdown } from './mdast-util-growi-directive/index.js';
@@ -10,26 +9,26 @@ import { directive } from './micromark-extension-growi-directive/index.js';
 /**
     * Plugin to support GROWI plugin (`$lsx(/path, depth=2)`).
     *
-    * @type {import('unified').Plugin<void[], Root>}
+    * Add support for generic directives.
+    *
+    * ###### Notes
+    *
+    * Doesn’t handle the directives: create your own plugin to do that.
+    *
+    * @returns {undefined}
+    *   Nothing.
     */
 export function remarkGrowiDirectivePlugin() {
-  const data = this.data();
-
-  add('micromarkExtensions', directive());
-  add('fromMarkdownExtensions', directiveFromMarkdown);
-  add('toMarkdownExtensions', directiveToMarkdown);
+  // @ts-expect-error: TS is wrong about `this`.
+  // eslint-disable-next-line @typescript-eslint/no-this-alias
+  const self = /** @type {Processor} */ (this);
+  const data = self.data();
 
-  /**
-      * @param {string} field
-      * @param {unknown} value
-      */
-  function add(field, value) {
-    const list = /** @type {unknown[]} */ (
-      // Other extensions
-      /* c8 ignore next 2 */
-      data[field] ? data[field] : (data[field] = [])
-    );
+  const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = []);
+  const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
+  const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
 
-    list.push(value);
-  }
+  micromarkExtensions.push(directive());
+  fromMarkdownExtensions.push(directiveFromMarkdown());
+  toMarkdownExtensions.push(directiveToMarkdown());
 }

+ 28 - 28
packages/remark-growi-directive/test/mdast-util-growi-directive.test.js

@@ -13,7 +13,7 @@ describe('markdown -> mdast', () => {
     expect(
       fromMarkdown('a $b[c](d) e.', {
         extensions: [directive()],
-        mdastExtensions: [directiveFromMarkdown],
+        mdastExtensions: [directiveFromMarkdown()],
       }).children[0],
     ).toEqual({
       type: 'paragraph',
@@ -65,7 +65,7 @@ describe('markdown -> mdast', () => {
     expect(
       fromMarkdown('$a[b](c)', {
         extensions: [directive()],
-        mdastExtensions: [directiveFromMarkdown],
+        mdastExtensions: [directiveFromMarkdown()],
       }).children[0],
     ).toEqual({
       type: DirectiveType.Leaf,
@@ -92,7 +92,7 @@ describe('markdown -> mdast', () => {
   it('should support content in a label', () => {
     const tree = fromMarkdown('x $a[b *c*\nd]', {
       extensions: [directive()],
-      mdastExtensions: [directiveFromMarkdown],
+      mdastExtensions: [directiveFromMarkdown()],
     });
 
     removePosition(tree, { force: true });
@@ -126,7 +126,7 @@ describe('markdown -> mdast', () => {
   it('should support attributes', () => {
     const tree = fromMarkdown('x $a(#b.c.d e=f g="h&amp;i&unknown;j")', {
       extensions: [directive()],
-      mdastExtensions: [directiveFromMarkdown],
+      mdastExtensions: [directiveFromMarkdown()],
     });
 
     removePosition(tree, { force: true });
@@ -156,7 +156,7 @@ describe('markdown -> mdast', () => {
   it('should support EOLs in attributes', () => {
     const tree = fromMarkdown('$a(b\nc="d\ne")', {
       extensions: [directive()],
-      mdastExtensions: [directiveFromMarkdown],
+      mdastExtensions: [directiveFromMarkdown()],
     });
 
     removePosition(tree, { force: true });
@@ -193,7 +193,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' b.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $ b.\n');
   });
@@ -210,7 +210,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' c.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b c.\n');
   });
@@ -230,7 +230,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' d.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b[c] d.\n');
   });
@@ -250,7 +250,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' f.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b[c\\[d\\]e] f.\n');
   });
@@ -270,7 +270,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' e.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b[c\nd] e.\n');
   });
@@ -294,7 +294,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' k.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(c="d" e="f" g j="2") k.\n');
   });
@@ -315,7 +315,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' k.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(#d .a.b.c key="value") k.\n');
   });
@@ -336,7 +336,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' k.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(x="y&#x22;\'\r\nz") k.\n');
   });
@@ -357,7 +357,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' e.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(id="c#d") e.\n');
   });
@@ -378,7 +378,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' g.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(c.d e<f) g.\n');
   });
@@ -401,7 +401,7 @@ describe('mdast -> markdown', () => {
             { type: 'text', value: ' k.' },
           ],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a $b(c.d e f<g hij) k.\n');
   });
@@ -411,7 +411,7 @@ describe('mdast -> markdown', () => {
     expect(
       toMarkdown(
         { type: DirectiveType.Leaf },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$\n');
   });
@@ -421,7 +421,7 @@ describe('mdast -> markdown', () => {
     expect(
       toMarkdown(
         { type: DirectiveType.Leaf, name: 'a' },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$a\n');
   });
@@ -434,7 +434,7 @@ describe('mdast -> markdown', () => {
           name: 'a',
           children: [{ type: 'text', value: 'b' }],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$a[b]\n');
   });
@@ -447,7 +447,7 @@ describe('mdast -> markdown', () => {
           name: 'a',
           children: [{ type: 'text', value: 'b\nc' }],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$a[b&#xA;c]\n');
   });
@@ -461,7 +461,7 @@ describe('mdast -> markdown', () => {
           attributes: { '#b': '', '.c.d': '', key: 'e\nf' },
           children: [],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$a(#b .c.d key="e&#xA;f")\n');
   });
@@ -473,7 +473,7 @@ describe('mdast -> markdown', () => {
           type: 'paragraph',
           children: [{ type: 'text', value: 'a$b' }],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('a\\$b\n');
   });
@@ -483,7 +483,7 @@ describe('mdast -> markdown', () => {
       toMarkdown({
         type: 'paragraph',
         children: [{ type: 'text', value: 'a$9' }],
-      }, { extensions: [directiveToMarkdown] }),
+      }, { extensions: [directiveToMarkdown()] }),
     ).toBe('a$9\n');
   });
 
@@ -492,7 +492,7 @@ describe('mdast -> markdown', () => {
       toMarkdown({
         type: 'paragraph',
         children: [{ type: 'text', value: 'a$c' }],
-      }, { extensions: [directiveToMarkdown] }),
+      }, { extensions: [directiveToMarkdown()] }),
     ).toBe('a\\$c\n');
   });
 
@@ -503,7 +503,7 @@ describe('mdast -> markdown', () => {
           type: 'paragraph',
           children: [{ type: 'text', value: '$\na' }],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('$\na\n');
   });
@@ -515,7 +515,7 @@ describe('mdast -> markdown', () => {
           type: 'paragraph',
           children: [{ type: 'text', value: '$a' }],
         },
-        { extensions: [directiveToMarkdown] },
+        { extensions: [directiveToMarkdown()] },
       ),
     ).toBe('\\$a\n');
   });
@@ -525,7 +525,7 @@ describe('mdast -> markdown', () => {
       toMarkdown({
         type: 'paragraph',
         children: [{ type: 'text', value: '$\na' }],
-      }, { extensions: [directiveToMarkdown] }),
+      }, { extensions: [directiveToMarkdown()] }),
     ).toBe('$\na\n');
   });
 
@@ -537,7 +537,7 @@ describe('mdast -> markdown', () => {
           { type: DirectiveType.Text, name: 'red', children: [] },
           { type: 'text', value: '$' },
         ],
-      }, { extensions: [directiveToMarkdown] }),
+      }, { extensions: [directiveToMarkdown()] }),
     ).toBe('$red$\n');
   });
 

+ 5 - 4
packages/remark-lsx/src/client/services/renderer/lsx.ts

@@ -2,11 +2,11 @@ import assert from 'assert';
 
 import { hasHeadingSlash, removeTrailingSlash, addTrailingSlash } from '@growi/core/dist/utils/path-utils';
 import { remarkGrowiDirectivePluginType } from '@growi/remark-growi-directive';
-import { Schema as SanitizeOption } from 'hast-util-sanitize';
+import type { Nodes as HastNode } from 'hast';
+import type { Schema as SanitizeOption } from 'hast-util-sanitize';
 import { selectAll } from 'hast-util-select';
-import type { Node as HastNode } from 'hast-util-select/lib/types';
 import isAbsolute from 'is-absolute-url';
-import { Plugin } from 'unified';
+import type { Plugin } from 'unified';
 import { visit } from 'unist-util-visit';
 
 const NODE_NAME_PATTERN = new RegExp(/ls|lsx/);
@@ -17,7 +17,8 @@ type DirectiveAttributes = Record<string, string>
 
 export const remarkPlugin: Plugin = function() {
   return (tree) => {
-    visit(tree, (node) => {
+    // TODO: setting growi-directive types
+    visit(tree, (node: any) => {
       if (node.type === remarkGrowiDirectivePluginType.Text || node.type === remarkGrowiDirectivePluginType.Leaf) {
         if (typeof node.name !== 'string') {
           return;