Browse Source

Merge pull request #7452 from weseek/support/build-remark-lsx-with-vite

support: Build remark-lsx with vite
Yuki Takei 3 years ago
parent
commit
96e31f5953
92 changed files with 621 additions and 782 deletions
  1. 0 3
      apps/app/jest.config.js
  2. 12 4
      apps/app/src/client/services/activate-plugin.ts
  3. 260 0
      apps/app/src/client/services/renderer/renderer.tsx
  4. 1 1
      apps/app/src/components/Admin/AuditLog/ActivityTable.tsx
  5. 2 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss
  6. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  7. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  8. 1 1
      apps/app/src/components/Admin/Users/UserTable.tsx
  9. 1 1
      apps/app/src/components/IdenticalPathPage.module.scss
  10. 1 1
      apps/app/src/components/InAppNotification/InAppNotificationElm.tsx
  11. 1 1
      apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  12. 1 1
      apps/app/src/components/Navbar/AuthorInfo.tsx
  13. 1 1
      apps/app/src/components/Navbar/PersonalDropdown.jsx
  14. 1 12
      apps/app/src/components/Page/PageView.tsx
  15. 1 1
      apps/app/src/components/PageAlert/TrashPageAlert.tsx
  16. 1 1
      apps/app/src/components/PageAttachment/DeleteAttachmentModal.tsx
  17. 1 1
      apps/app/src/components/PageAttachment/PageAttachmentList.tsx
  18. 1 1
      apps/app/src/components/PageComment.tsx
  19. 1 1
      apps/app/src/components/PageComment/Comment.tsx
  20. 1 1
      apps/app/src/components/PageComment/CommentEditor.tsx
  21. 1 1
      apps/app/src/components/PageComment/DeleteCommentModal.tsx
  22. 1 14
      apps/app/src/components/PageEditor.tsx
  23. 1 1
      apps/app/src/components/PageEditor/ConflictDiffModal.tsx
  24. 1 1
      apps/app/src/components/PageHistory/Revision.tsx
  25. 1 1
      apps/app/src/components/PageList/PageList.module.scss
  26. 2 1
      apps/app/src/components/PageList/PageListItemL.tsx
  27. 3 1
      apps/app/src/components/PageList/PageListItemS.tsx
  28. 1 1
      apps/app/src/components/PagePresentationModal.tsx
  29. 1 1
      apps/app/src/components/SearchPage/SearchPageBase.module.scss
  30. 3 1
      apps/app/src/components/SearchTypeahead.tsx
  31. 1 13
      apps/app/src/components/ShareLink/ShareLinkPageView.tsx
  32. 2 1
      apps/app/src/components/Sidebar/RecentChanges.tsx
  33. 1 1
      apps/app/src/components/User/SeenUserInfo.tsx
  34. 1 1
      apps/app/src/components/User/UserInfo.tsx
  35. 1 1
      apps/app/src/components/User/UserPictureList.jsx
  36. 1 1
      apps/app/src/components/UsersHomePageFooter.module.scss
  37. 20 0
      apps/app/src/interfaces/renderer-options.ts
  38. 1 1
      apps/app/src/server/crowi/index.js
  39. 7 270
      apps/app/src/services/renderer/renderer.tsx
  40. 40 33
      apps/app/src/stores/renderer.tsx
  41. 2 1
      apps/app/src/stores/ui.tsx
  42. 2 0
      apps/app/src/styles/style-app.scss
  43. 3 3
      apps/app/tsconfig.build.client.json
  44. 1 1
      apps/app/tsconfig.build.server.json
  45. 3 3
      apps/app/tsconfig.json
  46. 1 1
      packages-obsolete/plugin-attachment-refs/src/client/js/components/AttachmentList.jsx
  47. 1 1
      packages/core/package.json
  48. 2 19
      packages/core/src/index.ts
  49. 2 0
      packages/core/src/plugin/index.ts
  50. 0 18
      packages/core/src/plugin/model/tag-context.ts
  51. 0 57
      packages/core/src/plugin/util/args-parser.js
  52. 0 5
      packages/core/src/plugin/util/custom-tag-utils.ts
  53. 1 1
      packages/core/src/plugin/util/option-parser.ts
  54. 16 0
      packages/core/src/utils/index.ts
  55. 0 42
      packages/core/test/plugin/util/args-parser.test.js
  56. 0 74
      packages/core/test/plugin/util/custom-tag-utils.test.js
  57. 1 1
      packages/hackmd/package.json
  58. 1 1
      packages/presentation/package.json
  59. 1 1
      packages/preset-themes/package.json
  60. 1 0
      packages/remark-lsx/.gitignore
  61. 20 10
      packages/remark-lsx/package.json
  62. 2 0
      packages/remark-lsx/src/client/index.ts
  63. 0 8
      packages/remark-lsx/src/components/Lsx.module.scss
  64. 7 1
      packages/remark-lsx/src/components/LsxPageList/LsxListView.module.scss
  65. 2 1
      packages/remark-lsx/src/components/LsxPageList/LsxListView.tsx
  66. 7 0
      packages/remark-lsx/src/components/LsxPageList/LsxPage.module.scss
  67. 6 2
      packages/remark-lsx/src/components/LsxPageList/LsxPage.tsx
  68. 1 3
      packages/remark-lsx/src/components/lsx-context.ts
  69. 0 6
      packages/remark-lsx/src/index.ts
  70. 6 2
      packages/remark-lsx/src/server/index.ts
  71. 6 7
      packages/remark-lsx/src/server/routes/lsx.ts
  72. 0 13
      packages/remark-lsx/tsconfig.base.json
  73. 0 17
      packages/remark-lsx/tsconfig.build.json
  74. 7 3
      packages/remark-lsx/tsconfig.json
  75. 37 0
      packages/remark-lsx/vite.client.config.ts
  76. 37 0
      packages/remark-lsx/vite.server.config.ts
  77. 1 1
      packages/slack/package.json
  78. 1 0
      packages/ui/.gitignore
  79. 11 9
      packages/ui/package.json
  80. 1 1
      packages/ui/src/components/Attachment.tsx
  81. 1 1
      packages/ui/src/components/FootstampIcon.tsx
  82. 1 1
      packages/ui/src/components/PagePath/PageListMeta.tsx
  83. 0 10
      packages/ui/src/index.ts
  84. 0 2
      packages/ui/src/styles/molecules/_page_list.scss
  85. 2 0
      packages/ui/src/utils/index.ts
  86. 0 18
      packages/ui/tsconfig.base.json
  87. 0 19
      packages/ui/tsconfig.build.cjs.json
  88. 0 17
      packages/ui/tsconfig.build.esm.json
  89. 0 17
      packages/ui/tsconfig.build.json
  90. 7 3
      packages/ui/tsconfig.json
  91. 35 0
      packages/ui/vite.config.ts
  92. 5 1
      turbo.json

+ 0 - 3
apps/app/jest.config.js

