ui.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /** **********************************************************
  2. * Unions
  3. *********************************************************** */
  4. import { useCallback } from 'react';
  5. import type { SWRResponseWithUtils } from '@growi/core/dist/swr';
  6. import { isServer } from '@growi/core/dist/utils';
  7. import useSWRImmutable from 'swr/immutable';
  8. import { useIsNotFound } from '~/stores/page';
  9. import { useIsEditable } from './context';
  10. export const EditorMode = {
  11. View: 'view',
  12. Editor: 'editor',
  13. } as const;
  14. export type EditorMode = typeof EditorMode[keyof typeof EditorMode];
  15. const getClassNamesByEditorMode = (editorMode: EditorMode | undefined): string[] => {
  16. const classNames: string[] = [];
  17. switch (editorMode) {
  18. case EditorMode.Editor:
  19. classNames.push('editing', 'builtin-editor');
  20. break;
  21. }
  22. return classNames;
  23. };
  24. export const EditorModeHash = {
  25. View: '',
  26. Edit: '#edit',
  27. } as const;
  28. export type EditorModeHash = typeof EditorModeHash[keyof typeof EditorModeHash];
  29. const updateHashByEditorMode = (newEditorMode: EditorMode) => {
  30. const { pathname, search } = window.location;
  31. switch (newEditorMode) {
  32. case EditorMode.View:
  33. window.history.replaceState(null, '', `${pathname}${search}${EditorModeHash.View}`);
  34. break;
  35. case EditorMode.Editor:
  36. window.history.replaceState(null, '', `${pathname}${search}${EditorModeHash.Edit}`);
  37. break;
  38. }
  39. };
  40. export const determineEditorModeByHash = (): EditorMode => {
  41. if (isServer()) {
  42. return EditorMode.View;
  43. }
  44. const { hash } = window.location;
  45. switch (hash) {
  46. case EditorModeHash.Edit:
  47. return EditorMode.Editor;
  48. default:
  49. return EditorMode.View;
  50. }
  51. };
  52. type EditorModeUtils = {
  53. getClassNamesByEditorMode: () => string[],
  54. }
  55. export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
  56. const { data: _isEditable } = useIsEditable();
  57. const { data: isNotFound } = useIsNotFound();
  58. const editorModeByHash = determineEditorModeByHash();
  59. const isLoading = _isEditable === undefined;
  60. const isEditable = !isLoading && _isEditable;
  61. const preventModeEditor = !isEditable || isNotFound === undefined || isNotFound === true;
  62. const initialData = preventModeEditor ? EditorMode.View : editorModeByHash;
  63. const swrResponse = useSWRImmutable(
  64. isLoading ? null : ['editorMode', isEditable, preventModeEditor],
  65. null,
  66. { fallbackData: initialData },
  67. );
  68. // construct overriding mutate method
  69. const mutateOriginal = swrResponse.mutate;
  70. const mutate = useCallback((editorMode: EditorMode, shouldRevalidate?: boolean) => {
  71. if (preventModeEditor) {
  72. return Promise.resolve(EditorMode.View); // fixed if not editable
  73. }
  74. updateHashByEditorMode(editorMode);
  75. return mutateOriginal(editorMode, shouldRevalidate);
  76. }, [preventModeEditor, mutateOriginal]);
  77. const getClassNames = useCallback(() => {
  78. return getClassNamesByEditorMode(swrResponse.data);
  79. }, [swrResponse.data]);
  80. return Object.assign(swrResponse, {
  81. mutate,
  82. getClassNamesByEditorMode: getClassNames,
  83. });
  84. };