SavePageControls.tsx 4.5 KB

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