Pārlūkot izejas kodu

Merge pull request #8538 from weseek/imprv/141062-141361-truncate-page-path-when-editing

imprv: truncate page path when editing
Yuki Takei 2 gadi atpakaļ
vecāks
revīzija
5c3da282dc

+ 1 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -108,6 +108,7 @@
   "Error occurred": "Error occurred",
   "Error occurred": "Error occurred",
   "Input page name": "Input page name",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
   "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
   "New Page": "New page",
   "New Page": "New page",
   "Create under": "Create page under below:",
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "V5 Page Migration": "Convert To V5 Compatibility",

+ 1 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -107,6 +107,7 @@
   "Error occurred": "エラーが発生しました",
   "Error occurred": "エラーが発生しました",
   "Input page name": "ページ名を入力",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
+  "Input parent page path": "親ページのパスを入力",
   "New Page": "新規ページ",
   "New Page": "新規ページ",
   "Create under": "ページを以下に作成",
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",
   "V5 Page Migration": "V5 互換形式 への変換",

+ 1 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -113,6 +113,7 @@
   "Error occurred": "Error occurred",
   "Error occurred": "Error occurred",
   "Input page name": "Input page name",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
   "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
   "New Page": "新页面",
   "New Page": "新页面",
   "Create under": "Create page under below:",
   "Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",
   "V5 Page Migration": "转换为V5的兼容性",

+ 6 - 2
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx

@@ -13,6 +13,7 @@ type PagePathHierarchicalLinkProps = {
   linkedPagePathByHtml?: LinkedPagePath,
   linkedPagePathByHtml?: LinkedPagePath,
   basePath?: string,
   basePath?: string,
   isInTrash?: boolean,
   isInTrash?: boolean,
+  isIconHidden?: boolean,
 
 
   // !!INTERNAL USE ONLY!!
   // !!INTERNAL USE ONLY!!
   isInnerElem?: boolean,
   isInnerElem?: boolean,
@@ -23,16 +24,18 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
     linkedPagePath, linkedPagePathByHtml, basePath, isInTrash, isInnerElem,
     linkedPagePath, linkedPagePathByHtml, basePath, isInTrash, isInnerElem,
   } = props;
   } = props;
 
 