@@ -7,9 +7,6 @@ const MODULE_NAME_MAPPING = {
   '^@growi/codemirror-textlint$': '<rootDir>/../../packages/codemirror-textlint/src',
   '^@growi/remark-drawio$': '<rootDir>/../../packages/remark-drawio/src',
   '^@growi/remark-growi-directive$': '<rootDir>/../../packages/remark-growi-directive/src',
-  '^@growi/remark-lsx$': '<rootDir>/../../packages/remark-drawio/src',
-  '^@growi/remark-lsx/(.+)$': '<rootDir>/../../packages/remark-lsx/src/$1',
-  '^@growi/ui$': '<rootDir>/../../packages/ui/src',
 };
 
 module.exports = {

+ 12 - 4
apps/app/src/client/services/activate-plugin.ts

@@ -1,7 +1,6 @@
-import { initializeGrowiFacade } from '~/utils/growi-facade';
+import { initializeGrowiFacade, registerGrowiFacade } from '~/utils/growi-facade';
 import loggerFactory from '~/utils/logger';
 
-
 declare global {
   // eslint-disable-next-line vars-on-top, no-var
   var pluginActivators: {
@@ -17,10 +16,19 @@ const logger = loggerFactory('growi:cli:ActivatePluginService');
 
 export class ActivatePluginService {
 
-  static activateAll(): void {
+  static async activateAll(): Promise<void> {
     initializeGrowiFacade();
 
-    const { pluginActivators } = window;
+    // register renderer options to facade
+    const { generateViewOptions, generatePreviewOptions } = await import('./renderer/renderer');
+    registerGrowiFacade({
+      markdownRenderer: {
+        optionsGenerators: {
+          generateViewOptions,
+          generatePreviewOptions,
+        },
+      },
+    });
 
     if (pluginActivators == null) {
       return;

+ 260 - 0
apps/app/src/client/services/renderer/renderer.tsx

@@ -0,0 +1,260 @@
+import assert from 'assert';
+
+import { isClient } from '@growi/core/dist/utils/browser-utils';
+import * as drawioPlugin from '@growi/remark-drawio';
+// eslint-disable-next-line import/extensions
+import * as lsxGrowiPlugin from '@growi/remark-lsx/dist/client/index.mjs';
+import katex from 'rehype-katex';
+import sanitize from 'rehype-sanitize';
+import slug from 'rehype-slug';
+import type { HtmlElementNode } from 'rehype-toc';
+import breaks from 'remark-breaks';
+import math from 'remark-math';
+import deepmerge from 'ts-deepmerge';
+import type { Pluggable } from 'unified';
+
+
+import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents/DrawioViewerWithEditButton';
+import { Header } from '~/components/ReactMarkdownComponents/Header';
+import { Table } from '~/components/ReactMarkdownComponents/Table';
+import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
+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 keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
+import * as toc from '~/services/renderer/rehype-plugins/relocate-toc';
+import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
+import * as table from '~/services/renderer/remark-plugins/table';
+import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
+import {
+  commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
+} from '~/services/renderer/renderer';
+import loggerFactory from '~/utils/logger';
+
+// import EasyGrid from './PreProcessor/EasyGrid';
+// import BlockdiagConfigurer from './markdown-it/blockdiag';
+
+
+const logger = loggerFactory('growi:cli:services:renderer');
+
+
+assert(isClient(), 'This module must be loaded only from client modules.');
+
+
+export const generateViewOptions = (
+    pagePath: string,
+    config: RendererConfig,
+    storeTocNode: (toc: HtmlElementNode) => void,
+): RendererOptions => {
+
+  const options = generateCommonOptions(pagePath);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    math,
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    drawioPlugin.remarkPlugin,
+    xsvToTable.remarkPlugin,
+    lsxGrowiPlugin.remarkPlugin,
+  );
+  if (config.isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
+  }
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawioPlugin.sanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    slug,
+    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    rehypeSanitizePlugin,
+    katex,
+    [toc.rehypePluginStore, { storeTocNode }],
+  );
+
+  // add components
+  if (components != null) {
+    components.h1 = Header;
+    components.h2 = Header;
+    components.h3 = Header;
+    components.lsx = lsxGrowiPlugin.Lsx;
+    components.drawio = DrawioViewerWithEditButton;
+    components.table = TableWithEditButton;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};
+
+export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
+
+  const options = generateCommonOptions(undefined);
+
+  const { rehypePlugins } = options;
+
+  // add remark plugins
+  // remarkPlugins.push();
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [toc.rehypePluginRestore, { tocNode }],
+    rehypeSanitizePlugin,
+  );
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options);
+  }
+
+  return options;
+};
+
+export const generateSimpleViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+    highlightKeywords?: string | string[],
+    overrideIsEnabledLinebreaks?: boolean,
+): RendererOptions => {
+  const options = generateCommonOptions(pagePath);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    math,
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    drawioPlugin.remarkPlugin,
+    xsvToTable.remarkPlugin,
+    lsxGrowiPlugin.remarkPlugin,
+    table.remarkPlugin,
+  );
+
+  const isEnabledLinebreaks = overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
+
+  if (isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
+  }
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawioPlugin.sanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [keywordHighlighter.rehypePlugin, { keywords: highlightKeywords }],
+    rehypeSanitizePlugin,
+    katex,
+  );
+
+  // add components
+  if (components != null) {
+    components.lsx = lsxGrowiPlugin.LsxImmutable;
+    components.drawio = drawioPlugin.DrawioViewer;
+    components.table = Table;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};
+
+export const generatePresentationViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+): RendererOptions => {
+  // based on simple view options
+  const options = generateSimpleViewOptions(config, pagePath);
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};
+
+export const generatePreviewOptions = (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 }],
+    drawioPlugin.remarkPlugin,
+    xsvToTable.remarkPlugin,
+    lsxGrowiPlugin.remarkPlugin,
+    table.remarkPlugin,
+  );
+  if (config.isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
+  }
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+      drawioPlugin.sanitizeOption,
+      addLineNumberAttribute.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    addLineNumberAttribute.rehypePlugin,
+    rehypeSanitizePlugin,
+    katex,
+  );
+
+  // add components
+  if (components != null) {
+    components.lsx = lsxGrowiPlugin.LsxImmutable;
+    components.drawio = drawioPlugin.DrawioViewer;
+    components.table = Table;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};

+ 1 - 1
apps/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,7 +1,7 @@
 import React, { FC, useState, useCallback } from 'react';
 
 import { pagePathUtils } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format } from 'date-fns';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { useTranslation } from 'react-i18next';

+ 2 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss

@@ -1 +1,2 @@
-@use '~/styles/molecules/page_list';
+@use '@growi/ui/src/styles/molecules/page_list';
+

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 

+ 1 - 1
apps/app/src/components/Admin/Users/UserTable.tsx

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
 import type { IUserHasId } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 

+ 1 - 1
apps/app/src/components/IdenticalPathPage.module.scss

@@ -1,5 +1,5 @@
+@use '@growi/ui/src/styles/molecules/page_list';
 @use '~/styles/molecules/page-accessories-control';
-@use '~/styles/molecules/page_list';
 
 .grw-page-accessories-control :global {
   @extend %grw-page-accessories-control;

+ 1 - 1
apps/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -3,7 +3,7 @@ import React, {
 } from 'react';
 
 import { HasObjectId } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { DropdownItem } from 'reactstrap';
 
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';

+ 1 - 1
apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx

@@ -3,7 +3,7 @@ import React, {
 } from 'react';
 
 import { HasObjectId } from '@growi/core';
-import { PagePathLabel } from '@growi/ui';
+import { PagePathLabel } from '@growi/ui/dist/components/PagePath/PagePathLabel';
 import { useRouter } from 'next/router';
 
 import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';

+ 1 - 1
apps/app/src/components/Navbar/AuthorInfo.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { pagePathUtils } from '@growi/core';
 import type { IUser } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format } from 'date-fns';
 import Link from 'next/link';
 

+ 1 - 1
apps/app/src/components/Navbar/PersonalDropdown.jsx

@@ -1,6 +1,6 @@
 import React, { useRef } from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import { useRipple } from 'react-use-ripple';

+ 1 - 12
apps/app/src/components/Page/PageView.tsx

