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

Merge branch 'master' into feat/opentelemetry

Shun Miyazawa 1 год назад
Родитель
Сommit
f940367660
54 измененных файлов с 408 добавлено и 415 удалено
  1. 26 1
      CHANGELOG.md
  2. 1 1
      apps/app/docker/README.md
  3. 1 1
      apps/app/package.json
  4. 15 8
      apps/app/src/client/services/renderer/renderer.tsx
  5. 0 90
      apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
  6. 1 1
      apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
  7. 18 1
      apps/app/src/components/InstallerForm.tsx
  8. 13 2
      apps/app/src/components/Page/PageView.tsx
  9. 3 1
      apps/app/src/components/Page/RevisionRenderer.tsx
  10. 26 0
      apps/app/src/components/Page/SlideRenderer.tsx
  11. 15 5
      apps/app/src/components/PageEditor/Preview.tsx
  12. 7 4
      apps/app/src/components/PagePresentationModal.tsx
  13. 1 1
      apps/app/src/components/Presentation/Presentation.tsx
  14. 1 1
      apps/app/src/components/Presentation/Slides.tsx
  15. 0 33
      apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx
  16. 10 6
      apps/app/src/components/ShareLinkPageView.tsx
  17. 6 6
      apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx
  18. 10 5
      apps/app/src/server/models/activity.ts
  19. 6 4
      apps/app/src/server/models/in-app-notification.ts
  20. 3 0
      apps/app/src/server/models/page.ts
  21. 2 2
      apps/app/src/server/models/user.js
  22. 12 6
      apps/app/src/server/routes/apiv3/installer.ts
  23. 0 27
      apps/app/src/stores/slide-viewer-renderer.tsx
  24. 1 1
      apps/slackbot-proxy/package.json
  25. 1 1
      package.json
  26. 1 1
      packages/core/package.json
  27. 1 1
      packages/editor/package.json
  28. 15 3
      packages/presentation/package.json
  29. 0 0
      packages/presentation/src/client/components/GrowiSlides.tsx
  30. 0 0
      packages/presentation/src/client/components/MarpSlides.tsx
  31. 0 0
      packages/presentation/src/client/components/Presentation.global.scss
  32. 0 0
      packages/presentation/src/client/components/Presentation.module.scss
  33. 14 18
      packages/presentation/src/client/components/Presentation.tsx
  34. 0 0
      packages/presentation/src/client/components/RichSlideSection.tsx
  35. 0 0
      packages/presentation/src/client/components/Slides.module.scss
  36. 0 0
      packages/presentation/src/client/components/Slides.tsx
  37. 0 0
      packages/presentation/src/client/consts/index.ts
  38. 0 1
      packages/presentation/src/client/index.ts
  39. 0 0
      packages/presentation/src/client/services/growi-marpit.ts
  40. 0 0
      packages/presentation/src/client/services/renderer/extract-sections.ts
  41. 1 0
      packages/presentation/src/services/index.ts
  42. 0 43
      packages/presentation/src/services/parse-slide-frontmatter.ts
  43. 0 89
      packages/presentation/src/services/renderer/slides.ts
  44. 97 0
      packages/presentation/src/services/use-slides-by-frontmatter.ts
  45. 12 1
      packages/presentation/vite.config.ts
  46. 1 1
      packages/preset-templates/package.json
  47. 1 1
      packages/preset-themes/package.json
  48. 1 1
      packages/remark-attachment-refs/package.json
  49. 1 1
      packages/remark-drawio/package.json
  50. 1 1
      packages/remark-growi-directive/package.json
  51. 1 1
      packages/remark-lsx/package.json
  52. 1 1
      packages/slack/package.json
  53. 1 1
      packages/ui/package.json
  54. 80 42
      yarn.lock

+ 26 - 1
CHANGELOG.md

