Просмотр исходного кода

Merge branch 'feat/article-area-renovation' into feat/gw-4246-rename-component-recent-changes-icon

# Conflicts:
#	src/client/js/components/PageAccessoriesModal.jsx
yusuketk 5 лет назад
Родитель
Сommit
76fbf8d178

+ 98 - 39
src/client/js/components/CustomNavigation.jsx

@@ -1,50 +1,57 @@
-import React, { useEffect, useState, useRef } from 'react';
+import React, {
+  useEffect, useState, useRef, useMemo, useCallback,
+} from 'react';
 import PropTypes from 'prop-types';
 import {
   Nav, NavItem, NavLink, TabContent, TabPane,
 } from 'reactstrap';
 
 
-const CustomNavigation = (props) => {
-  const { navTabMapping } = props;
-  const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[0]);
+export const CustomNav = (props) => {
+  const navContainer = useRef();
   const [sliderWidth, setSliderWidth] = useState(0);
   const [sliderMarginLeft, setSliderMarginLeft] = useState(0);
-  const navBar = useRef();
-  const navTabs = {};
 
-  Object.keys(props.navTabMapping).forEach((key) => {
-    navTabs[key] = React.createRef();
-  });
+  const { activeTab, navTabMapping, onNavSelected } = props;
 
-  function switchActiveTab(activeTab) {
-    setActiveTab(activeTab);
-  }
+  const navTabRefs = useMemo(() => {
+    const obj = {};
+    Object.keys(navTabMapping).forEach((key) => {
+      obj[key] = React.createRef();
+    });
+    return obj;
+  }, [navTabMapping]);
 
-  // Might make this dynamic for px, %, pt, em
-  function getPercentage(min, max) {
-    return min / max * 100;
-  }
+  const navLinkClickHandler = useCallback((key) => {
+    if (onNavSelected != null) {
+      onNavSelected(key);
+    }
+  }, [onNavSelected]);
 
   function registerNavLink(key, elm) {
     if (elm != null) {
-      navTabs[key] = elm;
+      navTabRefs[key] = elm;
     }
   }
 
+  // Might make this dynamic for px, %, pt, em
+  function getPercentage(min, max) {
+    return min / max * 100;
+  }
+
   useEffect(() => {
     if (activeTab === '') {
       return;
     }
 
-    if (navBar == null || navTabs == null) {
+    if (navContainer == null) {
       return;
     }
 
     let tempML = 0;
 
-    const styles = Object.entries(navTabs).map((el) => {
-      const width = getPercentage(el[1].offsetWidth, navBar.current.offsetWidth);
+    const styles = Object.entries(navTabRefs).map((el) => {
+      const width = getPercentage(el[1].offsetWidth, navContainer.current.offsetWidth);
       const marginLeft = tempML;
       tempML += width;
       return { width, marginLeft };
@@ -54,23 +61,26 @@ const CustomNavigation = (props) => {
     setSliderWidth(width);
     setSliderMarginLeft(marginLeft);
 
-  }, [activeTab, navTabMapping]);
+  }, [activeTab, navTabRefs, navTabMapping]);
 
   return (
-    <React.Fragment>
-      <div ref={navBar}>
+    <>
+      <div ref={navContainer}>
         <Nav className="nav-title grw-custom-navbar" id="grw-custom-navbar">
-          {Object.entries(props.navTabMapping).map(([key, value]) => {
+          {Object.entries(navTabMapping).map(([key, value]) => {
+
+            const isActive = activeTab === key;
+            const isLinkEnabled = value.isLinkEnabled != null ? value.isLinkEnabled(value) : true;
+            const { Icon, i18n } = value;
+
             return (
               <NavItem
-
                 key={key}
                 type="button"
-                className={`p-0 grw-custom-navtab ${activeTab === key && 'active'}}`}
+                className={`p-0 grw-custom-navtab ${isActive && 'active'}}`}
               >
-                <NavLink key={key} innerRef={elm => registerNavLink(key, elm)} onClick={() => { switchActiveTab(key) }}>
-                  {value.icon}
-                  {value.i18n}
+                <NavLink key={key} innerRef={elm => registerNavLink(key, elm)} disabled={!isLinkEnabled} onClick={() => navLinkClickHandler(key)}>
+                  <Icon /> {i18n}
                 </NavLink>
               </NavItem>
             );
@@ -78,21 +88,70 @@ const CustomNavigation = (props) => {
         </Nav>
       </div>
       <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
-      <TabContent activeTab={activeTab} className="p-4">
-        {Object.entries(props.navTabMapping).map(([key, value]) => {
-          return (
-            <TabPane key={key} tabId={key}>
-              {value.tabContent}
-            </TabPane>
-          );
-        })}
-      </TabContent>
+    </>
+  );
+
+};
+
+CustomNav.propTypes = {
+  activeTab: PropTypes.string.isRequired,
+  navTabMapping: PropTypes.object.isRequired,
+  onNavSelected: PropTypes.func,
+};
+
+
+export const CustomTabContent = (props) => {
+
+  const { activeTab, navTabMapping, additionalClassNames } = props;
+
+  return (
+    <TabContent activeTab={activeTab} className={additionalClassNames.join(' ')}>
+      {Object.entries(navTabMapping).map(([key, value]) => {
+
+        const { Content } = value;
+
+        return (
+          <TabPane key={key} tabId={key}>
+            <Content />
+          </TabPane>
+        );
+      })}
+    </TabContent>
+  );
+
+};
+
+CustomTabContent.propTypes = {
+  activeTab: PropTypes.string.isRequired,
+  navTabMapping: PropTypes.object.isRequired,
+  additionalClassNames: PropTypes.arrayOf(PropTypes.string),
+};
+CustomTabContent.defaultProps = {
+  additionalClassNames: [],
+};
+
+
+const CustomNavigation = (props) => {
+  const { navTabMapping, defaultTabIndex, tabContentClasses } = props;
+  const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
+
+  return (
+    <React.Fragment>
+
+      <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={setActiveTab} />
+      <CustomTabContent activeTab={activeTab} navTabMapping={navTabMapping} additionalClassNames={tabContentClasses} />
+
     </React.Fragment>
   );
 };
 
 CustomNavigation.propTypes = {
-  navTabMapping: PropTypes.object,
+  navTabMapping: PropTypes.object.isRequired,
+  defaultTabIndex: PropTypes.number,
+  tabContentClasses: PropTypes.arrayOf(PropTypes.string),
+};
+CustomNavigation.defaultProps = {
+  tabContentClasses: ['p-4'],
 };
 
 export default CustomNavigation;

+ 4 - 4
src/client/js/components/NotFoundPage.jsx

@@ -12,15 +12,15 @@ const NotFoundPage = (props) => {
 
   const navTabMapping = {
     pagelist: {
-      icon: <PageListIcon />,
+      Icon: PageListIcon,
+      Content: PageList,
       i18n: t('page_list'),
-      tabContent: <PageList />,
       index: 0,
     },
     timeLine: {
-      icon: <TimeLineIcon />,
+      Icon: TimeLineIcon,
+      Content: PageTimeline,
       i18n: t('Timeline View'),
-      tabContent: <PageTimeline />,
       index: 1,
     },
   };

+ 51 - 108
src/client/js/components/PageAccessoriesModal.jsx

@@ -1,12 +1,11 @@
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useMemo } from 'react';
 import PropTypes from 'prop-types';
 
 import {
-  Modal, ModalBody, ModalHeader, Nav, NavItem, NavLink, TabContent, TabPane, UncontrolledTooltip,
+  Modal, ModalBody, ModalHeader, TabContent, TabPane,
 } from 'reactstrap';
 
 import { withTranslation } from 'react-i18next';
-
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import HistoryIcon from './Icons/HistoryIcon';
@@ -20,135 +19,80 @@ import PageTimeline from './PageTimeline';
 import PageList from './PageList';
 import PageHistory from './PageHistory';
 import ShareLink from './ShareLink/ShareLink';
-
-
-const navTabMapping = {
-  pagelist: {
-    icon: <PageListIcon />,
-    i18n: 'page_list',
-    index: 0,
-    isGuestNotAllowed: false,
-  },
-  timeline:  {
-    icon: <TimeLineIcon />,
-    i18n: 'Timeline View',
-    index: 1,
-    isGuestNotAllowed: false,
-  },
-  pageHistory: {
-    icon: <HistoryIcon />,
-    i18n: 'History',
-    index: 2,
-    isGuestNotAllowed: false,
-  },
-  attachment: {
-    icon: <AttachmentIcon />,
-    i18n: 'attachment_data',
-    index: 3,
-    isGuestNotAllowed: false,
-  },
-  shareLink: {
-    icon: <ShareLinkIcon />,
-    i18n: 'share_links.share_link_management',
-    index: 4,
-    isGuestNotAllowed: true,
-  },
-};
+import { CustomNav } from './CustomNavigation';
 
 const PageAccessoriesModal = (props) => {
-  const { t, pageAccessoriesContainer, isGuestUserMode } = props;
+  const {
+    t, pageAccessoriesContainer, onClose, isGuestUserMode,
+  } = props;
   const { switchActiveTab } = pageAccessoriesContainer;
-  const { activeTab } = pageAccessoriesContainer.state;
-
-  const [sliderWidth, setSliderWidth] = useState(null);
-  const [sliderMarginLeft, setSliderMarginLeft] = useState(null);
-
-  function closeModalHandler() {
-    if (props.onClose == null) {
-      return;
-    }
-    props.onClose();
-  }
-
-  // Might make this dynamic for px, %, pt, em
-  function getPercentage(min, max) {
-    return min / max * 100;
-  }
-
-  useEffect(() => {
-    if (activeTab === '') {
+  const { activeTab, activeComponents } = pageAccessoriesContainer.state;
+
+  const navTabMapping = useMemo(() => {
+    return {
+      pagelist: {
+        Icon: PageListIcon,
+        i18n: t('page_list'),
+        index: 0,
+      },
+      timeline:  {
+        Icon: TimeLineIcon,
+        i18n: t('Timeline View'),
+        index: 1,
+      },
+      pageHistory: {
+        Icon: HistoryIcon,
+        i18n: t('History'),
+        index: 2,
+      },
+      attachment: {
+        Icon: AttachmentIcon,
+        i18n: t('attachment_data'),
+        index: 3,
+      },
+      shareLink: {
+        Icon: ShareLinkIcon,
+        i18n: t('share_links.share_link_management'),
+        index: 4,
+        isLinkEnabled: v => !isGuestUserMode,
+      },
+    };
+  }, [t, isGuestUserMode]);
+
+  const closeModalHandler = useCallback(() => {
+    if (onClose == null) {
       return;
     }
-
-    const navTitle = document.getElementById('nav-title');
-    const navTabs = document.querySelectorAll('li.nav-link');
-
-    if (navTitle == null || navTabs == null) {
-      return;
-    }
-
-    let tempML = 0;
-
-    const styles = [].map.call(navTabs, (el) => {
-      const width = getPercentage(el.offsetWidth, navTitle.offsetWidth);
-      const marginLeft = tempML;
-      tempML += width;
-      return { width, marginLeft };
-    });
-
-    const { width, marginLeft } = styles[navTabMapping[activeTab].index];
-
-    setSliderWidth(width);
-    setSliderMarginLeft(marginLeft);
-
-  }, [activeTab]);
-
+    onClose();
+  }, [onClose]);
 
   return (
     <React.Fragment>
       <Modal size="xl" isOpen={props.isOpen} toggle={closeModalHandler} className="grw-page-accessories-modal">
         <ModalHeader className="p-0" toggle={closeModalHandler}>
-          <Nav className="nav-title" id="nav-title">
-            {Object.entries(navTabMapping).map(([key, value]) => {
-              const isDisabledNavLink = (isGuestUserMode && value.isGuestNotAllowed);
-              return (
-                <React.Fragment key={key}>
-                  <NavItem id={key} type="button" className={`p-0 nav-link ${activeTab === key && 'active'}`}>
-                    <NavLink disabled={isDisabledNavLink} onClick={() => { switchActiveTab(key) }}>
-                      {value.icon}
-                      {t(value.i18n)}
-                    </NavLink>
-                  </NavItem>
-                  {(isDisabledNavLink) && (
-                    <UncontrolledTooltip placement="bottom" target={key} fade={false}>
-                      {t('Not available for guest')}
-                    </UncontrolledTooltip>
-                  )}
-                </React.Fragment>
-              );
-            })}
-          </Nav>
-          <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
+          <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} />
         </ModalHeader>
         <ModalBody className="overflow-auto grw-modal-body-style p-0">
+          {/* Do not use CustomTabContent because of performance problem:
+              the 'navTabMapping[tabId].Content' for PageAccessoriesModal depends on activeComponents */}
           <TabContent activeTab={activeTab} className="p-5">
             <TabPane tabId="pagelist">
-              {pageAccessoriesContainer.state.activeComponents.has('pagelist') && <PageList />}
+              {activeComponents.has('pagelist') && <PageList />}
             </TabPane>
             <TabPane tabId="timeline">
-              {pageAccessoriesContainer.state.activeComponents.has('timeline') && <PageTimeline /> }
+              {activeComponents.has('timeline') && <PageTimeline /> }
             </TabPane>
             <TabPane tabId="pageHistory">
               <div className="overflow-auto">
-                {pageAccessoriesContainer.state.activeComponents.has('pageHistory') && <PageHistory /> }
+                {activeComponents.has('pageHistory') && <PageHistory /> }
               </div>
             </TabPane>
             <TabPane tabId="attachment">
-              {pageAccessoriesContainer.state.activeComponents.has('attachment') && <PageAttachment />}
+              {activeComponents.has('attachment') && <PageAttachment />}
             </TabPane>
             {!isGuestUserMode && (
               <TabPane tabId="shareLink">
-                {pageAccessoriesContainer.state.activeComponents.has('shareLink') && <ShareLink />}
+                {activeComponents.has('shareLink') && <ShareLink />}
               </TabPane>
             )}
           </TabContent>
@@ -165,7 +109,6 @@ const PageAccessoriesModalWrapper = withUnstatedContainers(PageAccessoriesModal,
 
 PageAccessoriesModal.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
-  // pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   pageAccessoriesContainer: PropTypes.instanceOf(PageAccessoriesContainer).isRequired,
   isGuestUserMode: PropTypes.bool.isRequired,
   isOpen: PropTypes.bool.isRequired,

+ 2 - 2
src/client/js/components/TrashPageList.jsx

@@ -11,9 +11,9 @@ const TrashPageList = (props) => {
 
   const navTabMapping = {
     pagelist: {
-      icon: <PageListIcon />,
+      Icon: PageListIcon,
+      Content: PageList,
       i18n: t('page_list'),
-      tabContent: <PageList />,
       index: 0,
     },
   };

+ 11 - 0
src/client/styles/scss/theme/_apply-colors.scss

@@ -418,6 +418,17 @@ body.on-edit {
   }
 }
 
+/*
+ * GROWI comment
+ */
+.page-comment-meta .page-comment-revision svg {
+  fill: $color-link;
+
+  &:hover() {
+    fill: $color-link-hover;
+  }
+}
+
 /*
  * GROWI comment form
  */