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

Merge branch 'master' into fix/putback

Haku Mizuki 3 лет назад
Родитель
Сommit
8e2d901670
40 измененных файлов с 162 добавлено и 1788 удалено
  1. 3 3
      packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  2. 6 8
      packages/app/src/components/Layout/SearchResultLayout.tsx
  3. 1 1
      packages/app/src/components/Page/RevisionLoader.tsx
  4. 0 137
      packages/app/src/components/Page/RevisionRenderer.tsx
  5. 8 8
      packages/app/src/components/PageTimeline.module.scss
  6. 32 26
      packages/app/src/components/PageTimeline.tsx
  7. 0 2
      packages/app/src/pages/[[...path]].page.tsx
  8. 2 2
      packages/app/src/pages/_search.page.tsx
  9. 0 8
      packages/app/src/server/routes/all-in-app-notifications.ts
  10. 3 21
      packages/app/src/server/routes/index.js
  11. 0 30
      packages/app/src/server/routes/me.js
  12. 33 560
      packages/app/src/server/routes/page.js
  13. 0 7
      packages/app/src/server/routes/private-legacy-pages.ts
  14. 0 19
      packages/app/src/server/routes/search.ts
  15. 0 4
      packages/app/src/server/routes/tag.js
  16. 0 0
      packages/app/src/services/renderer/PostProcessor/.keep
  17. 0 23
      packages/app/src/services/renderer/PreProcessor/CsvToTable.js
  18. 0 14
      packages/app/src/services/renderer/PreProcessor/Linker.js
  19. 0 29
      packages/app/src/services/renderer/PreProcessor/XssFilter.ts
  20. 0 139
      packages/app/src/services/renderer/interceptor/detach-code-blocks.js
  21. 0 0
      packages/app/src/services/renderer/markdown-it/PreProcessor/EasyGrid.js
  22. 0 9
      packages/app/src/services/renderer/markdown-it/drawio-viewer.js
  23. 0 66
      packages/app/src/services/renderer/markdown-it/emoji-mart-data.ts
  24. 0 12
      packages/app/src/services/renderer/markdown-it/emoji.js
  25. 0 7
      packages/app/src/services/renderer/markdown-it/footernote.js
  26. 0 34
      packages/app/src/services/renderer/markdown-it/header-line-number.js
  27. 0 13
      packages/app/src/services/renderer/markdown-it/header-with-edit-link.js
  28. 0 33
      packages/app/src/services/renderer/markdown-it/header.js
  29. 0 48
      packages/app/src/services/renderer/markdown-it/link-by-relative-path.ts
  30. 0 7
      packages/app/src/services/renderer/markdown-it/mathjax.js
  31. 0 28
      packages/app/src/services/renderer/markdown-it/plantuml.ts
  32. 0 16
      packages/app/src/services/renderer/markdown-it/table-with-handsontable-button.js
  33. 0 9
      packages/app/src/services/renderer/markdown-it/table.js
  34. 0 14
      packages/app/src/services/renderer/markdown-it/task-lists.js
  35. 0 27
      packages/app/src/services/renderer/markdown-it/toc-and-anchor.js
  36. 35 259
      packages/app/src/services/renderer/renderer.tsx
  37. 29 43
      packages/app/src/stores/renderer.tsx
  38. 8 4
      packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts
  39. 2 1
      packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts
  40. 0 117
      packages/app/test/integration/models/share-link.test.js

