ContextExtractor.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import React, { FC, useEffect, useState } from 'react';
  2. import { pagePathUtils } from '@growi/core';
  3. import { CustomWindow } from '~/interfaces/global';
  4. import { IUserUISettings } from '~/interfaces/user-ui-settings';
  5. import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
  6. import { useRendererSettings } from '~/stores/renderer';
  7. import {
  8. useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
  9. usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
  10. useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
  11. } from '~/stores/ui';
  12. import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
  13. import {
  14. useSiteUrl,
  15. useCurrentCreatedAt, useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
  16. useIsNotCreatable, useIsTrashPage, useIsUserPage, useLastUpdateUsername,
  17. useCurrentPageId, usePageIdOnHackmd, usePageUser, useCurrentPagePath, useRevisionCreatedAt, useRevisionId, useRevisionIdHackmdSynced,
  18. useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser, useTargetAndAncestors,
  19. useNotFoundTargetPathOrId, useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
  20. useIsAclEnabled, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsEnabledAttachTitleHeader,
  21. useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useIsEmptyPage, useEmptyPageId, useGrowiVersion, useAuditLogEnabled,
  22. useActivityExpirationSeconds, useAuditLogAvailableActions, useGrowiRendererConfig,
  23. } from '../../stores/context';
  24. const { isTrashPage: _isTrashPage } = pagePathUtils;
  25. const jsonNull = 'null';
  26. const ContextExtractorOnce: FC = () => {
  27. const mainContent = document.querySelector('#content-main');
  28. const notFoundContentForPt = document.getElementById('growi-pagetree-not-found-context');
  29. const notFoundContext = document.getElementById('growi-not-found-context');
  30. const forbiddenContent = document.getElementById('forbidden-page');
  31. // get csrf token from body element
  32. // DO NOT REMOVE: uploading attachment data requires appContainer.csrfToken
  33. const body = document.querySelector('body');
  34. const csrfToken = body?.dataset.csrftoken;
  35. /*
  36. * App Context from DOM
  37. */
  38. const currentUser = JSON.parse(document.getElementById('growi-current-user')?.textContent || jsonNull);
  39. /*
  40. * Settings from context-hydrate DOM
  41. */
  42. const configByContextHydrate = JSON.parse(document.getElementById('growi-context-hydrate')?.textContent || jsonNull);
  43. /*
  44. * UserUISettings from DOM
  45. */
  46. const userUISettings: Partial<IUserUISettings> = JSON.parse(document.getElementById('growi-user-ui-settings')?.textContent || jsonNull);
  47. /*
  48. * Page Context from DOM
  49. */
  50. const revisionId = mainContent?.getAttribute('data-page-revision-id');
  51. const path = decodeURI(mainContent?.getAttribute('data-path') || '');
  52. // assign `null` to avoid returning empty string
  53. const pageId = mainContent?.getAttribute('data-page-id') || null;
  54. const emptyPageId = notFoundContext?.getAttribute('data-page-id') || null;
  55. const revisionCreatedAt = +(mainContent?.getAttribute('data-page-revision-created') || '');
  56. // createdAt
  57. const createdAtAttribute = mainContent?.getAttribute('data-page-created-at');
  58. const createdAt: Date | null = (createdAtAttribute != null) ? new Date(createdAtAttribute) : null;
  59. // updatedAt
  60. const updatedAtAttribute = mainContent?.getAttribute('data-page-updated-at');
  61. const updatedAt: Date | null = (updatedAtAttribute != null) ? new Date(updatedAtAttribute) : null;
  62. const deletedAt = mainContent?.getAttribute('data-page-deleted-at') || null;
  63. const isIdenticalPath = JSON.parse(mainContent?.getAttribute('data-identical-path') || jsonNull) ?? false;
  64. const isUserPage = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull) != null;
  65. const isTrashPage = _isTrashPage(path);
  66. const isNotCreatable = JSON.parse(mainContent?.getAttribute('data-page-is-not-creatable') || jsonNull) ?? false;
  67. const isForbidden = forbiddenContent != null;
  68. const pageUser = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull);
  69. const hasChildren = JSON.parse(mainContent?.getAttribute('data-page-has-children') || jsonNull);
  70. const hasParent = JSON.parse(mainContent?.getAttribute('data-has-parent') || jsonNull);
  71. const templateTagData = mainContent?.getAttribute('data-template-tags') || null;
  72. const shareLinksNumber = mainContent?.getAttribute('data-share-links-number');
  73. const shareLinkId = JSON.parse(mainContent?.getAttribute('data-share-link-id') || jsonNull);
  74. const revisionIdHackmdSynced = mainContent?.getAttribute('data-page-revision-id-hackmd-synced') || null;
  75. const lastUpdateUsername = mainContent?.getAttribute('data-page-last-update-username') || null;
  76. const deleteUsername = mainContent?.getAttribute('data-page-delete-username') || null;
  77. const pageIdOnHackmd = mainContent?.getAttribute('data-page-id-on-hackmd') || null;
  78. const hasDraftOnHackmd = !!mainContent?.getAttribute('data-page-has-draft-on-hackmd');
  79. const creator = JSON.parse(mainContent?.getAttribute('data-page-creator') || jsonNull);
  80. const revisionAuthor = JSON.parse(mainContent?.getAttribute('data-page-revision-author') || jsonNull);
  81. const targetAndAncestors = JSON.parse(document.getElementById('growi-pagetree-target-and-ancestors')?.textContent || jsonNull);
  82. const notFoundTargetPathOrId = JSON.parse(notFoundContentForPt?.getAttribute('data-not-found-target-path-or-id') || jsonNull);
  83. const isSearchPage = document.getElementById('search-page') != null;
  84. const isEmptyPage = JSON.parse(mainContent?.getAttribute('data-page-is-empty') || jsonNull) ?? false;
  85. const grant = +(mainContent?.getAttribute('data-page-grant') || 1);
  86. const grantGroupId = mainContent?.getAttribute('data-page-grant-group') || null;
  87. const grantGroupName = mainContent?.getAttribute('data-page-grant-group-name') || null;
  88. /*
  89. * use static swr
  90. */
  91. useCsrfToken(csrfToken);
  92. // App
  93. useCurrentUser(currentUser);
  94. // UserUISettings
  95. usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser ?? configByContextHydrate.isSidebarDrawerMode);
  96. usePreferDrawerModeOnEditByUser(userUISettings?.preferDrawerModeOnEditByUser);
  97. useSidebarCollapsed(userUISettings?.isSidebarCollapsed ?? configByContextHydrate.isSidebarClosedAtDockMode);
  98. useCurrentSidebarContents(userUISettings?.currentSidebarContents);
  99. useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
  100. // hydrated config
  101. useSiteUrl(configByContextHydrate.crowi.url);
  102. useIsAclEnabled(configByContextHydrate.isAclEnabled);
  103. useIsSearchServiceConfigured(configByContextHydrate.isSearchServiceConfigured);
  104. useIsSearchServiceReachable(configByContextHydrate.isSearchServiceReachable);
  105. useIsEnabledAttachTitleHeader(configByContextHydrate.isEnabledAttachTitleHeader);
  106. useIsIndentSizeForced(configByContextHydrate.isIndentSizeForced);
  107. useDefaultIndentSize(configByContextHydrate.adminPreferredIndentSize);
  108. useAuditLogEnabled(configByContextHydrate.auditLogEnabled);
  109. useActivityExpirationSeconds(configByContextHydrate.activityExpirationSeconds);
  110. useAuditLogAvailableActions(configByContextHydrate.auditLogAvailableActions);
  111. useGrowiVersion(configByContextHydrate.crowi.version);
  112. useRendererSettings({
  113. isEnabledLinebreaks: configByContextHydrate.isEnabledLinebreaks,
  114. isEnabledLinebreaksInComments: configByContextHydrate.isEnabledLinebreaksInComments,
  115. adminPreferredIndentSize: configByContextHydrate.adminPreferredIndentSize,
  116. isIndentSizeForced: configByContextHydrate.isIndentSizeForced,
  117. });
  118. useGrowiRendererConfig({
  119. isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
  120. attrWhiteList: configByContextHydrate.attrWhiteList,
  121. tagWhiteList: configByContextHydrate.tagWhiteList,
  122. highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
  123. env: {
  124. MATHJAX: configByContextHydrate.env.MATHJAX,
  125. PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
  126. BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
  127. },
  128. });
  129. // Page
  130. useCurrentCreatedAt(createdAt);
  131. useDeleteUsername(deleteUsername);
  132. useDeletedAt(deletedAt);
  133. useHasChildren(hasChildren);
  134. useHasDraftOnHackmd(hasDraftOnHackmd);
  135. useIsIdenticalPath(isIdenticalPath);
  136. useIsNotCreatable(isNotCreatable);
  137. useIsForbidden(isForbidden);
  138. useIsTrashPage(isTrashPage);
  139. useIsUserPage(isUserPage);
  140. useLastUpdateUsername(lastUpdateUsername);
  141. useCurrentPageId(pageId);
  142. useEmptyPageId(emptyPageId);
  143. usePageIdOnHackmd(pageIdOnHackmd);
  144. usePageUser(pageUser);
  145. useCurrentPagePath(path);
  146. useRevisionCreatedAt(revisionCreatedAt);
  147. useRevisionId(revisionId);
  148. useRevisionIdHackmdSynced(revisionIdHackmdSynced);
  149. useShareLinkId(shareLinkId);
  150. useShareLinksNumber(shareLinksNumber);
  151. useTemplateTagData(templateTagData);
  152. useCurrentUpdatedAt(updatedAt);
  153. useCreator(creator);
  154. useRevisionAuthor(revisionAuthor);
  155. useTargetAndAncestors(targetAndAncestors);
  156. useNotFoundTargetPathOrId(notFoundTargetPathOrId);
  157. useIsSearchPage(isSearchPage);
  158. useIsEmptyPage(isEmptyPage);
  159. useHasParent(hasParent);
  160. // Navigation
  161. usePreferDrawerModeByUser();
  162. usePreferDrawerModeOnEditByUser();
  163. useIsDeviceSmallerThanMd();
  164. // Navigation
  165. usePreferDrawerModeByUser();
  166. usePreferDrawerModeOnEditByUser();
  167. useIsDeviceSmallerThanMd();
  168. // Editor
  169. useSelectedGrant(grant);
  170. useSelectedGrantGroupId(grantGroupId);
  171. useSelectedGrantGroupName(grantGroupName);
  172. // SearchResult
  173. useIsDeviceSmallerThanLg();
  174. // Global Socket
  175. useSetupGlobalSocket();
  176. const shouldInitAdminSock = !!currentUser?.isAdmin;
  177. useSetupGlobalAdminSocket(shouldInitAdminSock);
  178. // TODO: Remove this code when reveal.js is omitted. see: https://github.com/weseek/growi/pull/6223
  179. // Do not access this property from other than reveal.js plugins.
  180. (window as CustomWindow).previewRenderer = generatePreviewRenderer(
  181. {
  182. isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
  183. attrWhiteList: configByContextHydrate.attrWhiteList,
  184. tagWhiteList: configByContextHydrate.tagWhiteList,
  185. highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
  186. env: {
  187. MATHJAX: configByContextHydrate.env.MATHJAX,
  188. PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
  189. BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
  190. },
  191. },
  192. null,
  193. path,
  194. );
  195. return null;
  196. };
  197. const ContextExtractor: FC = React.memo(() => {
  198. const [isRunOnce, setRunOnce] = useState(false);
  199. useEffect(() => {
  200. setRunOnce(true);
  201. }, []);
  202. return isRunOnce ? null : <ContextExtractorOnce></ContextExtractorOnce>;
  203. });
  204. export default ContextExtractor;