PageTitleHeader.tsx 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import type { FC } from 'react';
  2. import { useState, useCallback } from 'react';
  3. import nodePath from 'path';
  4. import { pathUtils } from '@growi/core/dist/utils';
  5. import { useTranslation } from 'next-i18next';
  6. import { ValidationTarget } from '~/client/util/input-validator';
  7. import ClosableTextInput from '../Common/ClosableTextInput';
  8. import type { Props } from './PagePathHeader';
  9. import { usePagePathRenameHandler } from './page-header-utils';
  10. export const PageTitleHeader: FC<Props> = (props) => {
  11. const { currentPage } = props;
  12. const currentPagePath = currentPage.path;
  13. const pageTitle = nodePath.basename(currentPagePath) || '/';
  14. const [isRenameInputShown, setRenameInputShown] = useState(false);
  15. const [editedPagePath, setEditedPagePath] = useState(currentPagePath);
  16. const pagePathRenameHandler = usePagePathRenameHandler(currentPage);
  17. const { t } = useTranslation();
  18. const editedPageTitle = nodePath.basename(editedPagePath);
  19. const onRenameFinish = useCallback(() => {
  20. setRenameInputShown(false);
  21. }, []);
  22. const onRenameFailure = useCallback(() => {
  23. setRenameInputShown(true);
  24. }, []);
  25. const onInputChange = useCallback((inputText: string) => {
  26. const parentPagePath = pathUtils.addTrailingSlash(nodePath.dirname(currentPage.path));
  27. const newPagePath = nodePath.resolve(parentPagePath, inputText);
  28. setEditedPagePath(newPagePath);
  29. }, [currentPage?.path, setEditedPagePath]);
  30. const onPressEnter = useCallback(() => {
  31. pagePathRenameHandler(editedPagePath, onRenameFinish, onRenameFailure);
  32. }, [editedPagePath, onRenameFailure, onRenameFinish, pagePathRenameHandler]);
  33. const onPressEscape = useCallback(() => {
  34. setEditedPagePath(currentPagePath);
  35. setRenameInputShown(false);
  36. }, [currentPagePath]);
  37. const onClickButton = useCallback(() => {
  38. pagePathRenameHandler(editedPagePath, onRenameFinish, onRenameFailure);
  39. }, [editedPagePath, onRenameFailure, onRenameFinish, pagePathRenameHandler]);
  40. const onClickPageTitle = useCallback(() => {
  41. setEditedPagePath(currentPagePath);
  42. setRenameInputShown(true);
  43. }, [currentPagePath]);
  44. const PageTitle = <div onClick={onClickPageTitle}>{pageTitle}</div>;
  45. const buttonStyle = isRenameInputShown ? '' : 'd-none';
  46. return (
  47. <div className="row">
  48. <div className="col-4">
  49. {isRenameInputShown ? (
  50. <div className="flex-fill">
  51. <ClosableTextInput
  52. value={editedPageTitle}
  53. placeholder={t('Input page name')}
  54. onPressEnter={onPressEnter}
  55. onPressEscape={onPressEscape}
  56. validationTarget={ValidationTarget.PAGE}
  57. handleInputChange={onInputChange}
  58. />
  59. </div>
  60. ) : (
  61. <>{ PageTitle }</>
  62. )}
  63. </div>
  64. <div className={`col-4 ${buttonStyle}`}>
  65. <button type="button" onClick={onClickButton}>
  66. <span className="material-symbols-outlined">check_circle</span>
  67. </button>
  68. </div>
  69. </div>
  70. );
  71. };