@@ -1,9 +1,34 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.0.3...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.0.4...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.0.4](https://github.com/weseek/growi/compare/v7.0.3...v7.0.4) - 2024-05-13
+
+### 💎 Features
+
+* feat: Add a button to open PageCreateModal in the new page creation lead (#8774) @miya
+* feat: Show Yjs status indicator in SubNavigation (#8709) @miya
+* feat: Apply locale to defualt page title (#8600) @jam411
+
+### 🚀 Improvement
+
+* imprv: Behavior when creating first user fails (#8801) @miya
+* imprv: Add indexes for performance (#8800) @yuki-takei
+* imprv: Autosize Input for rename (#8795) @yuki-takei
+* imprv: behavior of dropdown toggle in 'Recent Changes' sidebar (#8782) @maeshinshin
+* imprv: Hide  personal drop down when guest user (#8786) @kazutoweseek
+
+### 🐛 Bug Fixes
+
+* fix: Drawio not available with GROWI slides (#8725) @reiji-h
+* fix: Auto-scroll to the active page in the page tree (#8772) @reiji-h
+
+### 🧰 Maintenance
+
+* ci(deps): bump ejs from 3.1.9 to 3.1.10 (#8784) @dependabot
+
 ## [v7.0.3](https://github.com/weseek/growi/compare/v7.0.2...v7.0.3) - 2024-05-01
 
 ### 🚀 Improvement

+ 1 - 1
apps/app/docker/README.md

@@ -10,7 +10,7 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`7.0.3`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.3/apps/app/docker/Dockerfile)
+* [`7.0.4`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.4/apps/app/docker/Dockerfile)
 * [`6.3.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",

+ 15 - 8
apps/app/src/client/services/renderer/renderer.tsx

@@ -1,7 +1,6 @@
 import assert from 'assert';
 
 import { isClient } from '@growi/core/dist/utils/browser-utils';
-import * as slides from '@growi/presentation';
 import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
 import * as drawio from '@growi/remark-drawio';
 // eslint-disable-next-line import/extensions
@@ -19,7 +18,6 @@ import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import * as mermaid from '~/features/mermaid';
 import { RehypeSanitizeOption } from '~/interfaces/rehype';
@@ -68,7 +66,6 @@ export const generateViewOptions = (
     attachment.remarkPlugin,
     lsxGrowiDirective.remarkPlugin,
     refsGrowiDirective.remarkPlugin,
-    [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
   );
   if (config.isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
@@ -84,7 +81,6 @@ export const generateViewOptions = (
       drawio.sanitizeOption,
       mermaid.sanitizeOption,
       attachment.sanitizeOption,
-      slides.sanitizeOption,
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
     )]
@@ -119,7 +115,6 @@ export const generateViewOptions = (
     components.mermaid = mermaid.MermaidViewer;
     components.attachment = RichAttachment;
     components.img = LightBox;
-    components.slide = SlideViewer;
   }
 
   if (config.isEnabledXssPrevention) {
@@ -241,6 +236,21 @@ export const generatePresentationViewOptions = (
   // based on simple view options
   const options = generateSimpleViewOptions(config, pagePath);
 
+  const { rehypePlugins } = options;
+
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      addLineNumberAttribute.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    addLineNumberAttribute.rehypePlugin,
+    rehypeSanitizePlugin,
+  );
+
   if (config.isEnabledXssPrevention) {
     verifySanitizePlugin(options, false);
   }
@@ -262,7 +272,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     attachment.remarkPlugin,
     lsxGrowiDirective.remarkPlugin,
     refsGrowiDirective.remarkPlugin,
-    [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
   );
   if (config.isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
@@ -281,7 +290,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
       addLineNumberAttribute.sanitizeOption,
-      slides.sanitizeOption,
     )]
     : () => {};
 
@@ -306,7 +314,6 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     components.mermaid = mermaid.MermaidViewer;
     components.attachment = RichAttachment;
     components.img = LightBox;
-    components.slide = SlideViewer;
   }
 
   if (config.isEnabledXssPrevention) {

+ 0 - 90
apps/app/src/client/services/renderer/slide-viewer-renderer.tsx

@@ -1,90 +0,0 @@
-import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
-import * as drawio from '@growi/remark-drawio';
-// eslint-disable-next-line import/extensions
-import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
-import katex from 'rehype-katex';
-import sanitize from 'rehype-sanitize';
-import math from 'remark-math';
-import deepmerge from 'ts-deepmerge';
-import type { Pluggable } from 'unified';
-
-import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
-import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-import * as mermaid from '~/features/mermaid';
-import { RehypeSanitizeOption } from '~/interfaces/rehype';
-import type { RendererOptions } from '~/interfaces/renderer-options';
-import type { RendererConfig } from '~/interfaces/services/renderer';
-import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
-import * as attachment from '~/services/renderer/remark-plugins/attachment';
-import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
-import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
-import {
-  commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
-} from '~/services/renderer/renderer';
-
-
-export const generatePresentationViewOptions = (
-    config: RendererConfig,
-    pagePath: string,
-): RendererOptions => {
-  const options = generateCommonOptions(pagePath);
-
-  const { remarkPlugins, rehypePlugins, components } = options;
-
-  // add remark plugins
-  remarkPlugins.push(
-    math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
-    drawio.remarkPlugin,
-    mermaid.remarkPlugin,
-    xsvToTable.remarkPlugin,
-    attachment.remarkPlugin,
-    lsxGrowiDirective.remarkPlugin,
-    refsGrowiDirective.remarkPlugin,
-  );
-
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    injectCustomSanitizeOption(config);
-  }
-
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      commonSanitizeOption,
-      drawio.sanitizeOption,
-      mermaid.sanitizeOption,
-      attachment.sanitizeOption,
-      lsxGrowiDirective.sanitizeOption,
-      refsGrowiDirective.sanitizeOption,
-      addLineNumberAttribute.sanitizeOption,
-    )]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
-    [refsGrowiDirective.rehypePlugin, { pagePath }],
-    rehypeSanitizePlugin,
-    addLineNumberAttribute.rehypePlugin,
-    katex,
-  );
-
-  // add components
-  if (components != null) {
-    components.lsx = lsxGrowiDirective.LsxImmutable;
-    components.ref = refsGrowiDirective.RefImmutable;
-    components.refs = refsGrowiDirective.RefsImmutable;
-    components.refimg = refsGrowiDirective.RefImgImmutable;
-    components.refsimg = refsGrowiDirective.RefsImgImmutable;
-    components.gallery = refsGrowiDirective.GalleryImmutable;
-    components.drawio = drawio.DrawioViewer;
-    components.mermaid = mermaid.MermaidViewer;
-    components.attachment = RichAttachment;
-    components.img = LightBox;
-  }
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options, false);
-  }
-  return options;
-};

+ 1 - 1
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -53,7 +53,7 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
                 href={`${t('admin:customize_settings.presentation_options.marp_in_gorwi_link')}`}
                 target="_blank"
                 rel="noopener noreferrer"
-              >{`${t('admin:customize_settings.presenattion_options.marp_in_growi')}`}
+              >{`${t('admin:customize_settings.presentation_options.marp_in_growi')}`}
               </a>
             </p>
           </CustomizePresentationOption>

+ 18 - 1
apps/app/src/components/InstallerForm.tsx

@@ -10,7 +10,7 @@ import { i18n as i18nConfig } from '^/config/next-i18next.config';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
-
+import type { IErrorV3 } from '~/interfaces/errors/v3-error';
 
 import styles from './InstallerForm.module.scss';
 
@@ -28,6 +28,8 @@ const InstallerForm = memo((): JSX.Element => {
   const [isLoading, setIsLoading] = useState(false);
   const [currentLocale, setCurrentLocale] = useState(isSupportedLang ? i18n.language : Lang.en_US);
 
+  const [registerErrors, setRegisterErrors] = useState<IErrorV3[]>([]);
+
   const checkUserName = useCallback(async(event) => {
     const axios = require('axios').create({
       headers: {
@@ -70,6 +72,7 @@ const InstallerForm = memo((): JSX.Element => {
     };
 
     try {
+      setRegisterErrors([]);
       await apiv3Post('/installer', data);
       router.push('/');
     }
@@ -77,6 +80,7 @@ const InstallerForm = memo((): JSX.Element => {
       const err = errs[0];
       const code = err.code;
       setIsLoading(false);
+      setRegisterErrors(errs);
 
       if (code === 'failed_to_login_after_install') {
         toastError(t('installer.failed_to_login_after_install'));
@@ -103,6 +107,19 @@ const InstallerForm = memo((): JSX.Element => {
         </div>
       </div>
       <div className="row mt-2">
+
+        {
+          registerErrors != null && registerErrors.length > 0 && (
+            <p className="alert alert-danger text-center">
+              {registerErrors.map(err => (
+                <span>
+                  {t(err.message)}<br />
+                </span>
+              ))}
+            </p>
+          )
+        }
+
         <form role="form" id="register-form" className="ps-1" onSubmit={submitHandler}>
           <div className="dropdown mb-3">
             <div className="input-group dropdown-with-icon">

+ 13 - 2
apps/app/src/components/Page/PageView.tsx

@@ -4,6 +4,7 @@ import React, {
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
+import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import dynamic from 'next/dynamic';
 
 import { useShouldExpandContent } from '~/client/services/layout';
@@ -40,6 +41,7 @@ const Comments = dynamic<CommentsProps>(() => import('../Comments').then(mod =>
 const UsersHomepageFooter = dynamic<UsersHomepageFooterProps>(() => import('../UsersHomepageFooter')
   .then(mod => mod.UsersHomepageFooter), { ssr: false });
 const IdenticalPathPage = dynamic(() => import('../IdenticalPathPage').then(mod => mod.IdenticalPathPage), { ssr: false });
+const SlideRenderer = dynamic(() => import('./SlideRenderer').then(mod => mod.SlideRenderer), { ssr: false });
 
 
 type Props = {
@@ -74,6 +76,10 @@ export const PageView = (props: Props): JSX.Element => {
   const shouldExpandContent = useShouldExpandContent(page);
 
 
+  const markdown = page?.revision?.body;
+  const isSlide = useSlidesByFrontmatter(markdown, rendererConfig.isEnabledMarp);
+
+
   // ***************************  Auto Scroll  ***************************
   useEffect(() => {
     // do nothing if hash is empty
@@ -90,6 +96,7 @@ export const PageView = (props: Props): JSX.Element => {
   }, [isCommentsLoaded]);
   // *******************************  end  *******************************
 
+
   const specialContents = useMemo(() => {
     if (isIdenticalPathPage) {
       return <IdenticalPathPage />;
@@ -128,15 +135,19 @@ export const PageView = (props: Props): JSX.Element => {
       return <NotFoundPage path={pagePath} />;
     }
 
-    const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
     const markdown = page.revision.body;
+    const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
 
     return (
       <>
         <PageContentsUtilities />
 
         <div className="flex-expand-vert justify-content-between">
-          <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
+
+          { isSlide != null
+            ? <SlideRenderer marp={isSlide.marp} markdown={markdown} />
+            : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
+          }
 
           { !isIdenticalPathPage && !isNotFound && (
             <div id="comments-container" ref={commentsContainerRef}>

+ 3 - 1
apps/app/src/components/Page/RevisionRenderer.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 
-import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
+import type { FallbackProps } from 'react-error-boundary';
+import { ErrorBoundary } from 'react-error-boundary';
 import ReactMarkdown from 'react-markdown';
 
 import type { RendererOptions } from '~/interfaces/renderer-options';
@@ -8,6 +9,7 @@ import loggerFactory from '~/utils/logger';
 
 import 'katex/dist/katex.min.css';
 
+
 const logger = loggerFactory('components:Page:RevisionRenderer');
 
 type Props = {

+ 26 - 0
apps/app/src/components/Page/SlideRenderer.tsx

@@ -0,0 +1,26 @@
+import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
+
+import { usePresentationViewOptions } from '~/stores/renderer';
+
+import { Slides } from '../Presentation/Slides';
+
+type SlideRendererProps = {
+  markdown: string,
+  marp?: boolean,
+};
+
+export const SlideRenderer = (props: SlideRendererProps): JSX.Element => {
+
+  const { markdown, marp = false } = props;
+
+  const { data: rendererOptions } = usePresentationViewOptions();
+
+  return (
+    <Slides
+      hasMarpFlag={marp}
+      options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
+    >
+      {markdown}
+    </Slides>
+  );
+};

+ 15 - 5
apps/app/src/components/PageEditor/Preview.tsx

@@ -1,10 +1,12 @@
 import type { CSSProperties } from 'react';
-import React from 'react';
+
+import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 
 import type { RendererOptions } from '~/interfaces/renderer-options';
+import { useIsEnabledMarp } from '~/stores/context';
 
 import RevisionRenderer from '../Page/RevisionRenderer';
-
+import { SlideRenderer } from '../Page/SlideRenderer';
 
 import styles from './Preview.module.scss';
 
@@ -28,17 +30,25 @@ const Preview = (props: Props): JSX.Element => {
     expandContentWidth,
   } = props;
 
+  const { data: isEnabledMarp } = useIsEnabledMarp();
+  const isSlide = useSlidesByFrontmatter(markdown, isEnabledMarp);
+
   const fluidLayoutClass = expandContentWidth ? 'fluid-layout' : '';
 
+
   return (
     <div
       data-testid="page-editor-preview-body"
       className={`${moduleClass} ${fluidLayoutClass} ${pagePath === '/Sidebar' ? 'preview-sidebar' : ''}`}
       style={style}
     >
-      { markdown != null && (
-        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown}></RevisionRenderer>
-      ) }
+      { markdown != null
+        && (
+          isSlide != null
+            ? <SlideRenderer marp={isSlide.marp} markdown={markdown} />
+            : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown}></RevisionRenderer>
+        )
+      }
     </div>
   );
 

+ 7 - 4
apps/app/src/components/PagePresentationModal.tsx

@@ -1,6 +1,7 @@
 import React, { useCallback } from 'react';
 
-import type { PresentationProps } from '@growi/presentation';
+import type { PresentationProps } from '@growi/presentation/dist/client';
+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';
@@ -39,6 +40,10 @@ const PagePresentationModal = (): JSX.Element => {
 
   const { data: isEnabledMarp } = useIsEnabledMarp();
 
+  const markdown = currentPage?.revision?.body;
+
+  const isSlide = useSlidesByFrontmatter(markdown, isEnabledMarp);
+
   const toggleFullscreenHandler = useCallback(() => {
     if (fullscreen.active) {
       fullscreen.exit();
@@ -61,8 +66,6 @@ const PagePresentationModal = (): JSX.Element => {
     return <></>;
   }
 
-  const markdown = currentPage?.revision?.body;
-
   return (
     <Modal
       isOpen={isOpen}
@@ -92,7 +95,7 @@ const PagePresentationModal = (): JSX.Element => {
               },
               isDarkMode,
             }}
-            isEnabledMarp={isEnabledMarp}
+            marp={isSlide?.marp}
           >
             {markdown}
           </Presentation>

+ 1 - 1
apps/app/src/components/Presentation/Presentation.tsx

@@ -1,4 +1,4 @@
-import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation';
+import { Presentation as PresentationSubstance, type PresentationProps } from '@growi/presentation/dist/client';
 
 import '@growi/presentation/dist/style.css';
 

+ 1 - 1
apps/app/src/components/Presentation/Slides.tsx

@@ -1,4 +1,4 @@
-import { Slides as SlidesSubstance, type SlidesProps } from '@growi/presentation';
+import { Slides as SlidesSubstance, type SlidesProps } from '@growi/presentation/dist/client';
 
 import '@growi/presentation/dist/style.css';
 

+ 0 - 33
apps/app/src/components/ReactMarkdownComponents/SlideViewer.tsx

@@ -1,33 +0,0 @@
-import React from 'react';
-
-import dynamic from 'next/dynamic';
-import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
-
-import { usePresentationViewOptions } from '~/stores/slide-viewer-renderer';
-
-
-const Slides = dynamic(() => import('../Presentation/Slides').then(mod => mod.Slides), { ssr: false });
-
-type SlideViewerProps = {
-  marp: string | undefined,
-  children: string,
-}
-
-export const SlideViewer: React.FC<SlideViewerProps> = React.memo((props: SlideViewerProps) => {
-  const {
-    marp, children,
-  } = props;
-
-  const { data: rendererOptions } = usePresentationViewOptions();
-
-  return (
-    <Slides
-      hasMarpFlag={marp != null}
-      options={{ rendererOptions: rendererOptions as ReactMarkdownOptions }}
-    >
-      {children}
-    </Slides>
-  );
-});
-
-SlideViewer.displayName = 'SlideViewer';

+ 10 - 6
apps/app/src/components/ShareLinkPageView.tsx

@@ -1,12 +1,14 @@
 import React, { useMemo } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
+import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import dynamic from 'next/dynamic';
 
 import { useShouldExpandContent } from '~/client/services/layout';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
+import { useIsEnabledMarp } from '~/stores/context';
 import { useIsNotFound } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
@@ -23,7 +25,7 @@ const logger = loggerFactory('growi:Page');
 
 const PageSideContents = dynamic<PageSideContentsProps>(() => import('./PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
 const ForbiddenPage = dynamic(() => import('./ForbiddenPage'), { ssr: false });
-
+const SlideRenderer = dynamic(() => import('./Page/SlideRenderer').then(mod => mod.SlideRenderer), { ssr: false });
 
 type Props = {
   pagePath: string,
@@ -47,6 +49,10 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
   const shouldExpandContent = useShouldExpandContent(page);
 
+  const markdown = page?.revision?.body;
+
+  const isSlide = useSlidesByFrontmatter(markdown, rendererConfig.isEnabledMarp);
+
   const isNotFound = isNotFoundMeta || page == null || shareLink == null;
 
   const specialContents = useMemo(() => {
@@ -85,11 +91,9 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     const rendererOptions = viewOptions ?? generateSSRViewOptions(rendererConfig, pagePath);
     const markdown = page.revision.body;
 
-    return (
-      <>
-        <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
-      </>
-    );
+    return isSlide != null
+      ? <SlideRenderer marp={isSlide.marp} markdown={markdown} />
+      : <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />;
   };
 
   return (

+ 6 - 6
apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx

@@ -184,13 +184,13 @@ export const PageTreeContent = memo(({ isWipPageShown }: PageTreeContentProps) =
         CustomTreeItem={PageTreeItem}
       />
 
-      {/* {!isGuestUser && !isReadOnlyUser && migrationStatus?.migratablePagesCount != null && migrationStatus.migratablePagesCount !== 0 && ( */}
-      <div className="grw-pagetree-footer border-top mt-4 py-2 w-100">
-        <div className="private-legacy-pages-link px-3 py-2">
-          <PrivateLegacyPagesLink />
+      {!isGuestUser && !isReadOnlyUser && migrationStatus?.migratablePagesCount != null && migrationStatus.migratablePagesCount !== 0 && (
+        <div className="grw-pagetree-footer border-top mt-4 py-2 w-100">
+          <div className="private-legacy-pages-link px-3 py-2">
+            <PrivateLegacyPagesLink />
+          </div>
         </div>
-      </div>
-      {/* )} */}
+      )}
     </div>
   );
 });

+ 10 - 5
apps/app/src/server/models/activity.ts

@@ -1,13 +1,17 @@
 import type { Ref, IPage } from '@growi/core';
-import {
-  Types, Document, Model, Schema, SortOrder,
+import type {
+  Types, Document, Model, SortOrder,
 } from 'mongoose';
+import { Schema } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
 
+import type {
+  IActivity, ISnapshot, SupportedActionType, SupportedTargetModelType, SupportedEventModelType,
+} from '~/interfaces/activity';
 import {
-  IActivity, ISnapshot, AllSupportedActions, SupportedActionType,
-  AllSupportedTargetModels, SupportedTargetModelType,
-  AllSupportedEventModels, SupportedEventModelType,
+  AllSupportedActions,
+  AllSupportedTargetModels,
+  AllSupportedEventModels,
 } from '~/interfaces/activity';
 
 import loggerFactory from '../../utils/logger';
@@ -83,6 +87,7 @@ const activitySchema = new Schema<ActivityDocument, ActivityModel>({
     updatedAt: false,
   },
 });
+// activitySchema.index({ createdAt: 1 }); // Do not create index here because it is created by ActivityService as TTL index
 activitySchema.index({ target: 1, action: 1 });
 activitySchema.index({
   user: 1, target: 1, action: 1, createdAt: 1,

+ 6 - 4
apps/app/src/server/models/in-app-notification.ts

@@ -1,6 +1,5 @@
-import {
-  Types, Document, Schema, Model,
-} from 'mongoose';
+import type { Types, Document, Model } from 'mongoose';
+import { Schema } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
 
 import { AllSupportedTargetModels, AllSupportedActions } from '~/interfaces/activity';
@@ -8,7 +7,7 @@ import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 
 import { getOrCreateModel } from '../util/mongoose-utils';
 
-import { ActivityDocument } from './activity';
+import type { ActivityDocument } from './activity';
 
 
 const { STATUS_UNREAD, STATUS_UNOPENED, STATUS_OPENED } = InAppNotificationStatuses;
@@ -79,6 +78,9 @@ const inAppNotificationSchema = new Schema<InAppNotificationDocument, InAppNotif
 }, {
   timestamps: { createdAt: true, updatedAt: false },
 });
+// indexes
+inAppNotificationSchema.index({ createdAt: 1 });
+// apply plugins
 inAppNotificationSchema.plugin(mongoosePaginate);
 
 const transform = (doc, ret) => {

+ 3 - 0
apps/app/src/server/models/page.ts

@@ -153,6 +153,9 @@ const schema = new Schema<PageDocument, PageModel>({
   toJSON: { getters: true },
   toObject: { getters: true },
 });
+// indexes
+schema.index({ createdAt: 1 });
+schema.index({ updatedAt: 1 });
 // apply plugins
 schema.plugin(mongoosePaginate);
 schema.plugin(uniqueValidator);

+ 2 - 2
apps/app/src/server/models/user.js

@@ -49,7 +49,7 @@ module.exports = function(crowi) {
     isGravatarEnabled: { type: Boolean, default: false },
     isEmailPublished: { type: Boolean, default: true },
     googleId: String,
-    name: { type: String },
+    name: { type: String, index: true },
     username: { type: String, required: true, unique: true },
     email: { type: String, unique: true, sparse: true },
     slackMemberId: { type: String, unique: true, sparse: true },
@@ -69,7 +69,7 @@ module.exports = function(crowi) {
     status: {
       type: Number, required: true, default: STATUS_ACTIVE, index: true,
     },
-    lastLoginAt: { type: Date },
+    lastLoginAt: { type: Date, index: true },
     admin: { type: Boolean, default: 0, index: true },
     readOnly: { type: Boolean, default: 0 },
     isInvitationEmailSended: { type: Boolean, default: false },

+ 12 - 6
apps/app/src/server/routes/apiv3/installer.ts

@@ -1,16 +1,16 @@
 import { ErrorV3 } from '@growi/core/dist/models';
-import express, { Request, Router } from 'express';
+import type { Request, Router } from 'express';
+import express from 'express';
 
 import { SupportedAction } from '~/interfaces/activity';
 import loggerFactory from '~/utils/logger';
 
-import Crowi from '../../crowi';
+import type Crowi from '../../crowi';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
-import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
-import { registerRules } from '../../middlewares/register-form-validator';
+import { registerRules, registerValidation } from '../../middlewares/register-form-validator';
 import { InstallerService, FailedToCreateAdminUserError } from '../../service/installer';
 
-import { ApiV3Response } from './interfaces/apiv3-response';
+import type { ApiV3Response } from './interfaces/apiv3-response';
 
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -27,11 +27,17 @@ module.exports = (crowi: Crowi): Router => {
   const router = express.Router();
 
   // eslint-disable-next-line max-len
-  router.post('/', registerRules(), apiV3FormValidator, addActivity, async(req: FormRequest, res: ApiV3Response) => {
+  router.post('/', registerRules(), registerValidation, addActivity, async(req: FormRequest, res: ApiV3Response) => {
     const appService = crowi.appService;
     if (appService == null) {
       return res.apiv3Err(new ErrorV3('GROWI cannot be installed due to an internal error', 'app_service_not_setup'), 500);
     }
+
+    if (!req.form.isValid) {
+      const errors = req.form.errors;
+      return res.apiv3Err(errors, 400);
+    }
+
     const registerForm = req.body.registerForm || {};
 
     const name = registerForm.name;

+ 0 - 27
apps/app/src/stores/slide-viewer-renderer.tsx

@@ -1,27 +0,0 @@
-import useSWR, { type SWRResponse } from 'swr';
-
-import type { RendererOptions } from '~/interfaces/renderer-options';
-import { useRendererConfig } from '~/stores/context';
-import { useCurrentPagePath } from '~/stores/page';
-
-
-export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
-
-  const isAllDataValid = currentPagePath != null && rendererConfig != null;
-
-  return useSWR(
-    isAllDataValid
-      ? ['presentationViewOptions', currentPagePath, rendererConfig]
-      : null,
-    async([, currentPagePath, rendererConfig]) => {
-      const { generatePresentationViewOptions } = await import('~/client/services/renderer/slide-viewer-renderer');
-      return generatePresentationViewOptions(rendererConfig, currentPagePath);
-    },
-    {
-      revalidateOnFocus: false,
-      revalidateOnReconnect: false,
-    },
-  );
-};

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.0.4-slackbot-proxy.0",
+  "version": "7.0.5-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/editor/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/editor",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "type": "module",
   "module": "dist/index.js",

+ 15 - 3
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [
@@ -13,6 +13,17 @@
     "dist"
   ],
   "type": "module",
+  "exports": {
+    "./dist/client": {
+      "import": "./dist/client/index.js"
+    },
+    "./dist/services": {
+      "import": "./dist/services/index.js"
+    },
+    "./dist/style.css": {
+      "import": "./dist/style.css"
+    }
+  },
   "scripts": {
     "build": "vite build",
     "clean": "shx rm -rf dist",
@@ -28,7 +39,8 @@
     "@growi/core": "link:../core"
   },
   "devDependencies": {
-    "@marp-team/marp-core": "^3.6.0",
+    "@marp-team/marp-core": "^3.9.0",
+    "@marp-team/marpit": "^2.6.1",
     "@types/reveal.js": "^4.4.1",
     "eslint-plugin-regex": "^1.8.0",
     "hast-util-sanitize": "^4.1.0",
@@ -38,6 +50,7 @@
     "mdast-util-to-markdown": "^1.3.0",
     "react-markdown": "^8.0.7",
     "remark-frontmatter": "^4.0.1",
+    "remark-parse": "^10.0.0",
     "remark-stringify": "^10.0.0",
     "reveal.js": "^4.4.0",
     "unified": "^10.1.2",
@@ -45,7 +58,6 @@
     "unist-util-visit": "^4.0.0"
   },
   "peerDependencies": {
-    "@marp-team/marpit": "*",
     "next": "^14",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"

+ 0 - 0
packages/presentation/src/components/GrowiSlides.tsx → packages/presentation/src/client/components/GrowiSlides.tsx


+ 0 - 0
packages/presentation/src/components/MarpSlides.tsx → packages/presentation/src/client/components/MarpSlides.tsx


+ 0 - 0
packages/presentation/src/components/Presentation.global.scss → packages/presentation/src/client/components/Presentation.global.scss


+ 0 - 0
packages/presentation/src/components/Presentation.module.scss → packages/presentation/src/client/components/Presentation.module.scss


+ 14 - 18
packages/presentation/src/components/Presentation.tsx → packages/presentation/src/client/components/Presentation.tsx

@@ -1,9 +1,8 @@
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
 
 import Reveal from 'reveal.js';
 
 import type { PresentationOptions } from '../consts';
-import { parseSlideFrontmatterInMarkdown } from '../services/parse-slide-frontmatter';
 
 import { Slides } from './Slides';
 
@@ -34,37 +33,34 @@ const removeAllHiddenElements = () => {
 
 export type PresentationProps = {
   options: PresentationOptions,
-  isEnabledMarp: boolean,
+  marp?: boolean,
   children?: string,
 }
 
 export const Presentation = (props: PresentationProps): JSX.Element => {
-  const { options, isEnabledMarp, children } = props;
+  const { options, marp, children } = props;
   const { revealOptions } = options;
 
-  const [marp] = parseSlideFrontmatterInMarkdown(children);
-  const hasMarpFlag = isEnabledMarp && marp;
-
   useEffect(() => {
-    let deck: Reveal.Api;
-    if (children != null) {
-      deck = new Reveal({ ...baseRevealOptions, ...revealOptions });
-      deck.initialize()
-        .then(() => deck.slide(0)); // navigate to the first slide
-
-      deck.on('ready', removeAllHiddenElements);
-      deck.on('slidechanged', removeAllHiddenElements);
+    if (children == null) {
+      return;
     }
+    const deck = new Reveal({ ...baseRevealOptions, ...revealOptions });
+    deck.initialize()
+      .then(() => deck.slide(0)); // navigate to the first slide
+
+    deck.on('ready', removeAllHiddenElements);
+    deck.on('slidechanged', removeAllHiddenElements);
 
     return function cleanup() {
-      deck?.off('ready', removeAllHiddenElements);
-      deck?.off('slidechanged', removeAllHiddenElements);
+      deck.off('ready', removeAllHiddenElements);
+      deck.off('slidechanged', removeAllHiddenElements);
     };
   }, [children, revealOptions]);
 
   return (
     <div className={`grw-presentation ${styles['grw-presentation']} reveal`}>
-      <Slides options={options} hasMarpFlag={hasMarpFlag} presentation>{children}</Slides>
+      <Slides options={options} hasMarpFlag={marp} presentation>{children}</Slides>
     </div>
   );
 };

+ 0 - 0
packages/presentation/src/components/RichSlideSection.tsx → packages/presentation/src/client/components/RichSlideSection.tsx


+ 0 - 0
packages/presentation/src/components/Slides.module.scss → packages/presentation/src/client/components/Slides.module.scss


+ 0 - 0
packages/presentation/src/components/Slides.tsx → packages/presentation/src/client/components/Slides.tsx


+ 0 - 0
packages/presentation/src/consts/index.ts → packages/presentation/src/client/consts/index.ts


+ 0 - 1
packages/presentation/src/index.ts → packages/presentation/src/client/index.ts

@@ -1,3 +1,2 @@
 export * from './components/Presentation';
 export * from './components/Slides';
-export * from './services/renderer/slides';

+ 0 - 0
packages/presentation/src/services/growi-marpit.ts → packages/presentation/src/client/services/growi-marpit.ts


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


+ 1 - 0
packages/presentation/src/services/index.ts

@@ -0,0 +1 @@
+export * from './use-slides-by-frontmatter';

+ 0 - 43
packages/presentation/src/services/parse-slide-frontmatter.ts

@@ -1,43 +0,0 @@
-import remarkFrontmatter from 'remark-frontmatter';
-import remarkParse from 'remark-parse';
-import remarkStringify from 'remark-stringify';
-import { unified } from 'unified';
-
-
-export const parseSlideFrontmatter = (frontmatter: string): [boolean, boolean] => {
-
-  let marp = false;
-  let slide = false;
-
-  const lines = frontmatter.split('\n');
-  lines.forEach((line) => {
-    const [key, value] = line.split(':').map(part => part.trim());
-    if (key === 'marp' && value === 'true') {
-      marp = true;
-    }
-    if (key === 'slide' && value === 'true') {
-      slide = true;
-    }
-  });
-
-  return [marp, slide];
-};
-
-export const parseSlideFrontmatterInMarkdown = (markdown?: string): [boolean, boolean] => {
-
-  let marp = false;
-  let slide = false;
-
-  unified()
-    .use(remarkParse)
-    .use(remarkStringify)
-    .use(remarkFrontmatter, ['yaml'])
-    .use(() => ((obj) => {
-      if (obj.children[0]?.type === 'yaml') {
-        [marp, slide] = parseSlideFrontmatter(obj.children[0]?.value as string);
-      }
-    }))
-    .process(markdown as string);
-
-  return [marp, slide];
-};

+ 0 - 89
packages/presentation/src/services/renderer/slides.ts

@@ -1,89 +0,0 @@
-import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-import type { Root } from 'mdast';
-import { frontmatterToMarkdown } from 'mdast-util-frontmatter';
-import { gfmToMarkdown } from 'mdast-util-gfm';
-import { toMarkdown } from 'mdast-util-to-markdown';
-import type { Plugin } from 'unified';
-import type { Node } from 'unist';
-import { visit } from 'unist-util-visit';
-
-import { parseSlideFrontmatter } from '../parse-slide-frontmatter';
-
-const SUPPORTED_ATTRIBUTES = ['children', 'marp'];
-
-const nodeToMakrdown = (node: Node) => {
-  return toMarkdown(node as Root, {
-    extensions: [
-      frontmatterToMarkdown(['yaml']),
-      gfmToMarkdown(),
-    ],
-  });
-};
-
-// Allow node tree to be converted to markdown
-const removeCustomType = (tree: Node) => {
-  // Try toMarkdown() on all Node.
-  visit(tree, (node) => {
-    const tmp = node?.children;
-    node.children = [];
-    try {
-      nodeToMakrdown(node);
-    }
-    catch (err) {
-      // if some Node cannot convert to markdown, change to a convertible type
-      node.type = 'text';
-      node.value = '';
-    }
-    finally {
-      node.children = tmp;
-    }
-  });
-};
-
-const rewriteNode = (tree: Node, node: Node, isEnabledMarp: boolean) => {
-
-  const [marp, slide] = parseSlideFrontmatter(node.value as string);
-
-  if ((marp && isEnabledMarp) || slide) {
-
-    removeCustomType(tree);
-
-    const markdown = nodeToMakrdown(tree);
-
-    const newNode: Node = {
-      type: 'root',
-      data: {},
-      position: tree.position,
-      children: tree.children,
-    };
-
-    const data = newNode.data ?? (newNode.data = {});
-    tree.children = [newNode];
-    data.hName = 'slide';
-    data.hProperties = {
-      marp: (marp && isEnabledMarp) ? '' : undefined,
-      children: markdown,
-    };
-  }
-};
-
-type SlidePluginParams = {
-  isEnabledMarp: boolean,
-}
-
-export const remarkPlugin: Plugin<[SlidePluginParams]> = (options) => {
-  return (tree) => {
-    visit(tree, (node) => {
-      if (node.type === 'yaml' && node.value != null) {
-        rewriteNode(tree, node, options.isEnabledMarp);
-      }
-    });
-  };
-};
-
-export const sanitizeOption: SanitizeOption = {
-  tagNames: ['slide'],
-  attributes: {
-    slide: SUPPORTED_ATTRIBUTES,
-  },
-};

+ 97 - 0
packages/presentation/src/services/use-slides-by-frontmatter.ts

@@ -0,0 +1,97 @@
+import { useEffect, useState } from 'react';
+
+import type { Processor } from 'unified';
+
+type ParseResult = {
+  marp: boolean | undefined,
+  slide: boolean | undefined,
+}
+
+const parseSlideFrontmatter = (frontmatter: string): ParseResult => {
+
+  let marp;
+  let slide;
+
+  const lines = frontmatter.split('\n');
+  lines.forEach((line) => {
+    const [key, value] = line.split(':').map(part => part.trim());
+    if (key === 'marp' && value === 'true') {
+      marp = true;
+    }
+    if (key === 'slide' && value === 'true') {
+      slide = true;
+    }
+  });
+
+  return { marp, slide };
+};
+
+
+type ProcessorOpts = {
+  onParsed?: (result: ParseResult) => void,
+  onSkipped?: () => void,
+};
+
+const generateFrontmatterProcessor = async(opts?: ProcessorOpts) => {
+
+  const remarkFrontmatter = (await import('remark-frontmatter')).default;
+  const remarkParse = (await import('remark-parse')).default;
+  const remarkStringify = (await import('remark-stringify')).default;
+  const unified = (await import('unified')).unified;
+
+  return unified()
+    .use(remarkParse)
+    .use(remarkStringify)
+    .use(remarkFrontmatter, ['yaml'])
+    .use(() => ((obj) => {
+      if (obj.children[0]?.type === 'yaml') {
+        const result = parseSlideFrontmatter(obj.children[0]?.value);
+        opts?.onParsed?.(result);
+      }
+      else {
+        opts?.onSkipped?.();
+      }
+    }));
+};
+
+export type UseSlide = {
+  marp?: boolean,
+}
+
+/**
+ * Frontmatter parser for slide
+ * @param markdown Markdwon document
+ * @returns An UseSlide instance. If the markdown does not contain neither "marp" or "slide" attribute in frontmatter, it returns undefined.
+ */
+export const useSlidesByFrontmatter = (markdown?: string, isEnabledMarp?: boolean): UseSlide | undefined => {
+
+  const [processor, setProcessor] = useState<Processor|undefined>();
+  const [parseResult, setParseResult] = useState<UseSlide|undefined>();
+
+  useEffect(() => {
+    if (processor != null) {
+      return;
+    }
+
+    (async() => {
+      const p = await generateFrontmatterProcessor({
+        onParsed: result => setParseResult(result.marp || result.slide ? result : undefined),
+        onSkipped: () => setParseResult(undefined),
+      });
+      setProcessor(p);
+    })();
+  }, [processor]);
+
+  useEffect(() => {
+    if (markdown == null || processor == null) {
+      return;
+    }
+
+    processor.process(markdown);
+  }, [markdown, processor]);
+
+  return parseResult != null
+    ? { marp: isEnabledMarp && parseResult?.marp }
+    : undefined;
+
+};

+ 12 - 1
packages/presentation/vite.config.ts

@@ -1,4 +1,7 @@
+import path from 'path';
+
 import react from '@vitejs/plugin-react';
+import glob from 'glob';
 import { nodeExternals } from 'rollup-plugin-node-externals';
 import { defineConfig } from 'vite';
 import dts from 'vite-plugin-dts';
@@ -20,9 +23,17 @@ export default defineConfig({
     outDir: 'dist',
     sourcemap: true,
     lib: {
-      entry: 'src/index.ts',
+      entry: glob.sync(path.resolve(__dirname, 'src/**/*.ts'), {
+        ignore: '**/*.spec.ts',
+      }),
       name: 'presentation-libs',
       formats: ['es'],
     },
+    rollupOptions: {
+      output: {
+        preserveModules: true,
+        preserveModulesRoot: 'src',
+      },
+    },
   },
 });

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "7.0.4-RC.0",
+  "version": "7.0.5-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [

+ 80 - 42
yarn.lock

@@ -1854,7 +1854,7 @@
     xdg-basedir "^4.0.0"
 
 "@growi/core@link:packages/core":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     bson-objectid "^2.0.4"
     escape-string-regexp "^4.0.0"
@@ -1863,7 +1863,7 @@
   version "7.0.0-RC.0"
 
 "@growi/editor@link:packages/editor":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     markdown-table "^3.0.3"
     react "^18.2.0"
@@ -1876,18 +1876,18 @@
     extensible-custom-error "^0.0.7"
 
 "@growi/presentation@link:packages/presentation":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
 "@growi/preset-templates@link:packages/preset-templates":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
 
 "@growi/preset-themes@link:packages/preset-themes":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
 
 "@growi/remark-attachment-refs@link:packages/remark-attachment-refs":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1900,10 +1900,10 @@
     universal-bunyan "^0.9.2"
 
 "@growi/remark-drawio@link:packages/remark-drawio":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
 
 "@growi/remark-growi-directive@link:packages/remark-growi-directive":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@types/mdast" "^3.0.0"
     "@types/unist" "^2.0.0"
@@ -1920,7 +1920,7 @@
     uvu "^0.5.0"
 
 "@growi/remark-lsx@link:packages/remark-lsx":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1932,7 +1932,7 @@
     swr "^2.2.2"
 
 "@growi/slack@link:packages/slack":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@slack/oauth" "^2.0.1"
     "@slack/web-api" "^6.2.4"
@@ -1951,7 +1951,7 @@
     url-join "^4.0.0"
 
 "@growi/ui@link:packages/ui":
-  version "7.0.4-RC.0"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
@@ -2648,18 +2648,18 @@
     google-libphonenumber ">=3.2.10"
     lodash ">=4.17.15"
 
-"@marp-team/marp-core@^3.6.0":
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/@marp-team/marp-core/-/marp-core-3.6.0.tgz#858958b96208370fe51f4df98642e2bfe2d090c7"
-  integrity sha512-V2tA5nLR4HR+hTGrMTaSLB7bDKi+xz1FrhytOyHV/U2ztRRjAKtlQnul+xPZ7LpRxVsLNwbmO/Alw1AwUx7eXQ==
+"@marp-team/marp-core@^3.9.0":
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/@marp-team/marp-core/-/marp-core-3.9.0.tgz#c554443bdcf84db635221b3a4085d1a656dd8bfc"
+  integrity sha512-gi6nq0rsB1oMA8ReppW4XxmS4fisQiAsD0ZoUgLeG4h6SWatveCAA7fZyxnXfwA2UC8pNb7ktPqYdRsxvuwntA==
   dependencies:
-    "@marp-team/marpit" "^2.4.2"
+    "@marp-team/marpit" "^2.6.1"
     "@marp-team/marpit-svg-polyfill" "^2.1.0"
-    highlight.js "^11.7.0"
-    katex "^0.16.4"
+    highlight.js "11.8.0"
+    katex "^0.16.9"
     mathjax-full "^3.2.2"
-    postcss "^8.4.21"
-    postcss-selector-parser "^6.0.11"
+    postcss "^8.4.31"
+    postcss-selector-parser "^6.0.13"
     xss "^1.0.14"
 
 "@marp-team/marpit-svg-polyfill@^2.1.0":
@@ -2667,18 +2667,18 @@
   resolved "https://registry.yarnpkg.com/@marp-team/marpit-svg-polyfill/-/marpit-svg-polyfill-2.1.0.tgz#40e7ce3a2aa7496748541cc7053e6779d2f866ac"
   integrity sha512-VqCoAKwv1HJdzZp36dDPxznz2JZgRjkVSSPHpCzk72G2N753F0HPKXjevdjxmzN6gir9bUGBgMD1SguWJIi11A==
 
-"@marp-team/marpit@^2.4.2":
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/@marp-team/marpit/-/marpit-2.4.2.tgz#f74240f14c15a4c5f0bd6b51afabc674750799be"
-  integrity sha512-MBCMaSjkeDzF+Exq8CmhmwsMTzU7fK4NyzA7zl+hreBGppAPvOq5NCnIR2O6nOm9F0QJ9Vmxhs7ZP/BXZ+lWBw==
+"@marp-team/marpit@^2.6.1":
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/@marp-team/marpit/-/marpit-2.6.1.tgz#6937e6bc6b1b0cc9efc6c1e0948fc165df038edc"
+  integrity sha512-Hg7fZ8SqXwLjxeIFzSnlXkXEmt0ZXPeMJneEn9n1M495a34C4xtkgEgL8R1MW2IRCh4Yibn0xmGKcaf+GuqR2A==
   dependencies:
     color-string "^1.9.1"
     cssesc "^3.0.0"
     js-yaml "^4.1.0"
     lodash.kebabcase "^4.1.1"
-    markdown-it "^13.0.1"
+    markdown-it "^13.0.2"
     markdown-it-front-matter "^0.2.3"
-    postcss "^8.4.19"
+    postcss "^8.4.29"
 
 "@microsoft/api-extractor-model@7.28.13":
   version "7.28.13"
@@ -7523,7 +7523,7 @@ commander@^6.2.1:
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
   integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
 
-commander@^8.0.0, commander@^8.1.0:
+commander@^8.0.0, commander@^8.1.0, commander@^8.3.0:
   version "8.3.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
   integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
@@ -10941,7 +10941,12 @@ helmet@^4.6.0:
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
 
-highlight.js@11.9.0, highlight.js@^11.7.0:
+highlight.js@11.8.0:
+  version "11.8.0"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.8.0.tgz#966518ea83257bae2e7c9a48596231856555bb65"
+  integrity sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==
+
+highlight.js@11.9.0:
   version "11.9.0"
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
   integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
@@ -12577,12 +12582,12 @@ katex@^0.15.0:
   dependencies:
     commander "^8.0.0"
 
-katex@^0.16.4:
-  version "0.16.4"
-  resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.4.tgz#87021bc3bbd80586ef715aeb476794cba6a49ad4"
-  integrity sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==
+katex@^0.16.9:
+  version "0.16.10"
+  resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.10.tgz#6f81b71ac37ff4ec7556861160f53bc5f058b185"
+  integrity sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==
   dependencies:
-    commander "^8.0.0"
+    commander "^8.3.0"
 
 keycloak-js@^17.0.1:
   version "17.0.1"
@@ -13252,10 +13257,10 @@ markdown-it-front-matter@^0.2.3:
   resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.2.3.tgz#d6fa0f4b362e02086dd4ce8219fadf3f4c9cfa37"
   integrity sha512-s9+rcClLmZsZc3YL8Awjg/YO/VdphlE20LJ9Bx5a8RAFLI5a1vq6Mll8kOzG6w/wy8yhFLBupaa6Mfd60GATkA==
 
-markdown-it@^13.0.1:
-  version "13.0.1"
-  resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430"
-  integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==
+markdown-it@^13.0.2:
+  version "13.0.2"
+  resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536"
+  integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==
   dependencies:
     argparse "^2.0.1"
     entities "~3.0.1"
@@ -15645,7 +15650,15 @@ postcss-scss@^4.0.3:
   resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.3.tgz#36c23c19a804274e722e83a54d20b838ab4767ac"
   integrity sha512-j4KxzWovfdHsyxwl1BxkUal/O4uirvHgdzMKS1aWJBAV0qh2qj5qAZqpeBfVUYGWv+4iK9Az7SPyZ4fyNju1uA==
 
-postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.7:
+postcss-selector-parser@^6.0.13:
+  version "6.0.16"
+  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04"
+  integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==
+  dependencies:
+    cssesc "^3.0.0"
+    util-deprecate "^1.0.2"
+
+postcss-selector-parser@^6.0.7:
   version "6.0.11"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc"
   integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
@@ -15685,7 +15698,7 @@ postcss@^7.0.0:
     picocolors "^0.2.1"
     source-map "^0.6.1"
 
-postcss@^8.3.11, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.31, postcss@^8.4.38:
+postcss@^8.3.11, postcss@^8.4.29, postcss@^8.4.31, postcss@^8.4.38:
   version "8.4.38"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
   integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
@@ -17958,7 +17971,7 @@ string-template@>=1.0.0:
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
 
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17976,6 +17989,15 @@ string-width@=4.2.2:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
 
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -18058,7 +18080,7 @@ stringify-entities@^4.0.0:
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18072,6 +18094,13 @@ strip-ansi@^3.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -19796,7 +19825,7 @@ word-wrap@^1.2.3:
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -19814,6 +19843,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"