_search.page.tsx 6.3 KB

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