@@ -14,7 +14,6 @@ import {
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import { useIsMobile } from '~/stores/ui';
-import { registerGrowiFacade } from '~/utils/growi-facade';
 
 
 import type { CommentsProps } from '../Comments';
@@ -67,22 +66,12 @@ export const PageView = (props: Props): JSX.Element => {
   const { data: isMobile } = useIsMobile();
 
   const { data: pageBySWR } = useSWRxCurrentPage();
-  const { data: viewOptions, mutate: mutateRendererOptions } = useViewOptions();
+  const { data: viewOptions } = useViewOptions();
 
   const page = pageBySWR ?? initialPage;
   const isNotFound = isNotFoundMeta || page?.revision == null;
   const isUsersHomePagePath = isUsersHomePage(pagePath);
 
-  // register to facade
-  useEffect(() => {
-    registerGrowiFacade({
-      markdownRenderer: {
-        optionsMutators: {
-          viewOptionsMutator: mutateRendererOptions,
-        },
-      },
-    });
-  }, [mutateRendererOptions]);
 
   // ***************************  Auto Scroll  ***************************
   useEffect(() => {

+ 1 - 1
apps/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format } from 'date-fns';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';

+ 1 - 1
apps/app/src/components/PageAttachment/DeleteAttachmentModal.tsx

@@ -2,7 +2,7 @@
 import React, { useCallback } from 'react';
 
 import { HasObjectId, IAttachment } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import {
   Button,
   Modal, ModalHeader, ModalBody, ModalFooter,

+ 1 - 1
apps/app/src/components/PageAttachment/PageAttachmentList.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { HasObjectId, IAttachment } from '@growi/core';
-import { Attachment } from '@growi/ui';
+import { Attachment } from '@growi/ui/dist/components/Attachment';
 import { useTranslation } from 'next-i18next';
 
 type Props = {

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

@@ -7,7 +7,6 @@ import { Button } from 'reactstrap';
 
 import { toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
-import { RendererOptions } from '~/services/renderer/renderer';
 import { useCommentForCurrentPageOptions } from '~/stores/renderer';
 
 import { ICommentHasId, ICommentHasIdList } from '../interfaces/comment';
@@ -20,6 +19,7 @@ import { DeleteCommentModal } from './PageComment/DeleteCommentModal';
 import { ReplyComments } from './PageComment/ReplyComments';
 
 import styles from './PageComment.module.scss';
+import { RendererOptions } from '~/interfaces/renderer-options';
 
 export const ROOT_ELEM_ID = 'page-comments' as const;
 

+ 1 - 1
apps/app/src/components/PageComment/Comment.tsx

@@ -1,7 +1,7 @@
 import React, { useEffect, useMemo, useState } from 'react';
 
 import { IUser, pathUtils } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format, parseISO } from 'date-fns';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';

+ 1 - 1
apps/app/src/components/PageComment/CommentEditor.tsx

@@ -2,7 +2,7 @@ import React, {
   useCallback, useState, useRef, useEffect,
 } from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import dynamic from 'next/dynamic';
 import {
   Button, TabContent, TabPane,

+ 1 - 1
apps/app/src/components/PageComment/DeleteCommentModal.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format } from 'date-fns';
 import {
   Button, Modal, ModalHeader, ModalBody, ModalFooter,

+ 1 - 14
apps/app/src/components/PageEditor.tsx

@@ -47,7 +47,6 @@ import {
   useEditorMode, useSelectedGrant,
 } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
-import { registerGrowiFacade } from '~/utils/growi-facade';
 import loggerFactory from '~/utils/logger';
 
 
@@ -103,7 +102,7 @@ const PageEditor = React.memo((): JSX.Element => {
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
-  const { data: rendererOptions, mutate: mutateRendererOptions } = usePreviewOptions();
+  const { data: rendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const saveOrUpdate = useSaveOrUpdate();
 
@@ -179,18 +178,6 @@ const PageEditor = React.memo((): JSX.Element => {
     return optionsToSave;
   }, [grantData, isSlackEnabled, pageTags]);
 
-  // register to facade
-  useEffect(() => {
-    // for markdownRenderer
-    registerGrowiFacade({
-      markdownRenderer: {
-        optionsMutators: {
-          previewOptionsMutator: mutateRendererOptions,
-        },
-      },
-    });
-  }, [mutateRendererOptions]);
-
   const setMarkdownWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string, isClean: boolean) => {
     markdownToSave.current = value;
     setMarkdownToPreview(value);

+ 1 - 1
apps/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -2,7 +2,7 @@ import React, {
   useState, useEffect, useRef, useMemo, useCallback,
 } from 'react';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import CodeMirror from 'codemirror/lib/codemirror';
 import { format, parseISO } from 'date-fns';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/components/PageHistory/Revision.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { IRevisionHasId, pathUtils } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import urljoin from 'url-join';

+ 1 - 1
apps/app/src/components/PageList/PageList.module.scss

@@ -1 +1 @@
-@use '~/styles/molecules/page_list';
+@use '@growi/ui/src/styles/molecules/page_list';

+ 2 - 1
apps/app/src/components/PageList/PageListItemL.tsx

@@ -5,7 +5,8 @@ import React, {
 
 
 import { DevidedPagePath, pathUtils } from '@growi/core';
-import { UserPicture, PageListMeta } from '@growi/ui';
+import { PageListMeta } from '@growi/ui/dist/components/PagePath/PageListMeta';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { format } from 'date-fns';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';

+ 3 - 1
apps/app/src/components/PageList/PageListItemS.tsx

@@ -1,6 +1,8 @@
 import React from 'react';
 
-import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
+import { PageListMeta } from '@growi/ui/dist/components/PagePath/PageListMeta';
+import { PagePathLabel } from '@growi/ui/dist/components/PagePath/PagePathLabel';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 
 import { IPageHasId } from '~/interfaces/page';
 

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

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
 import type { PresentationProps } from '@growi/presentation';
-import { useFullScreen } from '@growi/ui';
+import { useFullScreen } from '@growi/ui/dist/utils';
 import dynamic from 'next/dynamic';
 import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown';
 import {

+ 1 - 1
apps/app/src/components/SearchPage/SearchPageBase.module.scss

@@ -1 +1 @@
-@use '~/styles/molecules/page_list';
+@use '@growi/ui/src/styles/molecules/page_list';

+ 3 - 1
apps/app/src/components/SearchTypeahead.tsx

@@ -3,7 +3,9 @@ import React, {
   KeyboardEvent, useCallback, useRef, useState, MouseEvent, useEffect,
 } from 'react';
 
-import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
+import { PageListMeta } from '@growi/ui/dist/components/PagePath/PageListMeta';
+import { PagePathLabel } from '@growi/ui/dist/components/PagePath/PagePathLabel';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 
 import { IFocusable } from '~/client/interfaces/focusable';

+ 1 - 13
apps/app/src/components/ShareLink/ShareLinkPageView.tsx

@@ -8,7 +8,6 @@ import type { IShareLinkHasId } from '~/interfaces/share-link';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
 import { useIsNotFound } from '~/stores/context';
 import { useViewOptions } from '~/stores/renderer';
-import { registerGrowiFacade } from '~/utils/growi-facade';
 import loggerFactory from '~/utils/logger';
 
 import { MainPane } from '../Layout/MainPane';
@@ -42,18 +41,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
   const { data: isNotFoundMeta } = useIsNotFound();
 
-  const { data: viewOptions, mutate: mutateRendererOptions } = useViewOptions();
-
-  // register to facade
-  useEffect(() => {
-    registerGrowiFacade({
-      markdownRenderer: {
-        optionsMutators: {
-          viewOptionsMutator: mutateRendererOptions,
-        },
-      },
-    });
-  }, [mutateRendererOptions]);
+  const { data: viewOptions } = useViewOptions();
 
   const isNotFound = isNotFoundMeta || page == null || shareLink == null;
 

+ 2 - 1
apps/app/src/components/Sidebar/RecentChanges.tsx

@@ -3,7 +3,8 @@ import React, {
 } from 'react';
 
 import { DevidedPagePath, isPopulated } from '@growi/core';
-import { UserPicture, FootstampIcon } from '@growi/ui';
+import { FootstampIcon } from '@growi/ui/dist/components/FootstampIcon';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 

+ 1 - 1
apps/app/src/components/User/SeenUserInfo.tsx

@@ -1,6 +1,6 @@
 import React, { FC, useState } from 'react';
 
-import { FootstampIcon } from '@growi/ui';
+import { FootstampIcon } from '@growi/ui/dist/components/FootstampIcon';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
 

+ 1 - 1
apps/app/src/components/User/UserInfo.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import type { IUserHasId } from '@growi/core';
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 
 import styles from './UserInfo.module.scss';
 

+ 1 - 1
apps/app/src/components/User/UserPictureList.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { UserPicture } from '@growi/ui';
+import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 
 export default class UserPictureList extends React.Component {
 

+ 1 - 1
apps/app/src/components/UsersHomePageFooter.module.scss

@@ -1,4 +1,4 @@
-@use '~/styles/molecules/page_list';
+@use '@growi/ui/src/styles/molecules/page_list';
 
 .user-page-footer :global {
   .grw-user-page-list-m {

+ 20 - 0
apps/app/src/interfaces/renderer-options.ts

@@ -0,0 +1,20 @@
+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 { PluggableList } from 'unified';
+
+export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehypePlugins' | 'components' | 'children'> & {
+  remarkPlugins: PluggableList,
+  rehypePlugins: PluggableList,
+  components?:
+    | Partial<
+        Omit<NormalComponents, keyof SpecialComponents>
+        & SpecialComponents
+        & {
+          [elem: string]: ComponentType<any>,
+        }
+      >
+    | undefined
+};

+ 1 - 1
apps/app/src/server/crowi/index.js

@@ -3,7 +3,7 @@ import http from 'http';
 import path from 'path';
 
 import { createTerminus } from '@godaddy/terminus';
-import lsxRoutes from '@growi/remark-lsx/server/routes';
+import lsxRoutes from '@growi/remark-lsx/dist/server';
 import mongoose from 'mongoose';
 import next from 'next';
 

+ 7 - 270
apps/app/src/services/renderer/renderer.tsx

@@ -1,46 +1,28 @@
-// allow only types to import from react
-import type { ComponentType } from 'react';
-
-import { isClient } from '@growi/core';
-import * as drawioPlugin from '@growi/remark-drawio';
 import growiDirective from '@growi/remark-growi-directive';
-import { Lsx, LsxImmutable } from '@growi/remark-lsx/components';
-import * as lsxGrowiPlugin from '@growi/remark-lsx/services/renderer';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
-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 katex from 'rehype-katex';
 import raw from 'rehype-raw';
 import sanitize, { defaultSchema as rehypeSanitizeDefaultSchema } from 'rehype-sanitize';
 import slug from 'rehype-slug';
-import type { HtmlElementNode } from 'rehype-toc';
 import breaks from 'remark-breaks';
 import emoji from 'remark-emoji';
 import gfm from 'remark-gfm';
 import math from 'remark-math';
 import deepmerge from 'ts-deepmerge';
-import type { PluggableList, Pluggable, PluginTuple } from 'unified';
+import type { Pluggable, PluginTuple } from 'unified';
 
 
 import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
-import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents/DrawioViewerWithEditButton';
-import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import { Table } from '~/components/ReactMarkdownComponents/Table';
-import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import { RehypeSanitizeOption } from '~/interfaces/rehype';
+import type { RendererOptions } from '~/interfaces/renderer-options';
 import type { RendererConfig } from '~/interfaces/services/renderer';
-import { registerGrowiFacade } from '~/utils/growi-facade';
 import loggerFactory from '~/utils/logger';
 
 import * as addClass from './rehype-plugins/add-class';
-import * as addLineNumberAttribute from './rehype-plugins/add-line-number-attribute';
-import * as keywordHighlighter from './rehype-plugins/keyword-highlighter';
 import { relativeLinks } from './rehype-plugins/relative-links';
 import { relativeLinksByPukiwikiLikeLinker } from './rehype-plugins/relative-links-by-pukiwiki-like-linker';
-import * as toc from './rehype-plugins/relocate-toc';
-import * as plantuml from './remark-plugins/plantuml';
 import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
 import * as table from './remark-plugins/table';
 import * as xsvToTable from './remark-plugins/xsv-to-table';
@@ -49,23 +31,10 @@ import * as xsvToTable from './remark-plugins/xsv-to-table';
 // import BlockdiagConfigurer from './markdown-it/blockdiag';
 
 
-const logger = loggerFactory('growi:util:GrowiRenderer');
+const logger = loggerFactory('growi:services:renderer');
 
 
 type SanitizePlugin = PluginTuple<[SanitizeOption]>;
-export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehypePlugins' | 'components' | 'children'> & {
-  remarkPlugins: PluggableList,
-  rehypePlugins: PluggableList,
-  components?:
-    | Partial<
-        Omit<NormalComponents, keyof SpecialComponents>
-        & SpecialComponents
-        & {
-          [elem: string]: ComponentType<any>,
-        }
-      >
-    | undefined
-};
 
 const baseSanitizeSchema = {
   tagNames: ['iframe', 'section'],
@@ -77,7 +46,7 @@ const baseSanitizeSchema = {
   },
 };
 
-const commonSanitizeOption: SanitizeOption = deepmerge(
+export const commonSanitizeOption: SanitizeOption = deepmerge(
   rehypeSanitizeDefaultSchema,
   baseSanitizeSchema,
   {
@@ -87,7 +56,7 @@ const commonSanitizeOption: SanitizeOption = deepmerge(
 
 let isInjectedCustomSanitaizeOption = false;
 
-const injectCustomSanitizeOption = (config: RendererConfig) => {
+export const injectCustomSanitizeOption = (config: RendererConfig): void => {
   if (!isInjectedCustomSanitaizeOption && config.isEnabledXssPrevention && config.xssOption === RehypeSanitizeOption.CUSTOM) {
     commonSanitizeOption.tagNames = baseSanitizeSchema.tagNames.concat(config.tagWhiteList ?? []);
     commonSanitizeOption.attributes = deepmerge(baseSanitizeSchema.attributes, config.attrWhiteList ?? {});
@@ -114,7 +83,7 @@ const hasSanitizePlugin = (options: RendererOptions, shouldBeTheLastItem: boolea
     : rehypePlugins.some(rehypePlugin => isSanitizePlugin(rehypePlugin));
 };
 
-const verifySanitizePlugin = (options: RendererOptions, shouldBeTheLastItem = true): void => {
+export const verifySanitizePlugin = (options: RendererOptions, shouldBeTheLastItem = true): void => {
   if (hasSanitizePlugin(options, shouldBeTheLastItem)) {
     return;
   }
@@ -122,7 +91,7 @@ const verifySanitizePlugin = (options: RendererOptions, shouldBeTheLastItem = tr
   throw new Error('The specified options does not have sanitize plugin in \'rehypePlugins\'');
 };
 
-const generateCommonOptions = (pagePath: string|undefined): RendererOptions => {
+export const generateCommonOptions = (pagePath: string|undefined): RendererOptions => {
   return {
     remarkPlugins: [
       gfm,
@@ -149,170 +118,6 @@ const generateCommonOptions = (pagePath: string|undefined): RendererOptions => {
   };
 };
 
-export const generateViewOptions = (
-    pagePath: string,
-    config: RendererConfig,
-    storeTocNode: (toc: HtmlElementNode) => void,
-): RendererOptions => {
-
-  const options = generateCommonOptions(pagePath);
-
-  const { remarkPlugins, rehypePlugins, components } = options;
-
-  // add remark plugins
-  remarkPlugins.push(
-    math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
-    drawioPlugin.remarkPlugin,
-    xsvToTable.remarkPlugin,
-    lsxGrowiPlugin.remarkPlugin,
-  );
-  if (config.isEnabledLinebreaks) {
-    remarkPlugins.push(breaks);
-  }
-
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    injectCustomSanitizeOption(config);
-  }
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      commonSanitizeOption,
-      drawioPlugin.sanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-    )]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    slug,
-    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
-    rehypeSanitizePlugin,
-    katex,
-    [toc.rehypePluginStore, { storeTocNode }],
-  );
-
-  // add components
-  if (components != null) {
-    components.h1 = Header;
-    components.h2 = Header;
-    components.h3 = Header;
-    components.lsx = Lsx;
-    components.drawio = DrawioViewerWithEditButton;
-    components.table = TableWithEditButton;
-  }
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options, false);
-  }
-  return options;
-};
-
-export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
-
-  const options = generateCommonOptions(undefined);
-
-  const { rehypePlugins } = options;
-
-  // add remark plugins
-  // remarkPlugins.push();
-
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    injectCustomSanitizeOption(config);
-  }
-
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      commonSanitizeOption,
-    )]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    [toc.rehypePluginRestore, { tocNode }],
-    rehypeSanitizePlugin,
-  );
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options);
-  }
-
-  return options;
-};
-
-export const generateSimpleViewOptions = (
-    config: RendererConfig,
-    pagePath: string,
-    highlightKeywords?: string | string[],
-    overrideIsEnabledLinebreaks?: boolean,
-): RendererOptions => {
-  const options = generateCommonOptions(pagePath);
-
-  const { remarkPlugins, rehypePlugins, components } = options;
-
-  // add remark plugins
-  remarkPlugins.push(
-    math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
-    drawioPlugin.remarkPlugin,
-    xsvToTable.remarkPlugin,
-    lsxGrowiPlugin.remarkPlugin,
-    table.remarkPlugin,
-  );
-
-  const isEnabledLinebreaks = overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
-
-  if (isEnabledLinebreaks) {
-    remarkPlugins.push(breaks);
-  }
-
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    injectCustomSanitizeOption(config);
-  }
-
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      commonSanitizeOption,
-      drawioPlugin.sanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-    )]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
-    [keywordHighlighter.rehypePlugin, { keywords: highlightKeywords }],
-    rehypeSanitizePlugin,
-    katex,
-  );
-
-  // add components
-  if (components != null) {
-    components.lsx = LsxImmutable;
-    components.drawio = drawioPlugin.DrawioViewer;
-    components.table = Table;
-  }
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options, false);
-  }
-  return options;
-};
-
-export const generatePresentationViewOptions = (
-    config: RendererConfig,
-    pagePath: string,
-): RendererOptions => {
-  // based on simple view options
-  const options = generateSimpleViewOptions(config, pagePath);
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options, false);
-  }
-  return options;
-};
 
 export const generateSSRViewOptions = (
     config: RendererConfig,
@@ -326,7 +131,6 @@ export const generateSSRViewOptions = (
   remarkPlugins.push(
     math,
     xsvToTable.remarkPlugin,
-    lsxGrowiPlugin.remarkPlugin,
     table.remarkPlugin,
   );
 
@@ -343,21 +147,18 @@ export const generateSSRViewOptions = (
   const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
     ? [sanitize, deepmerge(
       commonSanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
     )]
     : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
     slug,
-    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
     rehypeSanitizePlugin,
     katex,
   );
 
   // add components
   if (components != null) {
-    components.lsx = LsxImmutable;
     components.table = Table;
   }
 
@@ -366,67 +167,3 @@ export const generateSSRViewOptions = (
   }
   return options;
 };
-
-export const generatePreviewOptions = (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 }],
-    drawioPlugin.remarkPlugin,
-    xsvToTable.remarkPlugin,
-    lsxGrowiPlugin.remarkPlugin,
-    table.remarkPlugin,
-  );
-  if (config.isEnabledLinebreaks) {
-    remarkPlugins.push(breaks);
-  }
-
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    injectCustomSanitizeOption(config);
-  }
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      commonSanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-      drawioPlugin.sanitizeOption,
-      addLineNumberAttribute.sanitizeOption,
-    )]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
-    addLineNumberAttribute.rehypePlugin,
-    rehypeSanitizePlugin,
-    katex,
-  );
-
-  // add components
-  if (components != null) {
-    components.lsx = LsxImmutable;
-    components.drawio = drawioPlugin.DrawioViewer;
-    components.table = Table;
-  }
-
-  if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options, false);
-  }
-  return options;
-};
-
-// register to facade
-if (isClient()) {
-  registerGrowiFacade({
-    markdownRenderer: {
-      optionsGenerators: {
-        generateViewOptions,
-        generatePreviewOptions,
-      },
-    },
-  });
-}

