commons.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import type { ColorScheme, IUserHasId } from '@growi/core';
  2. import {
  3. DevidedPagePath, Lang, AllLang,
  4. } from '@growi/core';
  5. import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
  6. import type { SSRConfig, UserConfig } from 'next-i18next';
  7. import * as nextI18NextConfig from '^/config/next-i18next.config';
  8. import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
  9. import type { CrowiRequest } from '~/interfaces/crowi-request';
  10. import type { ISidebarConfig } from '~/interfaces/sidebar-config';
  11. import type { IUserUISettings } from '~/interfaces/user-ui-settings';
  12. import type { PageDocument } from '~/server/models/page';
  13. import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
  14. import {
  15. useCurrentProductNavWidth, useCurrentSidebarContents, usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed,
  16. } from '~/stores/ui';
  17. export type CommonProps = {
  18. namespacesRequired: string[], // i18next
  19. currentPathname: string,
  20. appTitle: string,
  21. siteUrl: string,
  22. confidential: string,
  23. customTitleTemplate: string,
  24. csrfToken: string,
  25. isContainerFluid: boolean,
  26. growiVersion: string,
  27. isMaintenanceMode: boolean,
  28. redirectDestination: string | null,
  29. isDefaultLogo: boolean,
  30. currentUser?: IUserHasId,
  31. forcedColorScheme?: ColorScheme,
  32. sidebarConfig: ISidebarConfig,
  33. userUISettings?: IUserUISettings
  34. } & Partial<SSRConfig>;
  35. // eslint-disable-next-line max-len
  36. export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(context: GetServerSidePropsContext) => {
  37. const getModelSafely = await import('~/server/util/mongoose-utils').then(mod => mod.getModelSafely);
  38. const req = context.req as CrowiRequest<IUserHasId & any>;
  39. const { crowi, user } = req;
  40. const {
  41. appService, configManager, customizeService, attachmentService,
  42. } = crowi;
  43. const url = new URL(context.resolvedUrl, 'http://example.com');
  44. const currentPathname = decodeURIComponent(url.pathname);
  45. const isMaintenanceMode = appService.isMaintenanceMode();
  46. let currentUser;
  47. if (user != null) {
  48. currentUser = user.toObject();
  49. }
  50. // Redirect destination for page transition by next/link
  51. let redirectDestination: string | null = null;
  52. if (!crowi.aclService.isGuestAllowedToRead() && currentUser == null) {
  53. redirectDestination = '/login';
  54. }
  55. else if (!isMaintenanceMode && currentPathname === '/maintenance') {
  56. redirectDestination = '/';
  57. }
  58. else if (isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance')) {
  59. redirectDestination = '/maintenance';
  60. }
  61. else {
  62. redirectDestination = null;
  63. }
  64. const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
  65. const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
  66. const forcedColorScheme = crowi.customizeService.forcedColorScheme;
  67. // retrieve UserUISettings
  68. const UserUISettings = getModelSafely<UserUISettingsDocument>('UserUISettings');
  69. const userUISettings = user != null && UserUISettings != null
  70. ? await UserUISettings.findOne({ user: user._id }).exec()
  71. : req.session.uiSettings; // for guests
  72. const props: CommonProps = {
  73. namespacesRequired: ['translation'],
  74. currentPathname,
  75. appTitle: appService.getAppTitle(),
  76. siteUrl: configManager.getConfig('crowi', 'app:siteUrl'), // DON'T USE appService.getSiteUrl()
  77. confidential: appService.getAppConfidential() || '',
  78. customTitleTemplate: customizeService.customTitleTemplate,
  79. csrfToken: req.csrfToken(),
  80. isContainerFluid: configManager.getConfig('crowi', 'customize:isContainerFluid') ?? false,
  81. growiVersion: crowi.version,
  82. isMaintenanceMode,
  83. redirectDestination,
  84. currentUser,
  85. isDefaultLogo,
  86. forcedColorScheme,
  87. sidebarConfig: {
  88. isSidebarDrawerMode: configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'),
  89. isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
  90. },
  91. userUISettings: userUISettings?.toObject?.() ?? userUISettings,
  92. };
  93. return { props };
  94. };
  95. export const getNextI18NextConfig = async(
  96. // 'serverSideTranslations' method should be given from Next.js Page
  97. // because importing it in this file causes https://github.com/isaachinman/next-i18next/issues/1545
  98. serverSideTranslations: (
  99. initialLocale: string, namespacesRequired?: string[] | undefined, configOverride?: UserConfig | null, extraLocales?: string[] | false
  100. ) => Promise<SSRConfig>,
  101. context: GetServerSidePropsContext, namespacesRequired?: string[] | undefined, preloadAllLang = false,
  102. ): Promise<SSRConfig> => {
  103. const req: CrowiRequest = context.req as CrowiRequest;
  104. const { crowi, user, headers } = req;
  105. const { configManager } = crowi;
  106. // determine language
  107. const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
  108. : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
  109. const namespaces = ['commons'];
  110. if (namespacesRequired != null) {
  111. namespaces.push(...namespacesRequired);
  112. }
  113. // TODO: deprecate 'translation.json' in the future
  114. else {
  115. namespaces.push('translation');
  116. }
  117. return serverSideTranslations(locale, namespaces, nextI18NextConfig, preloadAllLang ? AllLang : false);
  118. };
  119. /**
  120. * Generate whole title string for the specified title
  121. * @param props
  122. * @param title
  123. */
  124. export const generateCustomTitle = (props: CommonProps, title: string): string => {
  125. return props.customTitleTemplate
  126. .replace('{{sitename}}', props.appTitle)
  127. .replace('{{pagepath}}', title)
  128. .replace('{{pagename}}', title);
  129. };
  130. /**
  131. * Generate whole title string for the specified page path
  132. * @param props
  133. * @param pagePath
  134. */
  135. export const generateCustomTitleForPage = (props: CommonProps, pagePath: string): string => {
  136. const dPagePath = new DevidedPagePath(pagePath, true, true);
  137. return props.customTitleTemplate
  138. .replace('{{sitename}}', props.appTitle)
  139. .replace('{{pagepath}}', pagePath)
  140. .replace('{{pagename}}', dPagePath.latter);
  141. };
  142. export const useInitSidebarConfig = (sidebarConfig: ISidebarConfig, userUISettings?: IUserUISettings): void => {
  143. // UserUISettings
  144. usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser ?? sidebarConfig.isSidebarDrawerMode);
  145. usePreferDrawerModeOnEditByUser(userUISettings?.preferDrawerModeOnEditByUser);
  146. useSidebarCollapsed(userUISettings?.isSidebarCollapsed ?? sidebarConfig.isSidebarClosedAtDockMode);
  147. useCurrentSidebarContents(userUISettings?.currentSidebarContents);
  148. useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
  149. };
  150. export const skipSSR = (context: GetServerSidePropsContext, page: PageDocument): boolean => {
  151. // page document only stores the bodyLength of the latest revision
  152. if (!page.isLatestRevision() || page.latestRevisionBodyLength == null) {
  153. return true;
  154. }
  155. const req = context.req as CrowiRequest;
  156. const ssrMaxRevisionBodyLength = req.crowi.configManager.getConfig('crowi', 'app:ssrMaxRevisionBodyLength');
  157. if (ssrMaxRevisionBodyLength < page.latestRevisionBodyLength) {
  158. return true;
  159. }
  160. return false;
  161. };