+  const isIconHidden = props.isIconHidden ?? false;
+
   // eslint-disable-next-line react/prop-types
   // eslint-disable-next-line react/prop-types
   const RootElm = useCallback(({ children }) => {
   const RootElm = useCallback(({ children }) => {
     return isInnerElem
     return isInnerElem
       ? <>{children}</>
       ? <>{children}</>
-      : <span className="text-break">{children}</span>;
+      : <span className="text-break" id="grw-page-path-hierarchical-link">{children}</span>;
   }, [isInnerElem]);
   }, [isInnerElem]);
 
 
   // render root element
   // render root element
   if (linkedPagePath.isRoot) {
   if (linkedPagePath.isRoot) {
-    if (basePath != null) {
+    if (basePath != null || isIconHidden) {
       return <></>;
       return <></>;
     }
     }
 
 
@@ -76,6 +79,7 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
           basePath={basePath}
           basePath={basePath}
           isInTrash={isInTrash || linkedPagePath.isInTrash}
           isInTrash={isInTrash || linkedPagePath.isInTrash}
           isInnerElem
           isInnerElem
+          isIconHidden={isIconHidden}
         />
         />
       ) }
       ) }
       { isSeparatorRequired && (
       { isSeparatorRequired && (

+ 1 - 0
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -295,6 +295,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
           d-flex align-items-center justify-content-end px-2 px-sm-3 px-md-4 py-1 gap-2 gap-md-4 d-print-none
           d-flex align-items-center justify-content-end px-2 px-sm-3 px-md-4 py-1 gap-2 gap-md-4 d-print-none
         `}
         `}
         data-testid="grw-contextual-sub-nav"
         data-testid="grw-contextual-sub-nav"
+        id="grw-contextual-sub-nav"
       >
       >
         {pageId != null && (
         {pageId != null && (
           <PageControls
           <PageControls

+ 1 - 1
apps/app/src/components/PageEditor/EditorNavbar/EditorNavbar.tsx

@@ -12,7 +12,7 @@ export const EditorNavbar = (): JSX.Element => {
   const { data: editingUsers } = useEditingUsers();
   const { data: editingUsers } = useEditingUsers();
 
 
   return (
   return (
-    <div className={`${moduleClass} d-flex justify-content-between px-4 py-1`}>
+    <div className={`${moduleClass} d-flex justify-content-between px-4 py-1 ms-3`}>
       <PageHeader />
       <PageHeader />
       <EditingUserList
       <EditingUserList
         userList={editingUsers?.userList ?? []}
         userList={editingUsers?.userList ?? []}

+ 1 - 1
apps/app/src/components/PageHeader/PageHeader.tsx

@@ -17,7 +17,7 @@ export const PageHeader: FC = () => {
   }
   }
 
 
   return (
   return (
-    <div className={moduleClass}>
+    <div className={`${moduleClass} w-100`}>
       <PagePathHeader
       <PagePathHeader
         currentPage={currentPage}
         currentPage={currentPage}
       />
       />

+ 1 - 0
apps/app/src/components/PageHeader/PagePathHeader.module.scss

@@ -1,4 +1,5 @@
 .page-path-header :global {
 .page-path-header :global {
+  max-width: calc(100vw - 650px);
   input {
   input {
     min-width: 20px;
     min-width: 20px;
     min-height: unset;
     min-height: unset;

+ 43 - 27
apps/app/src/components/PageHeader/PagePathHeader.tsx

@@ -1,5 +1,7 @@
-import { useState, useEffect, useCallback } from 'react';
-import type { FC } from 'react';
+import {
+  useState, useEffect, useCallback, memo, useMemo,
+} from 'react';
+import type { CSSProperties, FC } from 'react';
 
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { DevidedPagePath } from '@growi/core/dist/models';
@@ -24,7 +26,7 @@ type Props = {
   currentPage: IPagePopulatedToShowRevision
   currentPage: IPagePopulatedToShowRevision
 }
 }
 
 
-export const PagePathHeader: FC<Props> = (props) => {
+export const PagePathHeader: FC<Props> = memo((props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const { currentPage } = props;
   const { currentPage } = props;
 
 
@@ -37,6 +39,8 @@ export const PagePathHeader: FC<Props> = (props) => {
   const [isHover, setHover] = useState(false);
   const [isHover, setHover] = useState(false);
   const [editingParentPagePath, setEditingParentPagePath] = useState(parentPagePath);
   const [editingParentPagePath, setEditingParentPagePath] = useState(parentPagePath);
 
 
+  // const [isIconHidden, setIsIconHidden] = useState(false);
+
   const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal();
   const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal();
   const isOpened = PageSelectModalData?.isOpened ?? false;
   const isOpened = PageSelectModalData?.isOpened ?? false;
 
 
@@ -71,22 +75,25 @@ export const PagePathHeader: FC<Props> = (props) => {
     setRenameInputShown(true);
     setRenameInputShown(true);
   }, [parentPagePath]);
   }, [parentPagePath]);
 
 
-  const clickOutSideHandler = useCallback((e) => {
-    const container = document.getElementById('page-path-header');
-
-    if (container && !container.contains(e.target)) {
-      setRenameInputShown(false);
-    }
-  }, []);
-
-  useEffect(() => {
-    document.addEventListener('click', clickOutSideHandler);
-
-    return () => {
-      document.removeEventListener('click', clickOutSideHandler);
-    };
-  }, [clickOutSideHandler]);
-
+  // TODO: https://redmine.weseek.co.jp/issues/141062
+  // Truncate left side and don't use getElementById
+  //
+  // useEffect(() => {
+  //   const areaElem = document.getElementById('grw-page-path-header-container');
+  //   const linkElem = document.getElementById('grw-page-path-hierarchical-link');
+
+  //   const areaElemWidth = areaElem?.offsetWidth;
+  //   const linkElemWidth = linkElem?.offsetWidth;
+
+  //   if (areaElemWidth && linkElemWidth) {
+  //     setIsIconHidden(linkElemWidth > areaElemWidth);
+  //   }
+  //   else {
+  //     setIsIconHidden(false);
+  //   }
+  // }, [currentPage]);
+  //
+  // const styles: CSSProperties | undefined = isIconHidden ? { direction: 'rtl' } : undefined;
 
 
   if (dPagePath.isRoot) {
   if (dPagePath.isRoot) {
     return <></>;
     return <></>;
@@ -95,27 +102,36 @@ export const PagePathHeader: FC<Props> = (props) => {
   return (
   return (
     <div
     <div
       id="page-path-header"
       id="page-path-header"
-      className={`d-flex ${moduleClass} small`}
+      className={`d-flex ${moduleClass} small position-relative`}
       onMouseEnter={() => setHover(true)}
       onMouseEnter={() => setHover(true)}
       onMouseLeave={() => setHover(false)}
       onMouseLeave={() => setHover(false)}
     >
     >
-      <div className="me-2">
+      <div
+        id="grw-page-path-header-container"
+        className="me-2 d-inline-block overflow-hidden"
+      >
         { isRenameInputShown && (
         { isRenameInputShown && (
-          <div className="position-absolute">
+          <div className="position-absolute w-100">
             <ClosableTextInput
             <ClosableTextInput
-              useAutosizeInput
               value={editingParentPagePath}
               value={editingParentPagePath}
-              placeholder={t('Input page name')}
+              placeholder={t('Input parent page path')}
               inputClassName="form-control-sm"
               inputClassName="form-control-sm"
               onPressEnter={onPressEnter}
               onPressEnter={onPressEnter}
               onPressEscape={onPressEscape}
               onPressEscape={onPressEscape}
               onChange={onInputChange}
               onChange={onInputChange}
               validationTarget={ValidationTarget.PAGE}
               validationTarget={ValidationTarget.PAGE}
+              onClickOutside={onPressEscape}
             />
             />
           </div>
           </div>
         ) }
         ) }
-        <div className={`${isRenameInputShown ? 'invisible' : ''}`}>
-          <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />
+        <div
+          className={`${isRenameInputShown ? 'invisible' : ''} text-truncate`}
+          // style={styles}
+        >
+          <PagePathHierarchicalLink
+            linkedPagePath={linkedPagePath}
+            // isIconHidden={isIconHidden}
+          />
         </div>
         </div>
       </div>
       </div>
 
 
@@ -140,4 +156,4 @@ export const PagePathHeader: FC<Props> = (props) => {
       {isOpened && <PageSelectModal />}
       {isOpened && <PageSelectModal />}
     </div>
     </div>
   );
   );
-};
+});

+ 1 - 0
apps/app/src/components/PageHeader/PageTitleHeader.module.scss

@@ -1,4 +1,5 @@
 .page-title-header :global {
 .page-title-header :global {
+  max-width: calc(100vw - 650px);
   input {
   input {
     min-width: 20px;
     min-width: 20px;
     min-height: unset;
     min-height: unset;

+ 4 - 5
apps/app/src/components/PageHeader/PageTitleHeader.tsx

@@ -70,12 +70,11 @@ export const PageTitleHeader: FC<Props> = (props) => {
 
 
 
 
   return (
   return (
-    <div className={`d-flex align-items-center ${moduleClass} ${props.className ?? ''}`}>
-      <div className="me-1">
+    <div className={`d-flex ${moduleClass} ${props.className ?? ''} position-relative`}>
+      <div className="me-1 d-inline-block overflow-hidden">
         { isRenameInputShown && (
         { isRenameInputShown && (
-          <div className="position-absolute">
+          <div className="position-absolute w-100">
             <ClosableTextInput
             <ClosableTextInput
-              useAutosizeInput
               value={editedPageTitle}
               value={editedPageTitle}
               placeholder={t('Input page name')}
               placeholder={t('Input page name')}
               inputClassName="fs-4"
               inputClassName="fs-4"
@@ -87,7 +86,7 @@ export const PageTitleHeader: FC<Props> = (props) => {
             />
             />
           </div>
           </div>
         ) }
         ) }
-        <h1 className={`mb-0 fs-4 ${isRenameInputShown ? 'invisible' : ''}`} onClick={onClickPageTitle}>
+        <h1 className={`mb-0 fs-4 ${isRenameInputShown ? 'invisible' : ''} text-truncate`} onClick={onClickPageTitle}>
           {pageTitle}
           {pageTitle}
         </h1>
         </h1>
       </div>
       </div>