+ 40 - 33
apps/app/src/stores/renderer.tsx

@@ -3,11 +3,7 @@ import { useCallback } from 'react';
 import type { HtmlElementNode } from 'rehype-toc';
 import useSWR, { type SWRResponse } from 'swr';
 
-import {
-  type RendererOptions,
-  generateSimpleViewOptions, generatePreviewOptions,
-  generateViewOptions, generateTocOptions, generatePresentationViewOptions,
-} from '~/services/renderer/renderer';
+import type { RendererOptions } from '~/interfaces/renderer-options';
 import { getGrowiFacade } from '~/utils/growi-facade';
 
 
@@ -33,14 +29,17 @@ export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
     isAllDataValid
       ? ['viewOptions', currentPagePath, rendererConfig]
       : null,
-    ([, currentPagePath, rendererConfig]) => {
-      // determine options generator
-      const optionsGenerator = getGrowiFacade().markdownRenderer?.optionsGenerators?.customGenerateViewOptions ?? generateViewOptions;
-      return optionsGenerator(currentPagePath, rendererConfig, storeTocNodeHandler);
+    async([, currentPagePath, rendererConfig]) => {
+      const customGenerater = getGrowiFacade().markdownRenderer?.optionsGenerators?.customGenerateViewOptions;
+      if (customGenerater != null) {
+        return customGenerater(currentPagePath, rendererConfig, storeTocNodeHandler);
+      }
+
+      const { generateViewOptions } = await import('~/client/services/renderer/renderer');
+      return generateViewOptions(currentPagePath, rendererConfig, storeTocNodeHandler);
     },
     {
       keepPreviousData: true,
-      fallbackData: isAllDataValid ? generateViewOptions(currentPagePath, rendererConfig, storeTocNodeHandler) : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -58,10 +57,12 @@ export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
     isAllDataValid
       ? ['tocOptions', currentPagePath, tocNode, rendererConfig]
       : null,
-    ([, , tocNode, rendererConfig]) => generateTocOptions(rendererConfig, tocNode),
+    async([, , tocNode, rendererConfig]) => {
+      const { generateTocOptions } = await import('~/client/services/renderer/renderer');
+      return generateTocOptions(rendererConfig, tocNode);
+    },
     {
       keepPreviousData: true,
-      fallbackData: isAllDataValid ? generateTocOptions(rendererConfig, tocNode) : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -78,14 +79,17 @@ export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
     isAllDataValid
       ? ['previewOptions', rendererConfig, currentPagePath]
       : null,
-    ([, rendererConfig, pagePath]) => {
-      // determine options generator
-      const optionsGenerator = getGrowiFacade().markdownRenderer?.optionsGenerators?.customGeneratePreviewOptions ?? generatePreviewOptions;
-      return optionsGenerator(rendererConfig, pagePath);
+    async([, rendererConfig, pagePath]) => {
+      const customGenerater = getGrowiFacade().markdownRenderer?.optionsGenerators?.customGeneratePreviewOptions;
+      if (customGenerater != null) {
+        return customGenerater(rendererConfig, pagePath);
+      }
+
+      const { generatePreviewOptions } = await import('~/client/services/renderer/renderer');
+      generatePreviewOptions(rendererConfig, pagePath);
     },
     {
       keepPreviousData: true,
-      fallbackData: isAllDataValid ? generatePreviewOptions(rendererConfig, currentPagePath) : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -102,20 +106,17 @@ export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions,
     isAllDataValid
       ? ['commentPreviewOptions', rendererConfig, currentPagePath]
       : null,
-    ([, rendererConfig, currentPagePath]) => generateSimpleViewOptions(
-      rendererConfig,
-      currentPagePath,
-      undefined,
-      rendererConfig.isEnabledLinebreaksInComments,
-    ),
-    {
-      keepPreviousData: true,
-      fallbackData: isAllDataValid ? generateSimpleViewOptions(
+    async([, rendererConfig, currentPagePath]) => {
+      const { generateSimpleViewOptions } = await import('~/client/services/renderer/renderer');
+      return generateSimpleViewOptions(
         rendererConfig,
         currentPagePath,
         undefined,
         rendererConfig.isEnabledLinebreaksInComments,
-      ) : undefined,
+      );
+    },
+    {
+      keepPreviousData: true,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -132,9 +133,11 @@ export const useSelectedPagePreviewOptions = (pagePath: string, highlightKeyword
     isAllDataValid
       ? ['selectedPagePreviewOptions', rendererConfig, pagePath, highlightKeywords]
       : null,
-    ([, rendererConfig, pagePath, highlightKeywords]) => generateSimpleViewOptions(rendererConfig, pagePath, highlightKeywords),
+    async([, rendererConfig, pagePath, highlightKeywords]) => {
+      const { generateSimpleViewOptions } = await import('~/client/services/renderer/renderer');
+      return generateSimpleViewOptions(rendererConfig, pagePath, highlightKeywords);
+    },
     {
-      fallbackData: isAllDataValid ? generateSimpleViewOptions(rendererConfig, pagePath, highlightKeywords) : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -153,10 +156,12 @@ export const useCustomSidebarOptions = (): SWRResponse<RendererOptions, Error> =
     isAllDataValid
       ? ['customSidebarOptions', rendererConfig]
       : null,
-    ([, rendererConfig]) => generateSimpleViewOptions(rendererConfig, '/'),
+    async([, rendererConfig]) => {
+      const { generateSimpleViewOptions } = await import('~/client/services/renderer/renderer');
+      return generateSimpleViewOptions(rendererConfig, '/');
+    },
     {
       keepPreviousData: true,
-      fallbackData: isAllDataValid ? generateSimpleViewOptions(rendererConfig, '/') : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },
@@ -173,9 +178,11 @@ export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error
     isAllDataValid
       ? ['presentationViewOptions', currentPagePath, rendererConfig]
       : null,
-    ([, currentPagePath, rendererConfig]) => generatePresentationViewOptions(rendererConfig, currentPagePath),
+    async([, currentPagePath, rendererConfig]) => {
+      const { generatePresentationViewOptions } = await import('~/client/services/renderer/renderer');
+      return generatePresentationViewOptions(rendererConfig, currentPagePath);
+    },
     {
-      fallbackData: isAllDataValid ? generatePresentationViewOptions(rendererConfig, currentPagePath) : undefined,
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     },

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

@@ -4,7 +4,8 @@ import {
   isClient, isServer, pagePathUtils, PageGrant, withUtils,
 } from '@growi/core';
 import type { Nullable, SWRResponseWithUtils } from '@growi/core';
-import { Breakpoint, addBreakpointListener, cleanupBreakpointListener } from '@growi/ui';
+import { Breakpoint } from '@growi/ui/dist/interfaces/breakpoints';
+import { addBreakpointListener, cleanupBreakpointListener } from '@growi/ui/dist/utils';
 import type { HtmlElementNode } from 'rehype-toc';
 import type SimpleBar from 'simplebar-react';
 import {

+ 2 - 0
apps/app/src/styles/style-app.scss

@@ -16,6 +16,8 @@
 // KaTeX
 @import '~katex/dist/katex.min';
 
+@import '~@growi/remark-lsx/dist/client/style.css';
+
 // icons
 
 // DO NOT CHANGE THER OERDER OF font-awesome AND simple-line-icons.

+ 3 - 3
apps/app/tsconfig.build.client.json

@@ -16,10 +16,10 @@
       // "@growi/preset-themes": ["../../packages/preset-themes/src"],
       "@growi/remark-drawio": ["../../packages/remark-drawio/src"],
       "@growi/remark-growi-directive": ["../../packages/remark-growi-directive/src"],
-      "@growi/remark-lsx": ["../../packages/remark-lsx/src"],
-      "@growi/remark-lsx/*": ["../../packages/remark-lsx/src/*"],
+      // "@growi/remark-lsx": ["../../packages/remark-lsx/src"],
+      // "@growi/remark-lsx/*": ["../../packages/remark-lsx/src/*"],
       // "@growi/slack": ["../../packages/slack/src"],
-      "@growi/ui": ["../../packages/ui/src"],
+      // "@growi/ui": ["../../packages/ui/src"],
 
       "debug": ["./src/server/utils/logger/alias-for-debug"]
     }

+ 1 - 1
apps/app/tsconfig.build.server.json

@@ -13,7 +13,7 @@
     "paths": {
       "~/*": ["./src/*"],
       "^/*": ["./*"],
-      "@growi/remark-lsx/*": ["../../packages/remark-lsx/dist/*"],
+      // "@growi/remark-lsx/*": ["../../packages/remark-lsx/dist/*"],
       "debug": ["./src/utils/logger/alias-for-debug"]
     }
   },

+ 3 - 3
apps/app/tsconfig.json

@@ -16,10 +16,10 @@
       // "@growi/preset-themes": ["../../packages/preset-themes/src"],
       "@growi/remark-drawio": ["../../packages/remark-drawio/src"],
       "@growi/remark-growi-directive": ["../../packages/remark-growi-directive/src"],
-      "@growi/remark-lsx": ["../../packages/remark-lsx/src"],
-      "@growi/remark-lsx/*": ["../../packages/remark-lsx/src/*"],
+      // "@growi/remark-lsx": ["../../packages/remark-lsx/src"],
+      // "@growi/remark-lsx/*": ["../../packages/remark-lsx/src/*"],
       // "@growi/slack": ["../../packages/slack/src"],
-      "@growi/ui": ["../../packages/ui/src"],
+      // "@growi/ui": ["../../packages/ui/src"],
 
       "debug": ["./src/server/utils/logger/alias-for-debug"]
     }

+ 1 - 1
packages-obsolete/plugin-attachment-refs/src/client/js/components/AttachmentList.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { Attachment } from '@growi/ui';
+import { Attachment } from '@growi/ui/dist/components/Attachment';
 import axios from 'axios'; // import axios from growi dependencies
 import PropTypes from 'prop-types';
 

+ 1 - 1
packages/core/package.json

@@ -17,7 +17,7 @@
     "build": "vite build",
     "clean": "npx -y shx rm -rf dist types",
     "dev": "vite build --mode dev",
-    "watch": "yarn dev -w'",
+    "watch": "yarn dev -w",
     "lint:js": "eslint **/*.{js,ts}",
     "lint": "npm-run-all -p lint:*",
     "test": "jest --verbose"

+ 2 - 19
packages/core/src/index.ts

@@ -1,18 +1,3 @@
-import * as _envUtils from './utils/env-utils';
-
-// export utils by *.js
-export const envUtils = _envUtils;
-
-// export utils with namespace
-export * as customTagUtils from './plugin/util/custom-tag-utils';
-export * as templateChecker from './utils/template-checker';
-export * as objectIdUtils from './utils/objectid-utils';
-export * as pagePathUtils from './utils/page-path-utils';
-export * as pathUtils from './utils/path-utils';
-export * as pageUtils from './utils/page-utils';
-
-// export all
-export * from './plugin/interfaces/option-parser';
 export * from './interfaces/attachment';
 export * from './interfaces/color-scheme';
 export * from './interfaces/common';
@@ -29,8 +14,6 @@ export * from './interfaces/user';
 export * from './interfaces/vite';
 export * from './models/devided-page-path';
 export * from './models/vo/error-apiv3';
+export * from './plugin';
 export * from './service/localstorage-manager';
-export * from './utils/basic-interceptor';
-export * from './utils/browser-utils';
-export * from './utils/growi-theme-metadata';
-export * from './utils/with-utils';
+export * from './utils';

+ 2 - 0
packages/core/src/plugin/index.ts

@@ -0,0 +1,2 @@
+export * from './interfaces/option-parser';
+export * from './util/option-parser';

+ 0 - 18
packages/core/src/plugin/model/tag-context.ts

@@ -1,18 +0,0 @@
-/**
- * Context class for custom-tag-utils#findTagAndReplace
- */
-export class TagContext {
-
-  tagExpression: string | null;
-
-  method: string | null;
-
-  args: any;
-
-  constructor(initArgs: any = {}) {
-    this.tagExpression = initArgs.tagExpression || null;
-    this.method = initArgs.method || null;
-    this.args = initArgs.args || null;
-  }
-
-}

+ 0 - 57
packages/core/src/plugin/util/args-parser.js

@@ -1,57 +0,0 @@
-/**
- * Arguments parser for custom tag
- */
-export class ArgsParser {
-
-  /**
-   * @typedef ParseArgsResult
-   * @property {string} firstArgsKey - key of the first argument
-   * @property {string|boolean} firstArgsValue - value of the first argument
-   * @property {object} options - key of the first argument
-   */
-
-  /**
-   * parse plugin argument strings
-   *
-   * @static
-   * @param {string} str
-   * @returns {ParseArgsResult}
-   */
-  static parse(str) {
-    let firstArgsKey = null;
-    let firstArgsValue = null;
-    const options = {};
-
-    if (str != null && str.length > 0) {
-      const splittedArgs = str.split(',');
-
-      splittedArgs.forEach((rawArg, index) => {
-        const arg = rawArg.trim();
-
-        // parse string like 'key1=value1, key2=value2, ...'
-        // see https://regex101.com/r/pYHcOM/1
-        const match = arg.match(/([^=]+)=?(.+)?/);
-
-        if (match == null) {
-          return;
-        }
-
-        const key = match[1];
-        const value = match[2] || true;
-        options[key] = value;
-
-        if (index === 0) {
-          firstArgsKey = key;
-          firstArgsValue = value;
-        }
-      });
-    }
-
-    return {
-      firstArgsKey,
-      firstArgsValue,
-      options,
-    };
-  }
-
-}

+ 0 - 5
packages/core/src/plugin/util/custom-tag-utils.ts

@@ -1,5 +0,0 @@
-export * from '../model/tag-context';
-
-export * from './args-parser';
-
-export * from './option-parser';

+ 1 - 1
packages/core/src/plugin/util/option-parser.ts

@@ -1,4 +1,4 @@
-import { ParseRangeResult } from '../interfaces/option-parser';
+import type { ParseRangeResult } from '../interfaces/option-parser';
 
 /**
  * Options parser for custom tag

+ 16 - 0
packages/core/src/utils/index.ts

@@ -0,0 +1,16 @@
+import * as _envUtils from './env-utils';
+
+// export utils by *.js
+export const envUtils = _envUtils;
+
+// export utils with namespace
+export * as templateChecker from './template-checker';
+export * as objectIdUtils from './objectid-utils';
+export * as pagePathUtils from './page-path-utils';
+export * as pathUtils from './path-utils';
+export * as pageUtils from './page-utils';
+
+export * from './basic-interceptor';
+export * from './browser-utils';
+export * from './growi-theme-metadata';
+export * from './with-utils';

+ 0 - 42
packages/core/test/plugin/util/args-parser.test.js

@@ -1,42 +0,0 @@
-import { ArgsParser } from '~/plugin/util/args-parser';
-
-describe('args-parser', () => {
-
-  test('.parse(null) returns default object', () => {
-    const result = ArgsParser.parse(null);
-
-    expect(result.firstArgsKey).toBeNull();
-    expect(result.firstArgsValue).toBeNull();
-    expect(result.options).toEqual({});
-  });
-
-  test('.parse(\'prefix=/Level1\') returns a valid results', () => {
-    const result = ArgsParser.parse('prefix=/Level1');
-
-    expect(result.firstArgsKey).toBe('prefix');
-    expect(result.firstArgsValue).toBe('/Level1');
-  });
-
-  test('.parse(\'key, opt1=1, opt2=2\') returns a valid results', () => {
-    const result = ArgsParser.parse('key, opt1=1, opt2=2');
-
-    expect(result.firstArgsKey).toBe('key');
-    expect(result.firstArgsValue).toBeTruthy();
-
-    expect(Object.keys(result.options).length).toBe(3);
-    expect(result.options.key).toBeTruthy();
-    expect(result.options.opt1).toBe('1');
-    expect(result.options.opt2).toBe('2');
-  });
-
-  test('.parse(\'key, \') returns a valid results', () => {
-    const result = ArgsParser.parse('key, ');
-
-    expect(result.firstArgsKey).toBe('key');
-    expect(result.firstArgsValue).toBeTruthy();
-
-    expect(Object.keys(result.options).length).toBe(1);
-    expect(result.options.key).toBeTruthy();
-  });
-
-});

+ 0 - 74
packages/core/test/plugin/util/custom-tag-utils.test.js

@@ -1,74 +0,0 @@
-import rewire from 'rewire';
-
-import * as customTagUtils from '~/plugin/util/custom-tag-utils';
-
-// leave it commented out for rewire example -- 2022.08.18 Yuki Takei
-// const rewiredCustomTagUtils = rewire('../../../plugin/util/custom-tag-utils');
-
-describe('customTagUtils', () => {
-
-  test('exports TagContext', () => {
-    expect(customTagUtils.TagContext).not.toBeNull();
-    expect(typeof customTagUtils.TagContext).toBe('function');
-  });
-
-  test('exports ArgsParser', () => {
-    expect(customTagUtils.ArgsParser).not.toBeNull();
-    expect(typeof customTagUtils.ArgsParser).toBe('function');
-  });
-
-  test('exports OptionParser', () => {
-    expect(customTagUtils.OptionParser).not.toBeNull();
-    expect(typeof customTagUtils.OptionParser).toBe('function');
-  });
-
-  // leave it commented out for rewire example -- 2022.08.18 Yuki Takei
-  // test('.createRandomStr(10) returns random string', () => {
-  //   // get private resource
-  //   const createRandomStr = rewiredCustomTagUtils.__get__('createRandomStr');
-  //   expect(createRandomStr(10)).toMatch(/^[a-z0-9]{10}$/);
-  // });
-
-  // test('.findTagAndReplace() returns default object when tagPattern is null', () => {
-  //   const htmlMock = jest.fn();
-  //   htmlMock.replace = jest.fn();
-
-  //   const result = customTagUtils.findTagAndReplace(null, '');
-
-  //   expect(result).toEqual({ html: '', tagContextMap: {} });
-  //   expect(htmlMock.replace).not.toHaveBeenCalled();
-  // });
-
-  // test('.findTagAndReplace() returns default object when html is null', () => {
-  //   const tagPatternMock = jest.fn();
-  //   tagPatternMock.source = jest.fn();
-
-  //   const result = customTagUtils.findTagAndReplace(tagPatternMock, null);
-
-  //   expect(result).toEqual({ html: null, tagContextMap: {} });
-  //   expect(tagPatternMock.source).not.toHaveBeenCalled();
-  // });
-
-  // test('.findTagAndReplace() works correctly', () => {
-  //   // setup mocks for private function
-  //   rewiredCustomTagUtils.__set__('createRandomStr', (length) => {
-  //     return 'dummyDomId';
-  //   });
-
-  //   const tagPattern = /ls|lsx/;
-  //   const html = '<section><h1>header</h1>\n$ls(/)</section>';
-
-  //   const result = rewiredCustomTagUtils.findTagAndReplace(tagPattern, html);
-
-  //   expect(result.html).toMatch(/<section><h1>header<\/h1>\n<div id="ls-dummyDomId"><\/div>/);
-  //   expect(result.tagContextMap).toEqual({
-  //     'ls-dummyDomId': {
-  //       tagExpression: '$ls(/)',
-  //       method: 'ls',
-  //       args: '/',
-  //     },
-  //   });
-  // });
-
-
-});

+ 1 - 1
packages/hackmd/package.json

@@ -11,7 +11,7 @@
     "build": "vite build",
     "clean": "npx -y shx rm -rf dist types",
     "dev": "vite build --mode dev",
-    "watch": "yarn dev -w'",
+    "watch": "yarn dev -w",
     "lint:js": "eslint **/*.{js,ts}",
     "lint": "npm-run-all -p lint:*"
   },

+ 1 - 1
packages/presentation/package.json

@@ -16,7 +16,7 @@
     "clean": "npx -y shx rm -rf dist types",
     "dev": "run-p dev:*",
     "dev:js": "vite build --mode dev",
-    "watch": "run-p 'dev:js -w'",
+    "watch": "run-p 'dev:js -w",
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
     "lint:styles": "stylelint --allow-empty-input src/**/*.scss src/**/*.css",
     "lint": "run-p lint:*"

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

@@ -16,7 +16,7 @@
     "dev": "run-p dev:*",
     "dev:libs": "yarn build:libs --mode dev",
     "dev:themes": "yarn build:themes --mode dev",
-    "watch": "run-p 'dev:libs -w' 'dev:themes -w'",
+    "watch": "run-p 'dev:libs -w' 'dev:themes -w",
     "lint:eslint": "eslint --quiet \"**/*.{js,jsx,ts,tsx}\"",
     "lint:styles": "stylelint src/**/*.scss",
     "lint": "run-p lint:*",

+ 1 - 0
packages/remark-lsx/.gitignore

@@ -1 +1,2 @@
 /dist
+/types

+ 20 - 10
packages/remark-lsx/package.json

@@ -4,16 +4,22 @@
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": ["growi", "growi-plugin"],
-  "main": "dist/index.js",
-  "exports": {
-    "./components": "./dist/components/index.js",
-    "./services/renderer": "./dist/services/renderer/index.js",
-    "./server/routes": "./dist/server/routes/index.js"
-  },
-  "files": ["dist"],
+  "types": "types/index.d.ts",
+  "files": [
+    "dist",
+    "types"
+  ],
   "scripts": {
-    "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
+    "build": "run-p build:*",
+    "build:client": "vite build -c vite.client.config.ts",
+    "build:server": "vite build -c vite.server.config.ts",
     "clean": "npx -y shx rm -rf dist",
+    "dev": "run-p dev:*",
+    "dev:client": "vite build -c vite.client.config.ts --mode dev",
+    "dev:server": "vite build -c vite.server.config.ts --mode dev",
+    "watch": "run-p watch:*",
+    "watch:client": "yarn dev:client -w",
+    "watch:server": "yarn dev:server -w",
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
     "lint:styles": "stylelint --allow-empty-input src/**/*.scss src/**/*.css",
     "lint": "run-p lint:*"
@@ -25,8 +31,12 @@
     "swr": "^2.0.3"
   },
   "devDependencies": {
-    "eslint-plugin-regex": "^1.8.0",
+    "eslint-plugin-regex": "^1.8.0"
+  },
+  "peerDependencies": {
+    "next": "~12.2",
     "react": "^18.2.0",
-    "react-dom": "^18.2.0"
+    "react-dom": "^18.2.0",
+    "react-markdown": "^8.0.3"
   }
 }

+ 2 - 0
packages/remark-lsx/src/client/index.ts

@@ -0,0 +1,2 @@
+export * from '../components';
+export * from '../services/renderer';

+ 0 - 8
packages/remark-lsx/src/components/Lsx.module.scss

@@ -1,12 +1,4 @@
 .lsx :global {
-  page-list-ul > li > a:not(:hover) {
-    text-decoration: none;
-  }
-
-  .lsx-page-not-exist {
-    opacity: 0.6;
-  }
-
   // workaround
   // https://stackoverflow.com/a/57667536
   .lsx-blink {

+ 7 - 1
packages/remark-lsx/src/components/LsxPageList/LsxListView.module.scss

@@ -1 +1,7 @@
-@use '~/styles/molecules/page_list';
+@use '@growi/ui/src/styles/molecules/page_list';
+
+.page-list :global {
+  .page-list-ul > li > a:not(:hover) {
+    text-decoration: none;
+  }
+}

+ 2 - 1
packages/remark-lsx/src/components/LsxPageList/LsxListView.tsx

@@ -5,6 +5,7 @@ import { LsxContext } from '../lsx-context';
 
 import { LsxPage } from './LsxPage';
 
+
 import styles from './LsxListView.module.scss';
 
 
@@ -47,7 +48,7 @@ export const LsxListView = React.memo((props: Props): JSX.Element => {
   }, [basisViewersCount, isEmpty, lsxContext, nodeTree]);
 
   return (
-    <div className={`page-list ${styles['page-list']} lsx`}>
+    <div className={`page-list ${styles['page-list']}`}>
       <ul className="page-list-ul">
         {contents}
       </ul>

+ 7 - 0
packages/remark-lsx/src/components/LsxPageList/LsxPage.module.scss

@@ -0,0 +1,7 @@
+.page-list-li :global {
+
+  .lsx-page-not-exist {
+    opacity: 0.6;
+  }
+
+}

+ 6 - 2
packages/remark-lsx/src/components/LsxPageList/LsxPage.tsx

@@ -1,13 +1,17 @@
 import React, { useMemo } from 'react';
 
 import { pathUtils } from '@growi/core';
-import { PagePathLabel, PageListMeta } from '@growi/ui';
+import { PageListMeta } from '@growi/ui/dist/components/PagePath/PageListMeta';
+import { PagePathLabel } from '@growi/ui/dist/components/PagePath/PagePathLabel';
 import Link from 'next/link';
 
 import type { PageNode } from '../../interfaces/page-node';
 import { LsxContext } from '../lsx-context';
 
 
+import styles from './LsxPage.module.scss';
+
+
 type Props = {
   pageNode: PageNode,
   lsxContext: LsxContext,
@@ -98,7 +102,7 @@ export const LsxPage = React.memo((props: Props): JSX.Element => {
   }, [basisViewersCount, pageNode.page]);
 
   return (
-    <li className="page-list-li">
+    <li className={`page-list-li ${styles['page-list-li']}`}>
       <small>{iconElement}</small> {pagePathElement}
       <span className="ml-2">{pageListMetaElement}</span>
       {childrenElements}

+ 1 - 3
packages/remark-lsx/src/components/lsx-context.ts

@@ -1,6 +1,4 @@
-import { customTagUtils, ParseRangeResult } from '@growi/core';
-
-const { OptionParser } = customTagUtils;
+import { OptionParser, type ParseRangeResult } from '@growi/core/dist/plugin';
 
 
 export class LsxContext {

+ 0 - 6
packages/remark-lsx/src/index.ts

@@ -1,6 +0,0 @@
-import * as _serverRoutes from './server/routes';
-
-export const serverRoutes = _serverRoutes;
-
-export * from './components';
-export * from './services/renderer';

+ 6 - 2
packages/remark-lsx/src/server/routes/index.js → packages/remark-lsx/src/server/index.ts

@@ -1,13 +1,17 @@
+import { routesFactory } from './routes/lsx';
 
 const loginRequiredFallback = (req, res) => {
   return res.status(403).send('login required');
 };
 
-module.exports = (crowi, app) => {
-  const lsx = require('./lsx')(crowi, app);
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
+const middleware = (crowi: any, app: any): void => {
+  const lsx = routesFactory(crowi, app);
 
   const loginRequired = crowi.require('../middlewares/login-required')(crowi, true, loginRequiredFallback);
   const accessTokenParser = crowi.require('../middlewares/access-token-parser')(crowi);
 
   app.get('/_api/lsx', accessTokenParser, loginRequired, lsx.listPages);
 };
+
+export default middleware;

+ 6 - 7
packages/remark-lsx/src/server/routes/lsx.js → packages/remark-lsx/src/server/routes/lsx.ts

@@ -1,9 +1,7 @@
+import { OptionParser } from '@growi/core/dist/plugin';
+import { pathUtils, pagePathUtils } from '@growi/core/dist/utils';
 import createError, { isHttpError } from 'http-errors';
 
-const { pathUtils, pagePathUtils, customTagUtils } = require('@growi/core');
-
-const { OptionParser } = customTagUtils;
-
 
 const DEFAULT_PAGES_NUM = 50;
 
@@ -103,7 +101,7 @@ class Lsx {
       throw createError(400, 'filter option require value in regular expression.');
     }
 
-    let filterPath = '';
+    let filterPath;
     if (optionsFilter.charAt(0) === '^') {
       // move '^' to the first of path
       filterPath = new RegExp(`^${addTrailingSlash(pagePath)}${optionsFilter.slice(1, optionsFilter.length)}`);
@@ -159,9 +157,10 @@ class Lsx {
 
 }
 
-module.exports = (crowi, app) => {
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export const routesFactory = (crowi, app): any => {
   const Page = crowi.model('Page');
-  const actions = {};
+  const actions: any = {};
 
   /**
    *

+ 0 - 13
packages/remark-lsx/tsconfig.base.json

@@ -1,13 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "jsx": "preserve",
-  },
-  "include": [
-    "src"
-  ],
-  "exclude": [
-    "src/test"
-  ]
-}

+ 0 - 17
packages/remark-lsx/tsconfig.build.json

@@ -1,17 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "./src",
-    "outDir": "dist",
-    "declaration": true,
-    "noResolve": false,
-    "preserveConstEnums": true,
-    "sourceMap": false,
-    "noEmit": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  }
-}

+ 7 - 3
packages/remark-lsx/tsconfig.json

@@ -1,11 +1,15 @@
 {
   "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
+  "extends": "../../tsconfig.base.json",
   "compilerOptions": {
+    "jsx": "react-jsxdev",
+
     "baseUrl": ".",
     "paths": {
       "~/*": ["./src/*"],
-      "@growi/*": ["../*/src"]
     }
-  }
+  },
+  "include": [
+    "src"
+  ]
 }

+ 37 - 0
packages/remark-lsx/vite.client.config.ts

@@ -0,0 +1,37 @@
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    react(),
+    dts({ outputDir: 'types' }),
+  ],
+  build: {
+    outDir: 'dist/client',
+    lib: {
+      entry: {
+        index: 'src/client/index.ts',
+      },
+      name: 'remark-lsx-libs',
+      formats: ['es'],
+    },
+    rollupOptions: {
+      external: [
+        'react', 'react-dom',
+        'assert',
+        'axios',
+        'http-errors',
+        'is-absolute-url',
+        'react',
+        'next/link',
+        'unified',
+        'swr',
+        /^hast-.*/,
+        /^unist-.*/,
+        /^@growi\/.*/,
+      ],
+    },
+  },
+});

+ 37 - 0
packages/remark-lsx/vite.server.config.ts

@@ -0,0 +1,37 @@
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    dts({ outputDir: 'types' }),
+  ],
+  build: {
+    outDir: 'dist/server',
+    lib: {
+      entry: [
+        'src/server/index.ts',
+      ],
+      name: 'remark-lsx-libs',
+      formats: ['cjs'],
+    },
+    rollupOptions: {
+      output: {
+        preserveModules: true,
+        preserveModulesRoot: 'src/server',
+      },
+      external: [
+        'axios',
+        'http-errors',
+        'is-absolute-url',
+        'react',
+        'next/link',
+        'unified',
+        'swr',
+        /^hast-.*/,
+        /^unist-.*/,
+        /^@growi\/.*/,
+      ],
+    },
+  },
+});

+ 1 - 1
packages/slack/package.json

@@ -9,7 +9,7 @@
     "build": "vite build",
     "clean": "npx -y shx rm -rf dist types",
     "dev": "vite build --mode dev",
-    "watch": "yarn dev -w'",
+    "watch": "yarn dev -w",
     "lint:js": "eslint **/*.{js,ts}",
     "lint": "npm-run-all -p lint:*"
   },

+ 1 - 0
packages/ui/.gitignore

@@ -1 +1,2 @@
 /dist
+/types

+ 11 - 9
packages/ui/package.json

@@ -4,22 +4,24 @@
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": ["growi"],
-  "main": "dist/cjs/index.js",
-  "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "type": "module",
+  "types": "types/index.d.ts",
+  "files": [
+    "dist",
+    "types"
+  ],
   "scripts": {
-    "build": "run-p build:*",
-    "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
-    "build:esm": "tsc -p tsconfig.build.esm.json && tsc-alias -p tsconfig.build.esm.json",
-    "clean": "npx -y shx rm -rf dist",
-    "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
+    "build": "vite build",
+    "clean": "npx -y shx rm -rf dist types",
+    "dev": "vite build --mode dev",
+    "watch": "yarn dev -w",
+    "lint:js": "eslint **/*.{js,ts}",
     "lint": "npm-run-all -p lint:*"
   },
   "dependencies": {
     "@growi/core": "^6.0.8-RC.0"
   },
   "devDependencies": {
-    "eslint-plugin-regex": "^1.8.0",
     "react": "^18.2.0"
   }
 }

+ 1 - 1
packages/ui/src/components/Attachment/Attachment.tsx → packages/ui/src/components/Attachment.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { HasObjectId, IAttachment } from '@growi/core';
 
-import { UserPicture } from '../User/UserPicture';
+import { UserPicture } from './User/UserPicture';
 
 type AttachmentProps = {
   attachment: IAttachment & HasObjectId,

+ 1 - 1
packages/ui/src/components/SearchPage/FootstampIcon.jsx → packages/ui/src/components/FootstampIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-export const FootstampIcon = () => (
+export const FootstampIcon = (): JSX.Element => (
   <svg
     xmlns="http://www.w3.org/2000/svg"
     width={16}

+ 1 - 1
packages/ui/src/components/PagePath/PageListMeta.tsx

@@ -4,7 +4,7 @@ import assert from 'assert';
 
 import { templateChecker, pagePathUtils, IPageHasId } from '@growi/core';
 
-import { FootstampIcon } from '../SearchPage/FootstampIcon';
+import { FootstampIcon } from '../FootstampIcon';
 
 const { isTopPage } = pagePathUtils;
 const { checkTemplatePath } = templateChecker;

+ 0 - 10
packages/ui/src/index.ts

@@ -1,10 +0,0 @@
-export * from './interfaces/breakpoints';
-
-export * from './components/Attachment/Attachment';
-export * from './components/PagePath/PageListMeta';
-export * from './components/PagePath/PagePathLabel';
-export * from './components/SearchPage/FootstampIcon';
-export * from './components/User/UserPicture';
-
-export * from './utils/browser-utils';
-export * from './utils/use-fullscreen';

+ 0 - 2
apps/app/src/styles/molecules/_page_list.scss → packages/ui/src/styles/molecules/_page_list.scss

@@ -1,5 +1,3 @@
-@use '../bootstrap/variables' as var;
-
 .page-list :global {
   .btn-page-item-control {
     width: 20px;

+ 2 - 0
packages/ui/src/utils/index.ts

@@ -0,0 +1,2 @@
+export * from './browser-utils';
+export * from './use-fullscreen';

+ 0 - 18
packages/ui/tsconfig.base.json

@@ -1,18 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "module": "esnext",
-    "jsx": "react",
-    "noFallthroughCasesInSwitch": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "strict": true,
-  },
-  "include": [
-    "src"
-  ],
-  "exclude": [
-    "src/test"
-  ]
-}

+ 0 - 19
packages/ui/tsconfig.build.cjs.json

@@ -1,19 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
-  "compilerOptions": {
-    "module": "commonjs",
-
-    "rootDir": "./src",
-    "outDir": "dist/cjs",
-    "declaration": true,
-    "noResolve": false,
-    "preserveConstEnums": true,
-    "sourceMap": false,
-    "noEmit": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  }
-}

+ 0 - 17
packages/ui/tsconfig.build.esm.json

@@ -1,17 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "./src",
-    "outDir": "dist/esm",
-    "declaration": true,
-    "noResolve": false,
-    "preserveConstEnums": true,
-    "sourceMap": false,
-    "noEmit": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  }
-}

+ 0 - 17
packages/ui/tsconfig.build.json

@@ -1,17 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "./src",
-    "outDir": "dist/esm",
-    "declaration": true,
-    "noResolve": false,
-    "preserveConstEnums": true,
-    "sourceMap": false,
-    "noEmit": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  }
-}

+ 7 - 3
packages/ui/tsconfig.json

@@ -1,11 +1,15 @@
 {
   "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "./tsconfig.base.json",
+  "extends": "../../tsconfig.base.json",
   "compilerOptions": {
+    "jsx": "react-jsxdev",
+
     "baseUrl": ".",
     "paths": {
       "~/*": ["./src/*"],
-      "@growi/*": ["../*/src"]
     }
-  }
+  },
+  "include": [
+    "src"
+  ]
 }

