SavePageControls.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import React, { useCallback } from 'react';
  2. import type EventEmitter from 'events';
  3. import { isTopPage, isUsersProtectedPages } from '@growi/core/dist/utils/page-path-utils';
  4. import { useTranslation } from 'next-i18next';
  5. import {
  6. UncontrolledButtonDropdown, Button,
  7. DropdownToggle, DropdownMenu, DropdownItem,
  8. } from 'reactstrap';
  9. import { toastSuccess, toastError } from '~/client/util/toastr';
  10. import type { IPageGrantData } from '~/interfaces/page';
  11. import {
  12. useIsEditable, useIsAclEnabled,
  13. } from '~/stores/context';
  14. import { useWaitingSaveProcessing } from '~/stores/editor';
  15. import { useSWRMUTxCurrentPage, useSWRxCurrentPage } from '~/stores/page';
  16. import { mutatePageTree } from '~/stores/page-listing';
  17. import { useSelectedGrant } from '~/stores/ui';
  18. import loggerFactory from '~/utils/logger';
  19. import { unpublish } from '../client/services/page-operation';
  20. import { LoadingSpinner } from './LoadingSpinner';
  21. import { GrantSelector } from './SavePageControls/GrantSelector';
  22. declare global {
  23. // eslint-disable-next-line vars-on-top, no-var
  24. var globalEmitter: EventEmitter;
  25. }
  26. const logger = loggerFactory('growi:SavePageControls');
  27. export type SavePageControlsProps = {
  28. slackChannels: string
  29. }
  30. export const SavePageControls = (props: SavePageControlsProps): JSX.Element | null => {
  31. const { slackChannels } = props;
  32. const { t } = useTranslation();
  33. const { data: currentPage } = useSWRxCurrentPage();
  34. const { data: isEditable } = useIsEditable();
  35. const { data: isAclEnabled } = useIsAclEnabled();
  36. const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
  37. const { data: _isWaitingSaveProcessing } = useWaitingSaveProcessing();
  38. const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
  39. const isWaitingSaveProcessing = _isWaitingSaveProcessing === true; // ignore undefined
  40. const updateGrantHandler = useCallback((grantData: IPageGrantData): void => {
  41. mutateGrant(grantData);
  42. }, [mutateGrant]);
  43. const save = useCallback(async(): Promise<void> => {
  44. // save
  45. globalEmitter.emit('saveAndReturnToView', { slackChannels });
  46. }, [slackChannels]);
  47. const saveAndOverwriteScopesOfDescendants = useCallback(() => {
  48. // save
  49. globalEmitter.emit('saveAndReturnToView', { overwriteScopesOfDescendants: true, slackChannels });
  50. }, [slackChannels]);
  51. const clickUnpublishButtonHandler = useCallback(async() => {
  52. const pageId = currentPage?._id;
  53. if (pageId == null) {
  54. return;
  55. }
  56. try {
  57. await unpublish(pageId);
  58. await mutateCurrentPage();
  59. await mutatePageTree();
  60. toastSuccess(t('wip_page.success_save_as_wip'));
  61. }
  62. catch (err) {
  63. logger.error(err);
  64. toastError(t('wip_page.fail_save_as_wip'));
  65. }
  66. }, [currentPage?._id, mutateCurrentPage, t]);
  67. if (isEditable == null || isAclEnabled == null || grantData == null) {
  68. return null;
  69. }
  70. if (!isEditable) {
  71. return null;
  72. }
  73. const { grant, userRelatedGrantedGroups } = grantData;
  74. const isGrantSelectorDisabledPage = isTopPage(currentPage?.path ?? '') || isUsersProtectedPages(currentPage?.path ?? '');
  75. const labelSubmitButton = t('Update');
  76. const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
  77. const labelUnpublishPage = t('wip_page.save_as_wip');
  78. return (
  79. <div className="d-flex align-items-center flex-nowrap">
  80. {isAclEnabled
  81. && (
  82. <div className="me-2">
  83. <GrantSelector
  84. grant={grant}
  85. disabled={isGrantSelectorDisabledPage}
  86. userRelatedGrantedGroups={userRelatedGrantedGroups}
  87. onUpdateGrant={updateGrantHandler}
  88. />
  89. </div>
  90. )
  91. }
  92. <UncontrolledButtonDropdown direction="up" size="sm">
  93. <Button
  94. id="caret"
  95. data-testid="save-page-btn"
  96. color="primary"
  97. className="btn-submit"
  98. onClick={save}
  99. disabled={isWaitingSaveProcessing}
  100. >
  101. {isWaitingSaveProcessing && (
  102. <LoadingSpinner />
  103. )}
  104. {labelSubmitButton}
  105. </Button>
  106. <DropdownToggle caret color="primary" disabled={isWaitingSaveProcessing} />
  107. <DropdownMenu container="body" end>
  108. <DropdownItem onClick={saveAndOverwriteScopesOfDescendants}>
  109. {labelOverwriteScopes}
  110. </DropdownItem>
  111. <DropdownItem onClick={clickUnpublishButtonHandler}>
  112. {labelUnpublishPage}
  113. </DropdownItem>
  114. </DropdownMenu>
  115. </UncontrolledButtonDropdown>
  116. </div>
  117. );
  118. };