_search.page.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import type { ReactNode } from 'react';
  2. import type { IUser } from '@growi/core';
  3. import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
  4. import { useTranslation } from 'next-i18next';
  5. import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
  6. import dynamic from 'next/dynamic';
  7. import Head from 'next/head';
  8. import SearchResultLayout from '~/components-universal/Layout/SearchResultLayout';
  9. import { DrawioViewerScript } from '~/components-universal/Script/DrawioViewerScript';
  10. import type { CrowiRequest } from '~/interfaces/crowi-request';
  11. import type { RendererConfig } from '~/interfaces/services/renderer';
  12. import type { ISidebarConfig } from '~/interfaces/sidebar-config';
  13. import {
  14. useCsrfToken, useCurrentUser, useIsContainerFluid, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
  15. useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useShowPageLimitationL, useGrowiCloudUri, useCurrentPathname,
  16. } from '~/stores/context';
  17. import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
  18. import type { NextPageWithLayout } from './_app.page';
  19. import type { CommonProps } from './utils/commons';
  20. import {
  21. getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle, useInitSidebarConfig,
  22. } from './utils/commons';
  23. const SearchPage = dynamic(() => import('../components/SearchPage').then(mod => mod.SearchPage), { ssr: false });
  24. type Props = CommonProps & {
  25. currentUser: IUser,
  26. isSearchServiceConfigured: boolean,
  27. isSearchServiceReachable: boolean,
  28. isSearchScopeChildrenAsDefault: boolean,
  29. // Render config
  30. rendererConfig: RendererConfig,
  31. // search limit
  32. showPageLimitationL: number
  33. isContainerFluid: boolean,
  34. sidebarConfig: ISidebarConfig,
  35. };
  36. const SearchResultPage: NextPageWithLayout<Props> = (props: Props) => {
  37. const { t } = useTranslation();
  38. // commons
  39. useCsrfToken(props.csrfToken);
  40. useGrowiCloudUri(props.growiCloudUri);
  41. useCurrentUser(props.currentUser ?? null);
  42. // clear the cache for the current page
  43. // in order to fix https://redmine.weseek.co.jp/issues/135811
  44. useSWRxCurrentPage(null);
  45. useCurrentPageId(null);
  46. useCurrentPathname('/_search');
  47. // Search
  48. useIsSearchPage(true);
  49. useIsSearchServiceConfigured(props.isSearchServiceConfigured);
  50. useIsSearchServiceReachable(props.isSearchServiceReachable);
  51. useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault);
  52. // init sidebar config with UserUISettings and sidebarConfig
  53. useInitSidebarConfig(props.sidebarConfig, props.userUISettings);
  54. // render config
  55. useRendererConfig(props.rendererConfig);
  56. useShowPageLimitationL(props.showPageLimitationL);
  57. useIsContainerFluid(props.isContainerFluid);
  58. const title = generateCustomTitle(props, t('search_result.title'));
  59. return (
  60. <>
  61. <Head>
  62. <title>{title}</title>
  63. </Head>
  64. <SearchPage />
  65. </>
  66. );
  67. };
  68. type LayoutProps = Props & {
  69. sidebarConfig: ISidebarConfig,
  70. children?: ReactNode,
  71. }
  72. const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
  73. // init sidebar config with UserUISettings and sidebarConfig
  74. useInitSidebarConfig(props.sidebarConfig, props.userUISettings);
  75. return (
  76. <SearchResultLayout>
  77. {children}
  78. </SearchResultLayout>
  79. );
  80. };
  81. SearchResultPage.getLayout = function getLayout(page) {
  82. return (
  83. <>
  84. <DrawioViewerScript drawioUri={page.props.rendererConfig.drawioUri} />
  85. <Layout {...page.props}>{page}</Layout>
  86. </>
  87. );
  88. };
  89. function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
  90. const req: CrowiRequest = context.req as CrowiRequest;
  91. const { crowi } = req;
  92. const { configManager, searchService } = crowi;
  93. props.isSearchServiceConfigured = searchService.isConfigured;
  94. props.isSearchServiceReachable = searchService.isReachable;
  95. props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
  96. props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
  97. props.sidebarConfig = {
  98. isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
  99. isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
  100. };
  101. props.rendererConfig = {
  102. isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
  103. isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
  104. isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
  105. adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
  106. isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
  107. drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
  108. plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
  109. // XSS Options
  110. isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
  111. sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
  112. customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
  113. customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
  114. highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
  115. };
  116. props.showPageLimitationL = configManager.getConfig('crowi', 'customize:showPageLimitationL');
  117. }
  118. /**
  119. * for Server Side Translations
  120. * @param context
  121. * @param props
  122. * @param namespacesRequired
  123. */
  124. async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
  125. const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
  126. props._nextI18Next = nextI18NextConfig._nextI18Next;
  127. }
  128. export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
  129. const req = context.req as CrowiRequest;
  130. const { user } = req;
  131. const result = await getServerSideCommonProps(context);
  132. // check for presence
  133. // see: https://github.com/vercel/next.js/issues/19271#issuecomment-730006862
  134. if (!('props' in result)) {
  135. throw new Error('invalid getSSP result');
  136. }
  137. const props: Props = result.props as Props;
  138. if (user != null) {
  139. props.currentUser = user.toObject();
  140. }
  141. injectServerConfigurations(context, props);
  142. await injectNextI18NextConfigurations(context, props, ['translation']);
  143. return {
  144. props,
  145. };
  146. };
  147. export default SearchResultPage;