+ 3 - 3
packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -126,7 +126,7 @@ export const UserGroupPage: FC = () => {
 
   const deleteUserGroupById = useCallback(async(deleteGroupId: string, actionName: string, transferToUserGroupId: string) => {
     try {
-      const res = await apiv3Delete(`/user-groups/${deleteGroupId}`, {
+      await apiv3Delete(`/user-groups/${deleteGroupId}`, {
         actionName,
         transferToUserGroupId,
       });
@@ -137,12 +137,12 @@ export const UserGroupPage: FC = () => {
       setSelectedUserGroup(undefined);
       setDeleteModalShown(false);
 
-      toastSuccess(`Deleted ${res.data.userGroups.length} groups.`);
+      toastSuccess(`Deleted ${selectedUserGroup?.name} group.`);
     }
     catch (err) {
       toastError(new Error('Unable to delete the groups'));
     }
-  }, [mutateUserGroups]);
+  }, [mutateUserGroups, selectedUserGroup]);
 
   return (
     <div data-testid="admin-user-groups">

+ 6 - 8
packages/app/src/components/Layout/SearchResultLayout.tsx

@@ -11,14 +11,12 @@ type Props = {
 const SearchResultLayout = ({ children }: Props): JSX.Element => {
 
   return (
-    <div className={`on-search ${commonStyles['on-search']}`}>
-      <BasicLayout>
-        <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
-        <div id="main" className="main search-page mt-0">
-          { children }
-        </div>
-      </BasicLayout>
-    </div>
+    <BasicLayout className={`on-search ${commonStyles['on-search']}`}>
+      <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
+      <div id="main" className="main search-page mt-0">
+        { children }
+      </div>
+    </BasicLayout>
   );
 };
 

+ 1 - 1
packages/app/src/components/Page/RevisionLoader.tsx

@@ -85,7 +85,7 @@ export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
   if (lazy && !isLoaded) {
     return (
       <Waypoint onPositionChange={onWaypointChange} bottomOffset="-100px">
-        <></>
+        <div></div>
       </Waypoint>
     );
   }

+ 0 - 137
packages/app/src/components/Page/RevisionRenderer.tsx

@@ -23,7 +23,6 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
 
   return (
     <ReactMarkdown
-      data-testid="wiki"
       {...rendererOptions}
       className={`wiki ${additionalClassName ?? ''}`}
     >
@@ -31,142 +30,6 @@ const RevisionRenderer = React.memo((props: Props): JSX.Element => {
     </ReactMarkdown>
   );
 
-  // const [html, setHtml] = useState('');
-
-  // const { data: interceptorManager } = useInterceptorManager();
-  // const { data: editorSettings } = useEditorSettings();
-  // const { data: currentPathname } = useCurrentPathname();
-
-  // const currentRenderingContext = useMemo(() => {
-  //   return {
-  //     markdown,
-  //     parsedHTML: '',
-  //     pagePath,
-  //     renderDrawioInRealtime: editorSettings?.renderDrawioInRealtime,
-  //     currentPathname: decodeURIComponent(currentPathname ?? '/'),
-  //   };
-  // }, [editorSettings?.renderDrawioInRealtime, markdown, pagePath]);
-
-
-  // const renderHtml = useCallback(async() => {
-  //   if (interceptorManager == null) {
-  //     return;
-  //   }
-
-  //   const context = currentRenderingContext;
-
-  //   await interceptorManager.process('preRender', context);
-  //   await interceptorManager.process('prePreProcess', context);
-  //   context.markdown = growiRenderer.preProcess(context.markdown, context);
-  //   await interceptorManager.process('postPreProcess', context);
-  //   context.parsedHTML = growiRenderer.process(context.markdown, context);
-  //   await interceptorManager.process('prePostProcess', context);
-  //   context.parsedHTML = growiRenderer.postProcess(context.parsedHTML, context);
-
-  //   const isMarkdownEmpty = context.markdown.trim().length === 0;
-  //   if (highlightKeywords != null && !isMarkdownEmpty) {
-  //     context.parsedHTML = getHighlightedBody(context.parsedHTML, highlightKeywords);
-  //   }
-  //   await interceptorManager.process('postPostProcess', context);
-  //   await interceptorManager.process('preRenderHtml', context);
-
-  //   setHtml(context.parsedHTML);
-  // }, [currentRenderingContext, growiRenderer, highlightKeywords, interceptorManager]);
-
-  // useEffect(() => {
-  //   if (interceptorManager == null) {
-  //     return;
-  //   }
-
-  //   renderHtml()
-  //     .then(() => {
-  //       // const HeaderLink = document.getElementsByClassName('revision-head-link');
-  //       // const HeaderLinkArray = Array.from(HeaderLink);
-  //       // addSmoothScrollEvent(HeaderLinkArray as HTMLAnchorElement[], blinkElem);
-
-  //       // interceptorManager.process('postRenderHtml', currentRenderingContext);
-  //     });
-
-  // }, [currentRenderingContext, interceptorManager, renderHtml]);
-
-  // const config = props.appContainer.getConfig();
-  // const isMathJaxEnabled = !!config.env.MATHJAX;
-
-  // return (
-  //   <RevisionBody
-  //     html={html}
-  //     isMathJaxEnabled={isMathJaxEnabled}
-  //     additionalClassName={props.additionalClassName}
-  //     renderMathJaxOnInit
-  //   />
-  // );
-
-  // const [html, setHtml] = useState('');
-
-  // const { data: interceptorManager } = useInterceptorManager();
-  // const { data: editorSettings } = useEditorSettings();
-  // const { data: currentPathname } = useCurrentPathname();
-
-  // const currentRenderingContext = useMemo(() => {
-  //   return {
-  //     markdown,
-  //     parsedHTML: '',
-  //     pagePath,
-  //     renderDrawioInRealtime: editorSettings?.renderDrawioInRealtime,
-  //     currentPathname: decodeURIComponent(currentPathname ?? '/'),
-  //   };
-  // }, [editorSettings?.renderDrawioInRealtime, markdown, pagePath]);
-
-
-  // const renderHtml = useCallback(async() => {
-  //   if (interceptorManager == null) {
-  //     return;
-  //   }
-
-  //   const context = currentRenderingContext;
-
-  //   await interceptorManager.process('preRender', context);
-  //   await interceptorManager.process('prePreProcess', context);
-  //   context.markdown = growiRenderer.preProcess(context.markdown, context);
-  //   await interceptorManager.process('postPreProcess', context);
-  //   context.parsedHTML = growiRenderer.process(context.markdown, context);
-  //   await interceptorManager.process('prePostProcess', context);
-  //   context.parsedHTML = growiRenderer.postProcess(context.parsedHTML, context);
-
-  //   const isMarkdownEmpty = context.markdown.trim().length === 0;
-  //   if (highlightKeywords != null && !isMarkdownEmpty) {
-  //     context.parsedHTML = getHighlightedBody(context.parsedHTML, highlightKeywords);
-  //   }
-  //   await interceptorManager.process('postPostProcess', context);
-  //   await interceptorManager.process('preRenderHtml', context);
-
-  //   setHtml(context.parsedHTML);
-  // }, [currentRenderingContext, growiRenderer, highlightKeywords, interceptorManager]);
-
-  // useEffect(() => {
-  //   if (interceptorManager == null) {
-  //     return;
-  //   }
-
-  //   renderHtml()
-  //     .then(() => {
-  //       // const HeaderLink = document.getElementsByClassName('revision-head-link');
-  //       // const HeaderLinkArray = Array.from(HeaderLink);
-  //       // addSmoothScrollEvent(HeaderLinkArray as HTMLAnchorElement[], blinkElem);
-
-  //       // interceptorManager.process('postRenderHtml', currentRenderingContext);
-  //     });
-
-  // }, [currentRenderingContext, interceptorManager, renderHtml]);
-
-  // return (
-  //   <RevisionBody
-  //     html={html}
-  //     additionalClassName={props.additionalClassName}
-  //     renderMathJaxOnInit
-  //   />
-  // );
-
 });
 RevisionRenderer.displayName = 'RevisionRenderer';
 

+ 8 - 8
packages/app/src/components/PageTimeline.module.scss

@@ -1,14 +1,14 @@
 @use '../styles/bootstrap/variables' as var;
 
-.card-timeline {
-  :global {
-    border: 1px solid var.$gray-300;
+.card-timeline :global {
+  margin-bottom: 3rem;
+  border: 1px solid var.$gray-300;
+
+  .card-header {
+    background-color: var.$gray-300;
   }
 
-  &:global {
-    > .card-header {
-      background-color: var.$gray-300;
-    }
+  .card-body {
+    min-height: 15vh;
   }
 }
-

+ 32 - 26
packages/app/src/components/PageTimeline.tsx

@@ -13,6 +13,37 @@ import PaginationWrapper from './PaginationWrapper';
 
 import styles from './PageTimeline.module.scss';
 
+
+type TimelineCardProps = {
+  page: IPageHasId,
+}
+
+const TimelineCard = ({ page }: TimelineCardProps): JSX.Element => {
+
+  const { data: rendererOptions } = useTimelineOptions(page.path);
+
+  return (
+    <div className={`card card-timeline ${styles['card-timeline']}`}>
+      <div className="card-header h4 p-3">
+        <Link href={page.path} prefetch={false}>
+          <a>{page.path}</a>
+        </Link>
+      </div>
+      <div className="card-body">
+        { rendererOptions != null && (
+          <RevisionLoader
+            lazy
+            rendererOptions={rendererOptions}
+            pageId={page._id}
+            revisionId={page.revision}
+          />
+        ) }
+      </div>
+    </div>
+  );
+};
+
+
 export const PageTimeline = (): JSX.Element => {
   const [activePage, setActivePage] = useState(1);
   const [totalPageItems, setTotalPageItems] = useState(0);
@@ -21,7 +52,6 @@ export const PageTimeline = (): JSX.Element => {
 
   const { data: currentPagePath } = useCurrentPagePath();
   const { t } = useTranslation();
-  const { data: rendererOptions } = useTimelineOptions();
 
   const handlePage = useCallback(async(selectedPage: number) => {
     if (currentPagePath == null) { return }
@@ -36,10 +66,6 @@ export const PageTimeline = (): JSX.Element => {
     handlePage(1);
   }, [handlePage]);
 
-  if (rendererOptions == null) {
-    return <></>;
-  }
-
   if (pages == null || pages.length === 0) {
     return (
       <div className="mt-2">
@@ -51,27 +77,7 @@ export const PageTimeline = (): JSX.Element => {
 
   return (
     <div>
-      { pages.map((page) => {
-        return (
-          <div className="timeline-body" key={`key-${page._id}`}>
-            <div className={`card card-timeline ${styles['card-timeline']}`}>
-              <div className="card-header">
-                <Link href={page.path} prefetch={false}>
-                  <a>{page.path}</a>
-                </Link>
-              </div>
-              <div className="card-body">
-                <RevisionLoader
-                  lazy
-                  rendererOptions={rendererOptions}
-                  pageId={page._id}
-                  revisionId={page.revision}
-                />
-              </div>
-            </div>
-          </div>
-        );
-      }) }
+      { pages.map(page => <TimelineCard key={page._id} page={page} />) }
       <PaginationWrapper
         activePage={activePage}
         changePage={handlePage}

+ 0 - 2
packages/app/src/pages/[[...path]].page.tsx

@@ -542,8 +542,6 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   props.isAllReplyShown = configManager.getConfig('crowi', 'customize:isAllReplyShown');
   props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
   props.isEnabledStaleNotification = configManager.getConfig('crowi', 'customize:isEnabledStaleNotification');
-  // props.isEnabledLinebreaks = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks');
-  // props.isEnabledLinebreaksInComments = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments');
   props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing');
   props.editorConfig = {
     upload: {

+ 2 - 2
packages/app/src/pages/_search.page.tsx

@@ -5,6 +5,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
+import SearchResultLayout from '~/components/Layout/SearchResultLayout';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
@@ -23,12 +24,11 @@ import {
 
 import { SearchPage } from '../components/SearchPage';
 
+import { NextPageWithLayout } from './_app.page';
 import {
   CommonProps, getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle,
 } from './utils/commons';
-import { NextPageWithLayout } from './_app.page';
 
-const SearchResultLayout = dynamic(() => import('~/components/Layout/SearchResultLayout'), { ssr: false });
 
 type Props = CommonProps & {
   currentUser: IUser,

+ 0 - 8
packages/app/src/server/routes/all-in-app-notifications.ts

@@ -1,8 +0,0 @@
-import {
-  Request, Response,
-} from 'express';
-
-export const list = (req: Request, res: Response): void => {
-
-  return res.render('me/all-in-app-notifications');
-};

+ 3 - 21
packages/app/src/server/routes/index.js

@@ -6,15 +6,12 @@ import apiV1FormValidator from '../middlewares/apiv1-form-validator';
 import injectResetOrderByTokenMiddleware from '../middlewares/inject-reset-order-by-token-middleware';
 import injectUserRegistrationOrderByTokenMiddleware from '../middlewares/inject-user-registration-order-by-token-middleware';
 import * as loginFormValidator from '../middlewares/login-form-validator';
-import * as registerFormValidator from '../middlewares/register-form-validator';
 import {
   generateUnavailableWhenMaintenanceModeMiddleware, generateUnavailableWhenMaintenanceModeMiddlewareForApi,
 } from '../middlewares/unavailable-when-maintenance-mode';
 
-import * as allInAppNotifications from './all-in-app-notifications';
 import * as forgotPassword from './forgot-password';
 import nextFactory from './next';
-import * as privateLegacyPages from './private-legacy-pages';
 import * as userActivation from './user-activation';
 
 const multer = require('multer');
@@ -33,7 +30,6 @@ module.exports = function(crowi, app) {
   const loginRequired = require('../middlewares/login-required')(crowi, true);
   const adminRequired = require('../middlewares/admin-required')(crowi);
   const certifySharedFile = require('../middlewares/certify-shared-file')(crowi);
-  const injectUserUISettings = require('../middlewares/inject-user-ui-settings-to-localvars')();
   const rateLimiter = require('../middlewares/rate-limiter')();
   const addActivity = generateAddActivityMiddleware(crowi);
 
@@ -84,7 +80,7 @@ module.exports = function(crowi, app) {
   // NOTE: get method "/admin/export/:fileName" should be loaded before "/admin/*"
   app.get('/admin/export/:fileName'   , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
 
-  app.get('/admin/*'                    , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
+  app.get('/admin/*'                  , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
   app.get('/admin'                    , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
 
   // installer
@@ -153,18 +149,8 @@ module.exports = function(crowi, app) {
 
   app.use(unavailableWhenMaintenanceMode);
 
-  // app.get('/tags'                     , loginRequired, tag.showPage);
-  app.get('/tags', loginRequired, next.delegateToNext);
-
-  app.get('/me'                                 , loginRequiredStrictly, injectUserUISettings, next.delegateToNext);
-  app.get('/me/*'                                 , loginRequiredStrictly, injectUserUISettings, next.delegateToNext);
-  // external-accounts
-  // my in-app-notifications
-  // app.get('/me/all-in-app-notifications'   , loginRequiredStrictly, injectUserUISettings, allInAppNotifications.list);
-  // app.get('/me/external-accounts'               , loginRequiredStrictly, injectUserUISettings, me.externalAccounts.list);
-  // // my drafts
-  // app.get('/me/drafts'                          , loginRequiredStrictly, injectUserUISettings, me.drafts.list);
-
+  app.get('/me'                                   , loginRequiredStrictly, next.delegateToNext);
+  app.get('/me/*'                                 , loginRequiredStrictly, next.delegateToNext);
   app.get('/attachment/:id([0-9a-z]{24})' , certifySharedFile , loginRequired, attachment.api.get);
   app.get('/attachment/profile/:id([0-9a-z]{24})' , loginRequired, attachment.api.get);
   app.get('/attachment/:pageId/:fileName'       , loginRequired, attachment.api.obsoletedGetForMongoDB); // DEPRECATED: remains for backward compatibility for v3.3.x or below
@@ -172,10 +158,6 @@ module.exports = function(crowi, app) {
 
   app.get('/_search'                            , loginRequired, next.delegateToNext);
 
-  app.get('/trash$'                   , loginRequired, injectUserUISettings, next.delegateToNext);
-  app.get('/trash/$'                  , loginRequired, (req, res) => res.redirect('/trash'));
-  app.get('/trash/*/$'                , loginRequired, injectUserUISettings, page.deletedPageListShowWrapper);
-
   app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
   app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
   app.post('/_api/hackmd.integrate'      , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.integrate);

+ 0 - 30
packages/app/src/server/routes/me.js

@@ -100,35 +100,5 @@ module.exports = function(crowi, app) {
       });
   };
 
-  actions.externalAccounts = {};
-  actions.externalAccounts.list = function(req, res) {
-    const userData = req.user;
-
-    const renderVars = {};
-    ExternalAccount.find({ user: userData })
-      .then((externalAccounts) => {
-        renderVars.externalAccounts = externalAccounts;
-        return;
-      })
-      .then(() => {
-        if (req.method === 'POST' && req.form.isValid) {
-          // TODO impl
-          return res.render('me/external-accounts', renderVars);
-        }
-        // method GET
-        return res.render('me/external-accounts', renderVars);
-      });
-  };
-
-  actions.drafts = {};
-  actions.drafts.list = async function(req, res) {
-    return res.render('me/drafts');
-  };
-
-  actions.updates = function(req, res) {
-    res.render('me/update', {
-    });
-  };
-
   return actions;
 };

+ 33 - 560
packages/app/src/server/routes/page.js

@@ -1,17 +1,13 @@
-import { pagePathUtils } from '@growi/core';
 import { body } from 'express-validator';
 import mongoose from 'mongoose';
-import urljoin from 'url-join';
 
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
-import Activity from '~/server/models/activity';
 import XssOption from '~/services/xss/xssOption';
 import loggerFactory from '~/utils/logger';
 
 import { PathAlreadyExistsError } from '../models/errors';
 import UpdatePost from '../models/update-post';
 
-const { isCreatablePage, isTopPage, isUsersHomePage } = pagePathUtils;
 const { serializePageSecurely } = require('../models/serializers/page-serializer');
 const { serializeRevisionSecurely } = require('../models/serializers/revision-serializer');
 const { serializeUserSecurely } = require('../models/serializers/user-serializer');
@@ -139,7 +135,6 @@ const { serializeUserSecurely } = require('../models/serializers/user-serializer
 module.exports = function(crowi, app) {
   const debug = require('debug')('growi:routes:page');
   const logger = loggerFactory('growi:routes:page');
-  const swig = require('swig-templates');
 
   const { pathUtils } = require('@growi/core');
 
@@ -147,13 +142,9 @@ module.exports = function(crowi, app) {
   const User = crowi.model('User');
   const PageTagRelation = crowi.model('PageTagRelation');
   const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
-  const ShareLink = crowi.model('ShareLink');
   const PageRedirect = mongoose.model('PageRedirect');
 
-  const { PageQueryBuilder } = Page;
-
   const ApiResponse = require('../util/apiResponse');
-  const getToday = require('../util/getToday');
 
   const { configManager, xssService } = crowi;
   const globalNotificationService = crowi.getGlobalNotificationService();
@@ -173,572 +164,54 @@ module.exports = function(crowi, app) {
 
   const actions = {};
 
-  function getPathFromRequest(req) {
-    return pathUtils.normalizePath(req.pagePath || req.params[0] || req.params.id || '');
-  }
-
-  function generatePager(offset, limit, totalCount) {
-    let prev = null;
-
-    if (offset > 0) {
-      prev = offset - limit;
-      if (prev < 0) {
-        prev = 0;
-      }
-    }
-
-    let next = offset + limit;
-    if (totalCount < next) {
-      next = null;
-    }
-
-    return {
-      prev,
-      next,
-      offset,
-    };
-  }
-
-  function addRenderVarsForPage(renderVars, page) {
-    renderVars.page = page;
-    renderVars.revision = page.revision;
-    renderVars.pageIdOnHackmd = page.pageIdOnHackmd;
-    renderVars.revisionHackmdSynced = page.revisionHackmdSynced;
-    renderVars.hasDraftOnHackmd = page.hasDraftOnHackmd;
-
-    if (page.creator != null) {
-      renderVars.page.creator = renderVars.page.creator.toObject();
-    }
-    if (page.revision.author != null) {
-      renderVars.revision.author = renderVars.revision.author.toObject();
-    }
-    if (page.deleteUser != null) {
-      renderVars.page.deleteUser = renderVars.page.deleteUser.toObject();
-    }
-  }
-
-  function addRenderVarsForPresentation(renderVars, page) {
-    // sanitize page.revision.body
-    if (crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention')) {
-      const preventXssRevision = xss.process(page.revision.body);
-      page.revision.body = preventXssRevision;
-    }
-    renderVars.page = page;
-    renderVars.revision = page.revision;
-  }
-
-  async function addRenderVarsForUserPage(renderVars, page) {
-    const userData = await User.findUserByUsername(User.getUsernameByPath(page.path));
-
-    if (userData != null) {
-      renderVars.pageUser = serializeUserSecurely(userData);
-    }
-  }
-
-  function addRenderVarsForScope(renderVars, page) {
-    renderVars.grant = page.grant;
-    renderVars.grantedGroupId = page.grantedGroup ? page.grantedGroup.id : null;
-    renderVars.grantedGroupName = page.grantedGroup ? page.grantedGroup.name : null;
-  }
-
-  async function addRenderVarsForDescendants(renderVars, path, requestUser, offset, limit, isRegExpEscapedFromPath) {
-    const SEENER_THRESHOLD = 10;
-
-    const queryOptions = {
-      offset,
-      limit,
-      includeTrashed: path.startsWith('/trash/'),
-      isRegExpEscapedFromPath,
-    };
-    const result = await Page.findListWithDescendants(path, requestUser, queryOptions);
-    if (result.pages.length > limit) {
-      result.pages.pop();
-    }
-
-    renderVars.viewConfig = {
-      seener_threshold: SEENER_THRESHOLD,
-    };
-    renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
-    renderVars.pages = result.pages;
-  }
-
-  async function addRenderVarsForPageTree(renderVars, pathOrId, user) {
-    const { targetAndAncestors, rootPage } = await Page.findTargetAndAncestorsByPathOrId(pathOrId, user);
-
-    if (targetAndAncestors.length === 0 && pathOrId.includes('/') && !isTopPage(pathOrId)) {
-      throw new Error('Ancestors must have at least one page.');
-    }
-
-    renderVars.targetAndAncestors = { targetAndAncestors, rootPage };
-  }
-
-  async function addRenderVarsWhenNotFound(renderVars, pathOrId) {
-    if (pathOrId == null) {
-      return;
-    }
-
-    renderVars.notFoundTargetPathOrId = pathOrId;
-  }
-
-  async function addRenderVarsWhenEmptyPage(renderVars, isEmpty, pageId) {
-    if (!isEmpty) return;
-    renderVars.pageId = pageId;
-    renderVars.isEmpty = isEmpty;
-  }
-
-  function replacePlaceholdersOfTemplate(template, req) {
-    if (req.user == null) {
-      return '';
-    }
-
-    const definitions = {
-      pagepath: getPathFromRequest(req),
-      username: req.user.name,
-      today: getToday(),
-    };
-    const compiledTemplate = swig.compile(template);
-
-    return compiledTemplate(definitions);
-  }
-
-  async function _notFound(req, res) {
-    const path = getPathFromRequest(req);
-    const pathOrId = req.params.id || path;
-
-    let view;
-    let action;
-    const renderVars = { path };
-
-    if (!isCreatablePage(path)) {
-      view = 'layout-growi/not_creatable';
-      action = SupportedAction.ACTION_PAGE_NOT_CREATABLE;
-    }
-    else if (req.isForbidden) {
-      view = 'layout-growi/forbidden';
-      action = SupportedAction.ACTION_PAGE_FORBIDDEN;
-    }
-    else {
-      view = 'layout-growi/not_found';
-      action = SupportedAction.ACTION_PAGE_NOT_FOUND;
-
-      // retrieve templates
-      if (req.user != null) {
-        const template = await Page.findTemplate(path);
-
-        if (template.templateBody) {
-          const body = replacePlaceholdersOfTemplate(template.templateBody, req);
-          const tags = template.templateTags;
-          renderVars.template = body;
-          renderVars.templateTags = tags;
-        }
-      }
-
-      // add scope variables by ancestor page
-      const ancestor = await Page.findAncestorByPathAndViewer(path, req.user);
-      if (ancestor != null) {
-        await ancestor.populate('grantedGroup');
-        addRenderVarsForScope(renderVars, ancestor);
-      }
-    }
-
-    const limit = 50;
-    const offset = parseInt(req.query.offset) || 0;
-    await addRenderVarsForDescendants(renderVars, path, req.user, offset, limit, true);
-    await addRenderVarsForPageTree(renderVars, pathOrId, req.user);
-    await addRenderVarsWhenNotFound(renderVars, pathOrId);
-    await addRenderVarsWhenEmptyPage(renderVars, req.isEmpty, req.pageId);
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    crowi.activityService.createActivity(parameters);
-
-    return res.render(view, renderVars);
-  }
-
-  async function showPageForPresentation(req, res, next) {
-    const id = req.params.id;
-    const { revisionId } = req.query;
-
-    let page = await Page.findByIdAndViewer(id, req.user, null, true, true);
-
-    if (page == null) {
-      next();
-    }
-
-    // empty page
-    if (page.isEmpty) {
-      // redirect to page (path) url
-      const url = new URL('https://dummy.origin');
-      url.pathname = page.path;
-      Object.entries(req.query).forEach(([key, value], i) => {
-        url.searchParams.append(key, value);
-      });
-      return res.safeRedirect(urljoin(url.pathname, url.search));
-
-    }
-
-    const renderVars = {};
-
-    // populate
-    page = await page.populateDataToMakePresentation(revisionId);
-
-    if (page != null) {
-      addRenderVarsForPresentation(renderVars, page);
-    }
-
-    return res.render('page_presentation', renderVars);
-  }
-
-  async function showTopPage(req, res, next) {
-    const portalPath = req.path;
-    const revisionId = req.query.revision;
-
-    const view = 'layout-growi/page_list';
-    const renderVars = { path: portalPath };
+  // async function showPageForPresentation(req, res, next) {
+  //   const id = req.params.id;
+  //   const { revisionId } = req.query;
 
-    let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
-    portalPage.initLatestRevisionField(revisionId);
+  //   let page = await Page.findByIdAndViewer(id, req.user, null, true, true);
 
-    // add user to seen users
-    if (req.user != null) {
-      portalPage = await portalPage.seen(req.user);
-    }
-
-    // populate
-    portalPage = await portalPage.populateDataToShowRevision();
-
-    addRenderVarsForPage(renderVars, portalPage);
-
-    const sharelinksNumber = await ShareLink.countDocuments({ relatedPage: portalPage._id });
-    renderVars.sharelinksNumber = sharelinksNumber;
-
-    const limit = 50;
-    const offset = parseInt(req.query.offset) || 0;
-
-    await addRenderVarsForDescendants(renderVars, portalPath, req.user, offset, limit);
-
-    await addRenderVarsForPageTree(renderVars, portalPath, req.user);
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: SupportedAction.ACTION_PAGE_VIEW,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    crowi.activityService.createActivity(parameters);
+  //   if (page == null) {
+  //     next();
+  //   }
 
-    return res.render(view, renderVars);
-  }
+  //   // empty page
+  //   if (page.isEmpty) {
+  //     // redirect to page (path) url
+  //     const url = new URL('https://dummy.origin');
+  //     url.pathname = page.path;
+  //     Object.entries(req.query).forEach(([key, value], i) => {
+  //       url.searchParams.append(key, value);
+  //     });
+  //     return res.safeRedirect(urljoin(url.pathname, url.search));
 
-  async function showPageForGrowiBehavior(req, res, next) {
-    const id = req.params.id;
-    const revisionId = req.query.revision;
+  //   }
 
-    let page = await Page.findByIdAndViewer(id, req.user, null, true, true);
+  //   const renderVars = {};
 
-    if (page == null) {
-      // check the page is forbidden or just does not exist.
-      req.isForbidden = await Page.count({ _id: id }) > 0;
-      return _notFound(req, res);
-    }
-
-    // empty page
-    if (page.isEmpty) {
-      req.pageId = page._id;
-      req.pagePath = page.path;
-      req.isEmpty = page.isEmpty;
-      return _notFound(req, res);
-    }
-
-    const { path } = page; // this must exist
+  //   // populate
+  //   page = await page.populateDataToMakePresentation(revisionId);
 
-    logger.debug('Page is found when processing pageShowForGrowiBehavior', page._id, path);
-
-    const limit = 50;
-    const offset = parseInt(req.query.offset) || 0;
-    const renderVars = {};
-
-    let view = 'layout-growi/page';
-
-    page.initLatestRevisionField(revisionId);
-
-    // add user to seen users
-    if (req.user != null) {
-      page = await page.seen(req.user);
-    }
+  //   if (page != null) {
+  //     addRenderVarsForPresentation(renderVars, page);
+  //   }
 
-    // populate
-    page = await page.populateDataToShowRevision();
-    addRenderVarsForPage(renderVars, page);
-    addRenderVarsForScope(renderVars, page);
+  //   return res.render('page_presentation', renderVars);
+  // }
 
-    await addRenderVarsForDescendants(renderVars, path, req.user, offset, limit, true);
-
-    const sharelinksNumber = await ShareLink.countDocuments({ relatedPage: page._id });
-    renderVars.sharelinksNumber = sharelinksNumber;
-
-    if (isUsersHomePage(path)) {
-      // change template
-      view = 'layout-growi/user_page';
-      await addRenderVarsForUserPage(renderVars, page);
-    }
-
-    await addRenderVarsForPageTree(renderVars, path, req.user);
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: isUsersHomePage(path) ? SupportedAction.ACTION_PAGE_USER_HOME_VIEW : SupportedAction.ACTION_PAGE_VIEW,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    crowi.activityService.createActivity(parameters);
-
-    return res.render(view, renderVars);
-  }
-
-  actions.showTopPage = function(req, res) {
-    return showTopPage(req, res);
-  };
-
-  /**
-   * Redirect to the page without trailing slash
-   */
-  actions.showPageWithEndOfSlash = function(req, res, next) {
-    return res.redirect(pathUtils.removeTrailingSlash(req.path));
-  };
 
   /**
    * switch action
    *   - presentation mode
    *   - by behaviorType
    */
-  actions.showPage = async function(req, res, next) {
-    // presentation mode
-    if (req.query.presentation) {
-      return showPageForPresentation(req, res, next);
-    }
-    // delegate to showPageForGrowiBehavior
-    return showPageForGrowiBehavior(req, res, next);
-  };
-
-  actions.showSharedPage = async function(req, res, next) {
-    const { linkId } = req.params;
-    const revisionId = req.query.revision;
-    const renderVars = {};
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-
-    const shareLink = await ShareLink.findOne({ _id: linkId }).populate('relatedPage');
-
-    if (shareLink == null || shareLink.relatedPage == null || shareLink.relatedPage.isEmpty) {
-
-      Object.assign(parameters, { action: SupportedAction.ACTION_SHARE_LINK_NOT_FOUND });
-      crowi.activityService.createActivity(parameters);
-
-      // page or sharelink are not found (or page is empty: abnormaly)
-      return res.render('layout-growi/not_found_shared_page');
-    }
-    if (crowi.configManager.getConfig('crowi', 'security:disableLinkSharing')) {
-
-      Object.assign(parameters, { action: SupportedAction.ACTION_SHARE_LINK_NOT_FOUND });
-      crowi.activityService.createActivity(parameters);
-
-      return res.render('layout-growi/forbidden');
-    }
-
-    renderVars.sharelink = shareLink;
-
-    // check if share link is expired
-    if (shareLink.isExpired()) {
-      Object.assign(parameters, { action: SupportedAction.ACTION_SHARE_LINK_EXPIRED_PAGE_VIEW });
-      crowi.activityService.createActivity(parameters);
-
-      // page is not found
-      return res.render('layout-growi/expired_shared_page', renderVars);
-    }
-
-    let page = shareLink.relatedPage;
-
-    // presentation mode
-    if (req.query.presentation) {
-      page = await page.populateDataToMakePresentation(revisionId);
-
-      // populate
-      addRenderVarsForPage(renderVars, page);
-      return res.render('page_presentation', renderVars);
-    }
-
-    page.initLatestRevisionField(revisionId);
-
-    // populate
-    page = await page.populateDataToShowRevision();
-    addRenderVarsForPage(renderVars, page);
-    addRenderVarsForScope(renderVars, page);
-
-    Object.assign(parameters, { action: SupportedAction.ACTION_SHARE_LINK_PAGE_VIEW });
-    crowi.activityService.createActivity(parameters);
-
-    return res.render('layout-growi/shared_page', renderVars);
-  };
-
-  /**
-   * switch action by behaviorType
-   */
-  /* eslint-disable no-else-return */
-  actions.trashPageShowWrapper = function(req, res) {
-    // Crowi behavior for '/trash/*'
-    return actions.deletedPageListShow(req, res);
-  };
-  /* eslint-enable no-else-return */
-
-  /**
-   * switch action by behaviorType
-   */
-  /* eslint-disable no-else-return */
-  actions.deletedPageListShowWrapper = function(req, res) {
-    const path = `/trash${getPathFromRequest(req)}`;
-    return res.redirect(path);
-  };
-  /* eslint-enable no-else-return */
-
-  actions.notFound = async function(req, res) {
-    return _notFound(req, res);
-  };
-
-  actions.deletedPageListShow = async function(req, res) {
-    // normalizePath makes '/trash/' -> '/trash'
-    const path = pathUtils.normalizePath(`/trash${getPathFromRequest(req)}`);
-
-    const limit = 50;
-    const offset = parseInt(req.query.offset) || 0;
-
-    const queryOptions = {
-      offset,
-      limit,
-      includeTrashed: true,
-    };
-
-    const renderVars = {
-      page: null,
-      path,
-      pages: [],
-    };
-
-    const result = await Page.findListWithDescendants(path, req.user, queryOptions);
-
-    if (result.pages.length > limit) {
-      result.pages.pop();
-    }
-
-    renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
-    renderVars.pages = result.pages;
-    res.render('layout-growi/page_list', renderVars);
-  };
-
-  /**
-   * redirector
-   */
-  async function redirector(req, res, next, path) {
-    const { redirectFrom } = req.query;
-
-    const includeEmpty = true;
-    const builder = new PageQueryBuilder(Page.find({ path }), includeEmpty);
-
-    builder.populateDataToList(User.USER_FIELDS_EXCEPT_CONFIDENTIAL);
-
-    await Page.addConditionToFilteringByViewerForList(builder, req.user, true);
-    const pages = await builder.query.lean().clone().exec('find');
-    const nonEmptyPages = pages.filter(p => !p.isEmpty);
-
-    if (nonEmptyPages.length >= 2) {
-      return res.render('layout-growi/identical-path-page', {
-        identicalPathPages: nonEmptyPages,
-        redirectFrom,
-        path,
-      });
-    }
-
-    if (nonEmptyPages.length === 1) {
-      const nonEmptyPage = nonEmptyPages[0];
-      const url = new URL('https://dummy.origin');
-
-      url.pathname = `/${nonEmptyPage._id}`;
-      Object.entries(req.query).forEach(([key, value], i) => {
-        url.searchParams.append(key, value);
-      });
-      return res.safeRedirect(urljoin(url.pathname, url.search));
-    }
-
-    // Processing of nonEmptyPage is finished by the time this code is read
-    // If any pages exist then they should be empty
-    const emptyPage = pages[0];
-    if (emptyPage != null) {
-      req.pageId = emptyPage._id;
-      req.isEmpty = emptyPage.isEmpty;
-      return _notFound(req, res);
-    }
-    // redirect by PageRedirect
-    const pageRedirect = await PageRedirect.findOne({ fromPath: path });
-    if (pageRedirect != null) {
-      return res.safeRedirect(`${encodeURI(pageRedirect.toPath)}?redirectFrom=${encodeURIComponent(path)}`);
-    }
-
-    return _notFound(req, res);
-  }
-
-  actions.redirector = async function(req, res, next) {
-    const path = getPathFromRequest(req);
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: SupportedAction.ACTION_PAGE_VIEW,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    crowi.activityService.createActivity(parameters);
-    return redirector(req, res, next, path);
-  };
-
-  actions.redirectorWithEndOfSlash = async function(req, res, next) {
-    const _path = getPathFromRequest(req);
-    const path = pathUtils.removeTrailingSlash(_path);
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: SupportedAction.ACTION_PAGE_VIEW,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    crowi.activityService.createActivity(parameters);
-
-    return redirector(req, res, next, path);
-  };
+  // actions.showPage = async function(req, res, next) {
+  //   // presentation mode
+  //   if (req.query.presentation) {
+  //     return showPageForPresentation(req, res, next);
+  //   }
+  //   // delegate to showPageForGrowiBehavior
+  //   return showPageForGrowiBehavior(req, res, next);
+  // };
 
 
   const api = {};

+ 0 - 7
packages/app/src/server/routes/private-legacy-pages.ts

@@ -1,7 +0,0 @@
-import {
-  Request, Response,
-} from 'express';
-
-export const renderPrivateLegacyPages = (req: Request, res: Response): void => {
-  return res.render('private-legacy-pages');
-};

+ 0 - 19
packages/app/src/server/routes/search.ts

@@ -40,25 +40,6 @@ module.exports = function(crowi, app) {
   const actions: any = {};
   const api: any = {};
 
-  actions.searchPage = async function(req, res) {
-    const keyword = req.query.q || null;
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: SupportedAction.ACTION_SEARCH_PAGE_VIEW,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    await crowi.activityService.createActivity(parameters);
-
-    return res.render('search', {
-      q: keyword,
-    });
-  };
-
   /**
    * @swagger
    *

+ 0 - 4
packages/app/src/server/routes/tag.js

@@ -40,10 +40,6 @@ module.exports = function(crowi, app) {
 
   actions.api = api;
 
-  actions.showPage = function(req, res) {
-    return res.render('tags');
-  };
-
   /**
    * @swagger
    *

+ 0 - 0
packages/app/src/services/renderer/PostProcessor/.keep


+ 0 - 23
packages/app/src/services/renderer/PreProcessor/CsvToTable.js

@@ -1,23 +0,0 @@
-import csvToMarkdownTable from 'csv-to-markdown-table';
-
-export default class CsvToTable {
-
-  process(markdown) {
-    // see: https://regex101.com/r/WR6IvX/3
-    return markdown.replace(/:::\s*(\S+)[\r\n]((.|[\r\n])*?)[\r\n]:::/gm, (all, group1, group2) => {
-      switch (group1) {
-        case 'tsv':
-          return csvToMarkdownTable(group2, '\t');
-        case 'tsv-h':
-          return csvToMarkdownTable(group2, '\t', true);
-        case 'csv':
-          return csvToMarkdownTable(group2, ',');
-        case 'csv-h':
-          return csvToMarkdownTable(group2, ',', true);
-        default:
-          return all;
-      }
-    });
-  }
-
-}

+ 0 - 14
packages/app/src/services/renderer/PreProcessor/Linker.js

@@ -1,14 +0,0 @@
-
-export default class Linker {
-
-  process(markdown) {
-    return markdown
-      // process angle branckets like '</Level1/Level2>'
-      // see: https://regex101.com/r/rxAy4F/2
-      .replace(/<((\/[^>\n]+?){2,})>/g, '<a href="$1">$1</a>') // ページ間リンク: <> でかこまれてて / から始まり、 / が2個以上
-      // process square branckets like '[/Level1]'
-      // see: https://regex101.com/r/QSt1yu/5
-      .replace(/\[(\/[^\]\n]+?)\](?!\()/g, '<a href="$1">$1</a>');
-  }
-
-}

+ 0 - 29
packages/app/src/services/renderer/PreProcessor/XssFilter.ts

@@ -1,29 +0,0 @@
-import Xss from '~/services/xss';
-import XssOption, { XssOptionConfig } from '~/services/xss/xssOption';
-
-export default class XssFilter {
-
-  xssOption: XssOption;
-
-  xss;
-
-  config: XssOptionConfig;
-
-  constructor(config: XssOptionConfig) {
-    this.config = config;
-
-    if (config.isEnabledXssPrevention) {
-      this.xssOption = new XssOption(config);
-      this.xss = new Xss(this.xssOption);
-    }
-  }
-
-  process(markdown) {
-    if (this.config.isEnabledXssPrevention) {
-      return this.xss.process(markdown);
-    }
-
-    return markdown;
-  }
-
-}

+ 0 - 139
packages/app/src/services/renderer/interceptor/detach-code-blocks.js

@@ -1,139 +0,0 @@
-import { BasicInterceptor } from '@growi/core';
-
-import loggerFactory from '~/utils/logger';
-
-class DetachCodeBlockUtil {
-
-  static createReplaceStr(replaceId) {
-    return `<pre class="detached-code-block">${replaceId}</pre>`;
-  }
-
-}
-
-/**
- * The interceptor that detach code blocks
- */
-export class DetachCodeBlockInterceptor extends BasicInterceptor {
-
-  constructor() {
-    super();
-    this.logger = loggerFactory('growi:interceptor:DetachCodeBlockInterceptor');
-  }
-
-  /**
-   * @inheritdoc
-   */
-  isInterceptWhen(contextName) {
-    return /^prePreProcess|prePostProcess$/.test(contextName);
-  }
-
-  getTargetKey(contextName) {
-    if (contextName === 'prePreProcess') {
-      return 'markdown';
-    }
-    if (contextName === 'prePostProcess') {
-      return 'parsedHTML';
-    }
-  }
-
-  /**
-   * @inheritdoc
-   */
-  process(contextName, ...args) {
-    this.logger.debug(`processing: 'contextName'=${contextName}`);
-
-    const context = Object.assign(args[0]); // clone
-    const targetKey = this.getTargetKey(contextName);
-
-    context.dcbContextMap = {};
-
-    // see: https://regex101.com/r/8PAEcC/5
-    // eslint-disable-next-line max-len
-    context[targetKey] = context[targetKey].replace(/(^(```|~~~)(.|[\r\n])*?(```|~~~)$)|(`[^\r\n]*?`)|(<pre>(.|[\r\n])*?<\/pre>)|(<pre\s[^>]*>(.|[\r\n])*?<\/pre>)/gm, (all) => {
-      // create ID
-      const replaceId = `dcb-${this.createRandomStr(8)}`;
-      this.logger.debug(`'replaceId'=${replaceId} : `, all);
-
-      // register to context
-      const dcbContext = {};
-      dcbContext.content = all;
-      dcbContext.substituteContent = DetachCodeBlockUtil.createReplaceStr(replaceId);
-      context.dcbContextMap[replaceId] = dcbContext;
-
-      // return substituteContent
-      return dcbContext.substituteContent;
-    });
-
-    // resolve
-    return Promise.resolve(context);
-  }
-
-  /**
-   * @see http://qiita.com/ryounagaoka/items/4736c225bdd86a74d59c
-   *
-   * @param {number} length
-   * @return random strings
-   */
-  createRandomStr(length) {
-    const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
-    let generated = '';
-    for (let i = 0; i < length; i++) {
-      generated += bag[Math.floor(Math.random() * bag.length)];
-    }
-    return generated;
-  }
-
-}
-
-
-/**
- * The interceptor that restore detached code blocks
- */
-export class RestoreCodeBlockInterceptor extends BasicInterceptor {
-
-  constructor() {
-    super();
-    this.logger = loggerFactory('growi:interceptor:DetachCodeBlockInterceptor');
-  }
-
-  /**
-   * @inheritdoc
-   */
-  isInterceptWhen(contextName) {
-    return /^postPreProcess|preRenderHtml|preRenderPreviewHtml|preRenderCommentHtml|preRenderCommentPreviewHtml$/.test(contextName);
-  }
-
-  getTargetKey(contextName) {
-    if (contextName === 'postPreProcess') {
-      return 'markdown';
-    }
-    if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
-        || contextName === 'preRenderCommentHtml' || contextName === 'preRenderCommentPreviewHtml') {
-      return 'parsedHTML';
-    }
-  }
-
-  /**
-   * @inheritdoc
-   */
-  process(contextName, ...args) {
-    this.logger.debug(`processing: 'contextName'=${contextName}`);
-
-    const context = Object.assign(args[0]); // clone
-    const targetKey = this.getTargetKey(contextName);
-
-    // forEach keys of dcbContextMap
-    Object.keys(context.dcbContextMap).forEach((replaceId) => {
-      // get context object from context
-      const dcbContext = context.dcbContextMap[replaceId];
-
-      // replace it with content by using getter function so that the doller sign does not work
-      // see: https://github.com/weseek/growi/issues/285
-      context[targetKey] = context[targetKey].replace(dcbContext.substituteContent, () => { return dcbContext.content });
-    });
-
-    // resolve
-    return Promise.resolve(context);
-  }
-
-}

+ 0 - 0
packages/app/src/services/renderer/PreProcessor/EasyGrid.js → packages/app/src/services/renderer/markdown-it/PreProcessor/EasyGrid.js


+ 0 - 9
packages/app/src/services/renderer/markdown-it/drawio-viewer.js

@@ -1,9 +0,0 @@
-export default class DrawioViewerConfigurer {
-
-  configure(md) {
-    // md.use(require('markdown-it-drawio-viewer'), {
-    //   marker: ':::',
-    // });
-  }
-
-}

+ 0 - 66
packages/app/src/services/renderer/markdown-it/emoji-mart-data.ts

@@ -1,66 +0,0 @@
-import { Emoji } from 'emoji-mart';
-import data from 'emoji-mart/data/apple.json';
-
-const DEFAULT_EMOJI_SIZE = 24;
-
-
-type EmojiMap = {
-  [key: string]: string,
-};
-
-/**
- *
- * Get native emoji with skin tone
- * @param skin number
- * @returns emoji data with skin tone
- */
-const getEmojiSkinTone = (emojiName: string): EmojiMap => {
-  const emojiData = {};
-  [...Array(6).keys()].forEach((index) => {
-    if (index > 0) {
-      const elem = Emoji({
-        emoji: emojiName,
-        skin: index + 1,
-        size: DEFAULT_EMOJI_SIZE,
-      });
-      if (elem) {
-        emojiData[`${emojiName}::skin-tone-${index + 1}`] = elem.props['aria-label'].split(',')[0];
-      }
-    }
-  });
-  return emojiData;
-};
-
-/**
- * Get native emoji from emoji array
- * @returns emoji data
- */
-
-const getNativeEmoji = (): EmojiMap => {
-  const emojiData = {};
-  Object.entries(data.emojis).forEach((emoji) => {
-    const emojiName = emoji[0];
-    const hasSkinVariation = 'skin_variations' in emoji[1];
-
-    const elem = Emoji({
-      emoji: emojiName,
-      size: DEFAULT_EMOJI_SIZE,
-    });
-
-    if (elem != null) {
-      emojiData[emojiName] = elem.props['aria-label'].split(',')[0];
-      if (hasSkinVariation) {
-        const emojiWithSkinTone = getEmojiSkinTone(emojiName);
-        Object.assign(emojiData, emojiWithSkinTone);
-      }
-    }
-  });
-
-  return emojiData;
-};
-
-/**
- * Get native emoji mart data
- * @returns native emoji mart data
- */
-export const emojiMartData = getNativeEmoji();

+ 0 - 12
packages/app/src/services/renderer/markdown-it/emoji.js

@@ -1,12 +0,0 @@
-// import markdownItEmojiMart from 'markdown-it-emoji-mart';
-
-import { emojiMartData } from './emoji-mart-data';
-
-
-export default class EmojiConfigurer {
-
-  configure(md) {
-    // md.use(markdownItEmojiMart, { defs: emojiMartData });
-  }
-
-}

+ 0 - 7
packages/app/src/services/renderer/markdown-it/footernote.js

@@ -1,7 +0,0 @@
-export default class FooternoteConfigurer {
-
-  configure(md) {
-    // md.use(require('markdown-it-footnote'));
-  }
-
-}

+ 0 - 34
packages/app/src/services/renderer/markdown-it/header-line-number.js

@@ -1,34 +0,0 @@
-export default class HeaderLineNumberConfigurer {
-
-  constructor() {
-    this.firstLine = 0;
-  }
-
-  configure(md) {
-    for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'blockquote_open', 'list_item_open']) {
-      this.addLineNumberRenderer(md, renderName);
-    }
-  }
-
-  /**
-   * Add line numbers for sync scroll
-   * @see https://github.com/Microsoft/vscode/blob/6e8d4d057bd1152d49a1e9780ec6db6363593855/extensions/markdown/src/markdownEngine.ts#L118
-   */
-  addLineNumberRenderer(md, ruleName) {
-    const original = md.renderer.rules[ruleName];
-    md.renderer.rules[ruleName] = (tokens, idx, options, env, self) => {
-      const token = tokens[idx];
-      if (token.map && token.map.length) {
-        token.attrSet('data-line', this.firstLine + token.map[0]);
-        token.attrJoin('class', 'code-line');
-      }
-
-      if (original) {
-        return original(tokens, idx, options, env, self);
-      }
-
-      return self.renderToken(tokens, idx, options, env, self);
-    };
-  }
-
-}

+ 0 - 13
packages/app/src/services/renderer/markdown-it/header-with-edit-link.js

@@ -1,13 +0,0 @@
-export default class HeaderWithEditLinkConfigurer {
-
-  configure(md) {
-    md.renderer.rules.heading_close = (tokens, idx) => {
-      return `<span class="revision-head-edit-button">
-                <a href="#edit" onClick="Crowi.setCaretLine(parseInt(this.parentNode.parentNode.dataset.line, 10))">
-                  <i class="icon-note"></i>
-                </a>
-              </span></${tokens[idx].tag}>`;
-    };
-  }
-
-}

+ 0 - 33
packages/app/src/services/renderer/markdown-it/header.js

@@ -1,33 +0,0 @@
-export default class HeaderConfigurer {
-
-  constructor() {
-    this.injectRevisionHeadClass = this.injectRevisionHeadClass.bind(this);
-  }
-
-  configure(md) {
-    const rules = md.renderer.rules;
-    const original = rules.heading_open;
-    // combine rule and set
-    // rules.heading_open = this.combineRules(this.injectRevisionHeadClass, headingOpenOrg);
-    rules.heading_open = (tokens, idx, options, env, self) => {
-      // Inject 'revision-head' class
-      this.injectRevisionHeadClass(tokens, idx, options, env, self);
-
-      if (original) {
-        return original(tokens, idx, options, env, self);
-      }
-
-      return self.renderToken(tokens, idx, options, env, self);
-    };
-  }
-
-  /**
-   * Inject 'revision-head' class
-   */
-  injectRevisionHeadClass(tokens, idx, options, env, slf) {
-    if (tokens[idx].map && tokens[idx].level === 0) {
-      tokens[idx].attrJoin('class', 'revision-head');
-    }
-  }
-
-}

+ 0 - 48
packages/app/src/services/renderer/markdown-it/link-by-relative-path.ts

@@ -1,48 +0,0 @@
-import path from 'path';
-
-// https://regex101.com/r/vV8LUe/1
-const PATTERN_RELATIVE_PATH = new RegExp(/^(\.{1,2})(\/.*)?$/);
-
-export default class LinkerByRelativePathConfigurer {
-
-  pagePath: string;
-
-  constructor(pagePath: string) {
-    this.pagePath = pagePath;
-  }
-
-  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-  configure(md): void {
-    const pagePath = this.pagePath;
-
-    // Remember old renderer, if overridden, or proxy to default renderer
-    const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
-      return self.renderToken(tokens, idx, options);
-    };
-
-    md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
-      if (tokens[idx] == null || (typeof tokens[idx].attrIndex !== 'function')) {
-        return defaultRender(tokens, idx, options, env, self);
-      }
-
-      // get href
-      const hrefIndex = tokens[idx].attrIndex('href');
-
-      if (hrefIndex != null && hrefIndex >= 0) {
-        const href: string = tokens[idx].attrs[hrefIndex][1];
-        const currentPath: string | null = pagePath;
-
-        // resolve relative path and replace
-        if (PATTERN_RELATIVE_PATH.test(href) && currentPath != null) {
-          const newHref = path.resolve(path.dirname(currentPath), href);
-          tokens[idx].attrs[hrefIndex][1] = newHref;
-        }
-      }
-
-      // pass token to default renderer.
-      return defaultRender(tokens, idx, options, env, self);
-    };
-
-  }
-
-}

+ 0 - 7
packages/app/src/services/renderer/markdown-it/mathjax.js

@@ -1,7 +0,0 @@
-export default class MathJaxConfigurer {
-
-  configure(md) {
-    // md.use(require('markdown-it-mathjax')());
-  }
-
-}

+ 0 - 28
packages/app/src/services/renderer/markdown-it/plantuml.ts

@@ -1,28 +0,0 @@
-import plantumlEncoder from 'plantuml-encoder';
-import urljoin from 'url-join';
-
-import { RendererConfig } from '~/interfaces/services/renderer';
-
-export default class PlantUMLConfigurer {
-
-  serverUrl: string;
-
-  constructor(config: RendererConfig) {
-    // Do NOT use HTTPS URL because plantuml.com refuse request except from members
-    this.serverUrl = config.plantumlUri || 'http://plantuml.com/plantuml';
-
-    this.generateSource = this.generateSource.bind(this);
-  }
-
-  configure(md) {
-    // md.use(require('markdown-it-plantuml'), {
-    //   generateSource: this.generateSource,
-    // });
-  }
-
-  generateSource(umlCode) {
-    const zippedCode = plantumlEncoder.encode(`@startuml\n${umlCode}\n@enduml`);
-    return urljoin(this.serverUrl, 'svg', zippedCode);
-  }
-
-}

+ 0 - 16
packages/app/src/services/renderer/markdown-it/table-with-handsontable-button.js

@@ -1,16 +0,0 @@
-export default class TableWithHandsontableButtonConfigurer {
-
-  configure(md) {
-    md.renderer.rules.table_open = (tokens, idx) => {
-      const beginLine = tokens[idx].map[0] + 1;
-      const endLine = tokens[idx].map[1];
-      // eslint-disable-next-line max-len
-      return `<div class="editable-with-handsontable"><button class="handsontable-modal-trigger" onClick="globalEmitter.emit('launchHandsontableModal', ${beginLine}, ${endLine})"><i class="icon-note"></i></button><table class="table table-bordered">`;
-    };
-
-    md.renderer.rules.table_close = (tokens, idx) => {
-      return '</table></div>';
-    };
-  }
-
-}

+ 0 - 9
packages/app/src/services/renderer/markdown-it/table.js

@@ -1,9 +0,0 @@
-export default class TableConfigurer {
-
-  configure(md) {
-    md.renderer.rules.table_open = (tokens, idx) => {
-      return '<table class="table table-bordered">';
-    };
-  }
-
-}

+ 0 - 14
packages/app/src/services/renderer/markdown-it/task-lists.js

@@ -1,14 +0,0 @@
-export default class TaskListsConfigurer {
-
-  configure(md) {
-    // md.use(require('markdown-it-task-checkbox'), {
-    //   disabled: true,
-    //   divWrap: true,
-    //   divClass: 'checkbox checkbox-primary',
-    //   idPrefix: 'cbx_',
-    //   ulClass: 'task-list',
-    //   liClass: 'task-list-item',
-    // });
-  }
-
-}

+ 0 - 27
packages/app/src/services/renderer/markdown-it/toc-and-anchor.js

@@ -1,27 +0,0 @@
-// import markdownItEmojiMart from 'markdown-it-emoji-mart';
-// import markdownItToc from 'markdown-it-toc-and-anchor-with-slugid';
-
-import { emojiMartData } from './emoji-mart-data';
-
-export default class TocAndAnchorConfigurer {
-
-  configure(md) {
-    // md.use(markdownItEmojiMart, { defs: emojiMartData })
-    //   .use(markdownItToc, {
-    //     tocLastLevel: 3,
-    //     anchorLinkBefore: false,
-    //     anchorLinkSymbol: '',
-    //     anchorLinkSymbolClassName: 'icon-link',
-    //     anchorClassName: 'revision-head-link',
-    //   });
-
-    // set toc render function
-    md.set({
-      tocCallback: (tocMarkdown, tocArray, tocHtml) => {
-        // eslint-disable-next-line no-undef
-        globalEmitter.emit('renderTocHtml', tocHtml);
-      },
-    });
-  }
-
-}

+ 35 - 259
packages/app/src/services/renderer/renderer.tsx

@@ -44,199 +44,12 @@ import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
 import * as table from './remark-plugins/table';
 import * as xsvToTable from './remark-plugins/xsv-to-table';
 
-// import CsvToTable from './PreProcessor/CsvToTable';
 // import EasyGrid from './PreProcessor/EasyGrid';
-// import Linker from './PreProcessor/Linker';
-// import XssFilter from './PreProcessor/XssFilter';
 // import BlockdiagConfigurer from './markdown-it/blockdiag';
-// import DrawioViewerConfigurer from './markdown-it/drawio-viewer';
-// import EmojiConfigurer from './markdown-it/emoji';
-// import FooternoteConfigurer from './markdown-it/footernote';
-// import HeaderConfigurer from './markdown-it/header';
-// import HeaderLineNumberConfigurer from './markdown-it/header-line-number';
-// import HeaderWithEditLinkConfigurer from './markdown-it/header-with-edit-link';
-// import LinkerByRelativePathConfigurer from './markdown-it/link-by-relative-path';
-// import MathJaxConfigurer from './markdown-it/mathjax';
-// import PlantUMLConfigurer from './markdown-it/plantuml';
-// import TableConfigurer from './markdown-it/table';
-// import TableWithHandsontableButtonConfigurer from './markdown-it/table-with-handsontable-button';
-// import TaskListsConfigurer from './markdown-it/task-lists';
-// import TocAndAnchorConfigurer from './markdown-it/toc-and-anchor';
 
 
 const logger = loggerFactory('growi:util:GrowiRenderer');
 
-// declare const hljs;
-
-// type MarkdownSettings = {
-//   breaks?: boolean,
-// };
-
-// export default class GrowiRenderer {
-
-//   RendererConfig: RendererConfig;
-
-//   constructor(RendererConfig: RendererConfig, pagePath?: Nullable<string>) {
-//     this.RendererConfig = RendererConfig;
-//     this.pagePath = pagePath;
-
-//     if (isClient() && (window as CustomWindow).growiRenderer != null) {
-//       this.preProcessors = (window as CustomWindow).growiRenderer.preProcessors;
-//       this.postProcessors = (window as CustomWindow).growiRenderer.postProcessors;
-//     }
-//     else {
-//       this.preProcessors = [
-//         new EasyGrid(),
-//         new Linker(),
-//         new CsvToTable(),
-//         new XssFilter({
-//           isEnabledXssPrevention: this.RendererConfig.isEnabledXssPrevention,
-//           tagWhiteList: this.RendererConfig.tagWhiteList,
-//           attrWhiteList: this.RendererConfig.attrWhiteList,
-//         }),
-//       ];
-//       this.postProcessors = [
-//       ];
-//     }
-
-//     this.init = this.init.bind(this);
-//     this.addConfigurers = this.addConfigurers.bind(this);
-//     this.setMarkdownSettings = this.setMarkdownSettings.bind(this);
-//     this.configure = this.configure.bind(this);
-//     this.process = this.process.bind(this);
-//     this.codeRenderer = this.codeRenderer.bind(this);
-//   }
-
-//   init() {
-//     let parser: Processor = unified().use(parse);
-//     this.remarkPlugins.forEach((item) => {
-//       parser = applyPlugin(parser, item);
-//     });
-
-//     let rehype: Processor = parser.use(remark2rehype);
-//     this.rehypePlugins.forEach((item) => {
-//       rehype = applyPlugin(rehype, item);
-//     });
-
-//     this.processor = rehype.use(rehype2react, {
-//       createElement: React.createElement,
-//       components: {
-//         // a: NextLink,
-//       },
-//     });
-//   }
-
-//   init() {
-//     // init markdown-it
-//     this.md = new MarkdownIt({
-//       html: true,
-//       linkify: true,
-//       highlight: this.codeRenderer,
-//     });
-
-//     this.isMarkdownItConfigured = false;
-
-//     this.markdownItConfigurers = [
-//       new TaskListsConfigurer(),
-//       new HeaderConfigurer(),
-//       new EmojiConfigurer(),
-//       new MathJaxConfigurer(),
-//       new DrawioViewerConfigurer(),
-//       new PlantUMLConfigurer(this.RendererConfig),
-//       new BlockdiagConfigurer(this.RendererConfig),
-//     ];
-
-//     if (this.pagePath != null) {
-//       this.markdownItConfigurers.push(
-//         new LinkerByRelativePathConfigurer(this.pagePath),
-//       );
-//     }
-//   }
-
-//   addConfigurers(configurers: any[]): void {
-//     this.markdownItConfigurers.push(...configurers);
-//   }
-
-//   setMarkdownSettings(settings: MarkdownSettings): void {
-//     this.md.set(settings);
-//   }
-
-//   configure(): void {
-//     if (!this.isMarkdownItConfigured) {
-//       this.markdownItConfigurers.forEach((configurer) => {
-//         configurer.configure(this.md);
-//       });
-//     }
-//   }
-
-//   preProcess(markdown, context) {
-//     let processed = markdown;
-//     for (let i = 0; i < this.preProcessors.length; i++) {
-//       if (!this.preProcessors[i].process) {
-//         continue;
-//       }
-//       processed = this.preProcessors[i].process(processed, context);
-//     }
-
-//     return processed;
-//   }
-
-//   process(markdown, context) {
-//     return this.md.render(markdown, context);
-//   }
-
-//   postProcess(html, context) {
-//     let processed = html;
-//     for (let i = 0; i < this.postProcessors.length; i++) {
-//       if (!this.postProcessors[i].process) {
-//         continue;
-//       }
-//       processed = this.postProcessors[i].process(processed, context);
-//     }
-
-//     return processed;
-//   }
-
-//   codeRenderer(code, langExt) {
-//     const noborder = (!this.RendererConfig.highlightJsStyleBorder) ? 'hljs-no-border' : '';
-
-//     let citeTag = '';
-//     let hljsLang = 'plaintext';
-//     let showLinenumbers = false;
-
-//     if (langExt) {
-//       // https://regex101.com/r/qGs7eZ/3
-//       const match = langExt.match(/^([^:=\n]+)?(=([^:=\n]*))?(:([^:=\n]*))?(=([^:=\n]*))?$/);
-
-//       const lang = match[1];
-//       const fileName = match[5] || null;
-//       showLinenumbers = (match[2] != null) || (match[6] != null);
-
-//       if (fileName != null) {
-//         citeTag = `<cite>${fileName}</cite>`;
-//       }
-//       if (hljs.getLanguage(lang)) {
-//         hljsLang = lang;
-//       }
-//     }
-
-//     let highlightCode = code;
-//     try {
-//       highlightCode = hljs.highlight(hljsLang, code, true).value;
-
-//       // add line numbers
-//       if (showLinenumbers) {
-//         highlightCode = hljs.lineNumbersValue((highlightCode));
-//       }
-//     }
-//     catch (err) {
-//       logger.error(err);
-//     }
-
-//     return `<pre class="hljs ${noborder}">${citeTag}<code>${highlightCode}</code></pre>`;
-//   }
-
-// }
 
 type SanitizePlugin = PluginTuple<[SanitizeOption]>;
 export type RendererOptions = Omit<ReactMarkdownOptions, 'remarkPlugins' | 'rehypePlugins' | 'components' | 'children'> & {
@@ -290,7 +103,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, config: RendererConfig): RendererOptions => {
+const generateCommonOptions = (pagePath: string|undefined): RendererOptions => {
   return {
     remarkPlugins: [
       gfm,
@@ -319,7 +132,7 @@ export const generateViewOptions = (
     storeTocNode: (toc: HtmlElementNode) => void,
 ): RendererOptions => {
 
-  const options = generateCommonOptions(pagePath, config);
+  const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
 
@@ -336,24 +149,20 @@ export const generateViewOptions = (
   }
 
   const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(commonSanitizeOption, lsxGrowiPlugin.sanitizeOption)]
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawioPlugin.sanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+    )]
     : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
     slug,
     [lsxGrowiPlugin.rehypePlugin, { pagePath }],
-    [sanitize, deepmerge(
-      commonSanitizeOption,
-      drawioPlugin.sanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-    )],
     rehypeSanitizePlugin,
     katex,
     [toc.rehypePluginStore, { storeTocNode }],
-    // [autoLinkHeadings, {
-    //   behavior: 'append',
-    // }]
   );
 
   // add components
@@ -366,18 +175,6 @@ export const generateViewOptions = (
     components.table = TableWithEditButton;
   }
 
-  // // Add configurers for viewer
-  // renderer.addConfigurers([
-  //   new FooternoteConfigurer(),
-  //   new TocAndAnchorConfigurer(),
-  //   new HeaderLineNumberConfigurer(),
-  //   new HeaderWithEditLinkConfigurer(),
-  //   new TableWithHandsontableButtonConfigurer(),
-  // ]);
-
-  // renderer.setMarkdownSettings({ breaks: rendererSettings.isEnabledLinebreaks });
-  // renderer.configure();
-
   if (config.isEnabledXssPrevention) {
     verifySanitizePlugin(options, false);
   }
@@ -386,16 +183,17 @@ export const generateViewOptions = (
 
 export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
 
-  const options = generateCommonOptions(undefined, config);
+  const options = generateCommonOptions(undefined);
 
   const { rehypePlugins } = options;
 
   // add remark plugins
   // remarkPlugins.push();
 
-
   const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(commonSanitizeOption, lsxGrowiPlugin.sanitizeOption)]
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+    )]
     : () => {};
 
   // add rehype plugins
@@ -403,9 +201,6 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
     [toc.rehypePluginRestore, { tocNode }],
     rehypeSanitizePlugin,
   );
-  // renderer.rehypePlugins.push([autoLinkHeadings, {
-  //   behavior: 'append',
-  // }]);
 
   if (config.isEnabledXssPrevention) {
     verifySanitizePlugin(options);
@@ -413,8 +208,13 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
   return options;
 };
 
-export const generateSimpleViewOptions = (config: RendererConfig, pagePath: string, highlightKeywords?: string | string[]): RendererOptions => {
-  const options = generateCommonOptions(pagePath, config);
+export const generateSimpleViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+    highlightKeywords?: string | string[],
+    overrideIsEnabledLinebreaks?: boolean,
+): RendererOptions => {
+  const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
 
@@ -427,23 +227,25 @@ export const generateSimpleViewOptions = (config: RendererConfig, pagePath: stri
     lsxGrowiPlugin.remarkPlugin,
     table.remarkPlugin,
   );
-  if (config.isEnabledLinebreaks) {
+
+  const isEnabledLinebreaks = overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
+
+  if (isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
   }
 
   const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(commonSanitizeOption, lsxGrowiPlugin.sanitizeOption)]
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawioPlugin.sanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+    )]
     : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
     [lsxGrowiPlugin.rehypePlugin, { pagePath }],
     [keywordHighlighter.rehypePlugin, { keywords: highlightKeywords }],
-    [sanitize, deepmerge(
-      commonSanitizeOption,
-      drawioPlugin.sanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-    )],
     rehypeSanitizePlugin,
     katex,
   );
@@ -462,7 +264,7 @@ export const generateSimpleViewOptions = (config: RendererConfig, pagePath: stri
 };
 
 export const generatePreviewOptions = (config: RendererConfig, pagePath: string): RendererOptions => {
-  const options = generateCommonOptions(pagePath, config);
+  const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
 
@@ -480,19 +282,18 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
   }
 
   const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(commonSanitizeOption, lsxGrowiPlugin.sanitizeOption, addLineNumberAttribute.sanitizeOption)]
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+      drawioPlugin.sanitizeOption,
+      addLineNumberAttribute.sanitizeOption,
+    )]
     : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
     [lsxGrowiPlugin.rehypePlugin, { pagePath }],
     addLineNumberAttribute.rehypePlugin,
-    [sanitize, deepmerge(
-      commonSanitizeOption,
-      lsxGrowiPlugin.sanitizeOption,
-      drawioPlugin.sanitizeOption,
-      addLineNumberAttribute.sanitizeOption,
-    )],
     rehypeSanitizePlugin,
     katex,
   );
@@ -504,37 +305,12 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     components.table = Table;
   }
 
-  // verifySanitizePlugin(options, false);
-  return options;
-};
-
-export const generateOthersOptions = (config: RendererConfig): RendererOptions => {
-  const options = generateCommonOptions(undefined, config);
-  const { rehypePlugins } = options;
-
-  // renderer.addConfigurers([
-  //   new TableConfigurer(),
-  // ]);
-
-  // renderer.setMarkdownSettings({ breaks: rendererSettings.isEnabledLinebreaks });
-  // renderer.configure();
-
-  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(commonSanitizeOption)]
-    : () => {};
-
-  // add rehype plugins
-  rehypePlugins.push(
-    rehypeSanitizePlugin,
-  );
-
   if (config.isEnabledXssPrevention) {
-    verifySanitizePlugin(options);
+    verifySanitizePlugin(options, false);
   }
   return options;
 };
 
-
 // register to facade
 if (isClient()) {
   registerGrowiFacade({

+ 29 - 43
packages/app/src/stores/renderer.tsx

@@ -1,11 +1,10 @@
 import { HtmlElementNode } from 'rehype-toc';
-import useSWR, { Key, SWRResponse } from 'swr';
+import useSWR, { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
-import { RendererConfig } from '~/interfaces/services/renderer';
 import {
   RendererOptions,
-  generateSimpleViewOptions, generatePreviewOptions, generateOthersOptions,
+  generateSimpleViewOptions, generatePreviewOptions,
   generateViewOptions, generateTocOptions,
 } from '~/services/renderer/renderer';
 import { getGrowiFacade } from '~/utils/growi-facade';
@@ -17,31 +16,6 @@ import {
 import { useCurrentPagePath } from './page';
 import { useCurrentPageTocNode } from './ui';
 
-interface ReactMarkdownOptionsGenerator {
-  (config: RendererConfig): RendererOptions
-}
-
-// The base hook with common processes
-const _useOptionsBase = (
-    rendererId: string, generator: ReactMarkdownOptionsGenerator,
-): SWRResponse<RendererOptions, Error> => {
-  const { data: rendererConfig } = useRendererConfig();
-
-  const isAllDataValid = rendererConfig != null;
-
-  const key = isAllDataValid
-    ? [rendererId, rendererConfig]
-    : null;
-
-  const swrResult = useSWRImmutable<RendererOptions, Error>(key);
-
-  if (isAllDataValid && swrResult.data == null) {
-    swrResult.mutate(generator(rendererConfig));
-  }
-
-  // call useSWRImmutable again to foce to update cache
-  return useSWRImmutable<RendererOptions, Error>(key);
-};
 
 export const useViewOptions = (storeTocNodeHandler: (toc: HtmlElementNode) => void): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
@@ -113,14 +87,24 @@ export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions,
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
 
   const key = isAllDataValid
-    ? ['commentForCurrentPageOptions', rendererConfig, currentPagePath]
+    ? ['commentPreviewOptions', rendererConfig, currentPagePath]
     : null;
 
   return useSWRImmutable<RendererOptions, Error>(
     key,
-    (rendererId, rendererConfig, currentPagePath) => generateSimpleViewOptions(rendererConfig, currentPagePath),
+    (rendererId, rendererConfig, currentPagePath) => generateSimpleViewOptions(
+      rendererConfig,
+      currentPagePath,
+      undefined,
+      rendererConfig.isEnabledLinebreaksInComments,
+    ),
     {
-      fallbackData: isAllDataValid ? generateSimpleViewOptions(rendererConfig, currentPagePath) : undefined,
+      fallbackData: isAllDataValid ? generateSimpleViewOptions(
+        rendererConfig,
+        currentPagePath,
+        undefined,
+        rendererConfig.isEnabledLinebreaksInComments,
+      ) : undefined,
     },
   );
 };
@@ -145,20 +129,22 @@ export const useSelectedPagePreviewOptions = (pagePath: string, highlightKeyword
 };
 export const useSearchResultOptions = useSelectedPagePreviewOptions;
 
-export const useTimelineOptions = (): SWRResponse<RendererOptions, Error> => {
-  const key = 'timelineOptions';
-
-  return _useOptionsBase(key, generateOthersOptions);
-};
+export const useTimelineOptions = useSelectedPagePreviewOptions;
 
-export const useDraftOptions = (): SWRResponse<RendererOptions, Error> => {
-  const key = 'draftOptions';
+export const useCustomSidebarOptions = (): SWRResponse<RendererOptions, Error> => {
+  const { data: rendererConfig } = useRendererConfig();
 
-  return _useOptionsBase(key, generateOthersOptions);
-};
+  const isAllDataValid = rendererConfig != null;
 
-export const useCustomSidebarOptions = (): SWRResponse<RendererOptions, Error> => {
-  const key: Key = 'customSidebarOptions';
+  const key = isAllDataValid
+    ? ['customSidebarOptions', rendererConfig]
+    : null;
 
-  return _useOptionsBase(key, generateOthersOptions);
+  return useSWRImmutable<RendererOptions, Error>(
+    key,
+    (rendererId, rendererConfig, pagePath, highlightKeywords) => generateSimpleViewOptions(rendererConfig, pagePath, highlightKeywords),
+    {
+      fallbackData: isAllDataValid ? generateSimpleViewOptions(rendererConfig, '/') : undefined,
+    },
+  );
 };

+ 8 - 4
packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts

@@ -13,7 +13,8 @@ context('Access to page', () => {
     cy.waitUntilSkeletonDisappear();
 
     // for check download toc data
-    cy.get('.toc-link').eq(0).contains('Table of Contents');
+    // https://redmine.weseek.co.jp/issues/111384
+    // cy.get('.toc-link').should('be.visible');
 
     cy.collapseSidebar(true, true);
     cy.screenshot(`${ssPrefix}-sandbox`);
@@ -25,7 +26,8 @@ context('Access to page', () => {
     cy.waitUntilSkeletonDisappear();
 
     // for check download toc data
-    cy.get('.toc-link').should('be.visible');
+    // https://redmine.weseek.co.jp/issues/111384
+    // cy.get('.toc-link').should('be.visible');
 
     // hide fab // disable fab for sticky-events warning
     // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
@@ -43,7 +45,8 @@ context('Access to page', () => {
     cy.waitUntilSkeletonDisappear();
 
     // for check download toc data
-    cy.get('.toc-link').should('be.visible');
+    // https://redmine.weseek.co.jp/issues/111384
+    // cy.get('.toc-link').should('be.visible');
 
     cy.get('.math').should('be.visible');
 
@@ -68,7 +71,8 @@ context('Access to page', () => {
 
     cy.waitUntilSkeletonDisappear();
     // for check download toc data
-    cy.get('.toc-link').should('be.visible');
+    // https://redmine.weseek.co.jp/issues/111384
+    // cy.get('.toc-link').should('be.visible');
 
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(2000); // wait for calcViewHeight and rendering

+ 2 - 1
packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts

@@ -30,7 +30,8 @@ context('Access to page by guest', () => {
     cy.waitUntilSkeletonDisappear();
 
     // for check download toc data
-    cy.get('.toc-link').should('be.visible');
+    // https://redmine.weseek.co.jp/issues/111384
+    // cy.get('.toc-link').should('be.visible');
 
     cy.get('.math').should('be.visible');
 

+ 0 - 117
packages/app/test/integration/models/share-link.test.js

@@ -1,117 +0,0 @@
-const { getInstance } = require('../setup-crowi');
-
-describe('ShareLink', () => {
-  // eslint-disable-next-line no-unused-vars
-  let crowi;
-  let ShareLink;
-  let Page;
-
-  beforeAll(async() => {
-    crowi = await getInstance();
-    ShareLink = crowi.model('ShareLink');
-    Page = require('~/server/routes/page')(crowi);
-  });
-
-  describe('accessShareLink', () => {
-    const req = {
-      path: '/share/:id',
-      params: {
-        linkId: 'someLinkId',
-      },
-      query: {
-        revision: 'someRevision',
-      },
-    };
-
-    const res = {
-      render: jest.fn((page, renderVars = null) => { return { page, renderVars } }),
-    };
-
-    const findOneResult = {
-      populate: null,
-    };
-
-    const relatedPage = {
-      path: '/somePath',
-      populateDataToShowRevision: () => {
-        return {
-          revision: {
-            author: {
-              toObject: jest.fn(() => { return {} }),
-            },
-          },
-          creator: {
-            toObject: jest.fn(() => { return {} }),
-          },
-        };
-      },
-      initLatestRevisionField: (revisionId) => {
-        return revisionId;
-      },
-    };
-
-    test('share link is not found', async() => {
-
-      findOneResult.populate = jest.fn(() => { return null });
-
-      jest.spyOn(ShareLink, 'findOne').mockImplementation(() => {
-        return findOneResult;
-      });
-
-      const response = await Page.showSharedPage(req, res);
-
-      expect(findOneResult.populate).toHaveBeenCalled();
-      expect(res.render).toHaveBeenCalled();
-      expect(response.page).toEqual('layout-growi/not_found_shared_page');
-      expect(response.renderVars).toEqual(null);
-    });
-
-    test('share link is found, but it does not have Page', async() => {
-
-      findOneResult.populate = jest.fn(() => { return { _id: 'somePageId' } });
-
-      jest.spyOn(ShareLink, 'findOne').mockImplementation(() => {
-        return findOneResult;
-      });
-      const response = await Page.showSharedPage(req, res);
-
-      expect(findOneResult.populate).toHaveBeenCalled();
-      expect(res.render).toHaveBeenCalled();
-      expect(response.page).toEqual('layout-growi/not_found_shared_page');
-      expect(response.renderVars).toEqual(null);
-    });
-
-
-    test('share link is found, but it is expired', async() => {
-
-      findOneResult.populate = jest.fn(() => { return { _id: 'somePageId', relatedPage, isExpired: () => { return true } } });
-
-      jest.spyOn(ShareLink, 'findOne').mockImplementation(() => {
-        return findOneResult;
-      });
-
-      const response = await Page.showSharedPage(req, res);
-
-      expect(findOneResult.populate).toHaveBeenCalled();
-      expect(res.render).toHaveBeenCalled();
-      expect(response.page).toEqual('layout-growi/expired_shared_page');
-      expect(response.renderVars).not.toEqual(null);
-    });
-
-    test('share link is found, and it has the page you can see', async() => {
-
-      findOneResult.populate = jest.fn(() => { return { _id: 'somePageId', relatedPage, isExpired: () => { return false } } });
-
-      jest.spyOn(ShareLink, 'findOne').mockImplementation(() => {
-        return findOneResult;
-      });
-      const response = await Page.showSharedPage(req, res);
-
-      expect(findOneResult.populate).toHaveBeenCalled();
-      expect(res.render).toHaveBeenCalled();
-      expect(response.page).toEqual('layout-growi/shared_page');
-      expect(response.renderVars).not.toEqual(null);
-    });
-  });
-
-});