+ 35 - 0
packages/ui/vite.config.ts

@@ -0,0 +1,35 @@
+import path from 'path';
+
+import react from '@vitejs/plugin-react';
+import glob from 'glob';
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    react(),
+    dts({ outputDir: 'types' }),
+  ],
+  build: {
+    outDir: 'dist',
+    lib: {
+      entry: glob.sync(path.resolve(__dirname, 'src/**/*.{ts,tsx}')),
+      name: 'ui-libs',
+      formats: ['es'],
+    },
+    rollupOptions: {
+      output: {
+        preserveModules: true,
+        preserveModulesRoot: 'src',
+      },
+      external: [
+        'react', 'react-dom',
+        'assert',
+        'reactstrap',
+        /^next\/.*/,
+        /^@growi\/.*/,
+      ],
+    },
+  },
+});

+ 5 - 1
turbo.json

@@ -6,7 +6,7 @@
       "outputs": ["dist/**", "types/**"]
     },
     "@growi/remark-lsx#build": {
-      "dependsOn": ["@growi/remark-growi-directive#build", "@growi/ui#build"],
+      "dependsOn": ["@growi/core#build", "@growi/remark-growi-directive#build", "@growi/ui#build"],
       "outputs": ["dist/**", "types/**"]
     },
     "@growi/app#build": {
@@ -32,6 +32,10 @@
       "outputs": ["tmp/cache/migration-status.out"],
       "inputs": ["src/migration/*.js"]
     },
+    "@growi/remark-lsx#dev": {
+      "dependsOn": ["@growi/core#dev", "@growi/remark-growi-directive#dev", "@growi/ui#dev"],
+      "outputs": ["dist/**", "types/**"]
+    },
     "@growi/app#dev": {
       "dependsOn": ["^dev", "@growi/app#dev:migrate"],
       "cache": false,