PagePathHeader.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {
  2. useState, useCallback, memo,
  3. } from 'react';
  4. import type { IPagePopulatedToShowRevision } from '@growi/core';
  5. import { DevidedPagePath } from '@growi/core/dist/models';
  6. import { normalizePath } from '@growi/core/dist/utils/path-utils';
  7. import { useTranslation } from 'next-i18next';
  8. import { ValidationTarget } from '~/client/util/input-validator';
  9. import LinkedPagePath from '~/models/linked-page-path';
  10. import { usePageSelectModal } from '~/stores/modal';
  11. import ClosableTextInput from '../Common/ClosableTextInput';
  12. import { PagePathHierarchicalLink } from '../Common/PagePathHierarchicalLink';
  13. import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils';
  14. import { PageSelectModal } from '../PageSelectModal/PageSelectModal';
  15. import styles from './PagePathHeader.module.scss';
  16. const moduleClass = styles['page-path-header'];
  17. type Props = {
  18. currentPage: IPagePopulatedToShowRevision,
  19. className?: string,
  20. maxWidth?: number,
  21. onRenameTerminated?: () => void,
  22. }
  23. export const PagePathHeader = memo((props: Props): JSX.Element => {
  24. const { t } = useTranslation();
  25. const {
  26. currentPage, className, maxWidth, onRenameTerminated,
  27. } = props;
  28. const dPagePath = new DevidedPagePath(currentPage.path, true);
  29. const parentPagePath = dPagePath.former;
  30. const linkedPagePath = new LinkedPagePath(parentPagePath);
  31. const [isRenameInputShown, setRenameInputShown] = useState(false);
  32. const [isHover, setHover] = useState(false);
  33. const [editingParentPagePath, setEditingParentPagePath] = useState(parentPagePath);
  34. // const [isIconHidden, setIsIconHidden] = useState(false);
  35. const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal();
  36. const isOpened = PageSelectModalData?.isOpened ?? false;
  37. const pagePathRenameHandler = usePagePathRenameHandler(currentPage);
  38. const onRenameFinish = useCallback(() => {
  39. setRenameInputShown(false);
  40. onRenameTerminated?.();
  41. }, [onRenameTerminated]);
  42. const onRenameFailure = useCallback(() => {
  43. setRenameInputShown(true);
  44. }, []);
  45. const onInputChange = useCallback((inputText: string) => {
  46. setEditingParentPagePath(inputText);
  47. }, []);
  48. const onPressEnter = useCallback(() => {
  49. const pathToRename = normalizePath(`${editingParentPagePath}/${dPagePath.latter}`);
  50. pagePathRenameHandler(pathToRename, onRenameFinish, onRenameFailure);
  51. }, [editingParentPagePath, onRenameFailure, onRenameFinish, pagePathRenameHandler, dPagePath.latter]);
  52. const onPressEscape = useCallback(() => {
  53. // reset
  54. setEditingParentPagePath(parentPagePath);
  55. setRenameInputShown(false);
  56. }, [parentPagePath]);
  57. const onClickEditButton = useCallback(() => {
  58. // reset
  59. setEditingParentPagePath(parentPagePath);
  60. setRenameInputShown(true);
  61. }, [parentPagePath]);
  62. // TODO: https://redmine.weseek.co.jp/issues/141062
  63. // Truncate left side and don't use getElementById
  64. //
  65. // useEffect(() => {
  66. // const areaElem = document.getElementById('grw-page-path-header-container');
  67. // const linkElem = document.getElementById('grw-page-path-hierarchical-link');
  68. // const areaElemWidth = areaElem?.offsetWidth;
  69. // const linkElemWidth = linkElem?.offsetWidth;
  70. // if (areaElemWidth && linkElemWidth) {
  71. // setIsIconHidden(linkElemWidth > areaElemWidth);
  72. // }
  73. // else {
  74. // setIsIconHidden(false);
  75. // }
  76. // }, [currentPage]);
  77. //
  78. // const styles: CSSProperties | undefined = isIconHidden ? { direction: 'rtl' } : undefined;
  79. if (dPagePath.isRoot) {
  80. return <></>;
  81. }
  82. return (
  83. <div
  84. id="page-path-header"
  85. className={`d-flex ${moduleClass} ${className ?? ''} small position-relative ms-2`}
  86. style={{ maxWidth }}
  87. onMouseEnter={() => setHover(true)}
  88. onMouseLeave={() => setHover(false)}
  89. >
  90. <div
  91. className="page-path-header-input d-inline-block overflow-x-scroll"
  92. >
  93. { isRenameInputShown && (
  94. <div className="position-relative">
  95. <div className="position-absolute w-100">
  96. <ClosableTextInput
  97. value={editingParentPagePath}
  98. placeholder={t('Input parent page path')}
  99. inputClassName="form-control-sm"
  100. onPressEnter={onPressEnter}
  101. onPressEscape={onPressEscape}
  102. onChange={onInputChange}
  103. validationTarget={ValidationTarget.PAGE}
  104. onClickOutside={onPressEscape}
  105. useAutosizeInput
  106. />
  107. </div>
  108. </div>
  109. ) }
  110. <div
  111. className={`${isRenameInputShown ? 'invisible' : ''} text-truncate`}
  112. // style={styles}
  113. >
  114. <PagePathHierarchicalLink
  115. linkedPagePath={linkedPagePath}
  116. // isIconHidden={isIconHidden}
  117. />
  118. </div>
  119. </div>
  120. <div
  121. className={`page-path-header-buttons d-flex align-items-center ${isHover && !isRenameInputShown ? '' : 'invisible'}`}
  122. >
  123. <button
  124. type="button"
  125. className="btn btn-outline-neutral-secondary me-2 d-flex align-items-center justify-content-center"
  126. onClick={onClickEditButton}
  127. >
  128. <span className="material-symbols-outlined fs-6">edit</span>
  129. </button>
  130. <button
  131. type="button"
  132. className="btn btn-outline-neutral-secondary d-flex align-items-center justify-content-center"
  133. onClick={openPageSelectModal}
  134. >
  135. <span className="material-symbols-outlined fs-6">account_tree</span>
  136. </button>
  137. </div>
  138. {isOpened && <PageSelectModal />}
  139. </div>
  140. );
  141. });