Yuki Takei 5 лет назад
Родитель
Сommit
04b29fcab6

+ 48 - 15
src/client/js/components/StickyStretchableScroller.jsx

@@ -1,8 +1,12 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useCallback } from 'react';
 import PropTypes from 'prop-types';
 import loggerFactory from '@alias/logger';
 
 import { debounce } from 'throttle-debounce';
+import StickyEvents from 'sticky-events';
+
+import NavigationContainer from '../services/NavigationContainer';
+import { withUnstatedContainers } from './UnstatedUtils';
 
 const logger = loggerFactory('growi:cli:StickyStretchableScroller');
 
@@ -10,15 +14,18 @@ const logger = loggerFactory('growi:cli:StickyStretchableScroller');
 const StickyStretchableScroller = (props) => {
 
   const {
-    children, contentsElemSelector, calcViewHeightFunc, calcContentsHeightFunc,
+    navigationContainer,
+    children, contentsElemSelector, stickyElemSelector,
+    calcViewHeightFunc, calcContentsHeightFunc,
   } = props;
 
   const id = children.props.id;
 
+
   /**
    * Reset scrollbar
    */
-  const resetScrollbar = () => {
+  const resetScrollbar = useCallback(() => {
     const contentsElem = document.querySelector(contentsElemSelector);
     if (contentsElem == null) {
       return;
@@ -34,21 +41,37 @@ const StickyStretchableScroller = (props) => {
     logger.debug('viewHeight', viewHeight);
     logger.debug('contentsHeight', contentsHeight);
 
-    if (viewHeight < contentsHeight) {
-      $(`#${id}`).slimScroll({
-        railVisible: true,
-        position: 'right',
-        height: viewHeight,
-      });
-    }
-    else {
+    $(`#${id}`).slimScroll({
+      railVisible: true,
+      position: 'right',
+      height: viewHeight,
+    });
+    if (contentsHeight < viewHeight) {
       $(`#${id}`).slimScroll({ destroy: true });
     }
-  };
+  }, [contentsElemSelector, calcViewHeightFunc, calcContentsHeightFunc]);
+
   const resetScrollbarDebounced = debounce(100, resetScrollbar);
 
 
-  // setup resize event
+  // setup effect by sticky event
+  useEffect(() => {
+    if (stickyElemSelector == null) {
+      return;
+    }
+
+    // sticky
+    // See: https://github.com/ryanwalters/sticky-events
+    const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
+    const { stickySelector } = stickyEvents;
+    const elem = document.querySelector(stickySelector);
+    elem.addEventListener(StickyEvents.CHANGE, (event) => {
+      logger.debug('StickyEvents.CHANGE detected');
+      resetScrollbar();
+    });
+  }, []);
+
+  // setup effect by resizing event
   useEffect(() => {
     const resizeHandler = (event) => {
       resetScrollbarDebounced();
@@ -62,7 +85,14 @@ const StickyStretchableScroller = (props) => {
     };
   }, []);
 
-  // setup update scrollbar effect
+  // setup effect by isScrollTop
+  useEffect(() => {
+    if (navigationContainer.state.isScrollTop) {
+      resetScrollbar();
+    }
+  }, [navigationContainer.state.isScrollTop]);
+
+  // setup effect by update props
   useEffect(() => {
     resetScrollbarDebounced();
   });
@@ -75,11 +105,14 @@ const StickyStretchableScroller = (props) => {
 };
 
 StickyStretchableScroller.propTypes = {
+  navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
   children: PropTypes.node.isRequired,
   contentsElemSelector: PropTypes.string.isRequired,
 
+  stickyElemSelector: PropTypes.string,
+
   calcViewHeightFunc: PropTypes.func,
   calcContentsHeightFunc: PropTypes.func,
 };
 
-export default StickyStretchableScroller;
+export default withUnstatedContainers(StickyStretchableScroller, [NavigationContainer]);

+ 6 - 62
src/client/js/components/TableOfContents.jsx

@@ -4,25 +4,15 @@ import loggerFactory from '@alias/logger';
 
 import { withTranslation } from 'react-i18next';
 
-import { debounce } from 'throttle-debounce';
-import StickyEvents from 'sticky-events';
-
-import { isUserPage } from '@commons/util/path-utils';
-
 import AppContainer from '../services/AppContainer';
 import PageContainer from '../services/PageContainer';
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import StickyStretchableScroller from './StickyStretchableScroller';
 
+// eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:TableOfContents');
 
-// get these value with
-//   document.querySelector('.revision-toc').getBoundingClientRect().top
-const DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT = 204;
-const DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT_USER_PAGE = 264;
-const DEFAULT_REVISION_TOC_TOP_FOR_KIBELA_LAYOUT = 105;
-
 /**
  * @author Yuki Takei <yuki@weseek.co.jp>
  *
@@ -32,61 +22,15 @@ const DEFAULT_REVISION_TOC_TOP_FOR_KIBELA_LAYOUT = 105;
  */
 class TableOfContents extends React.Component {
 
-  constructor(props) {
-    super(props);
-
-    // this.init = this.init.bind(this);
-    // this.resetScrollbarDebounced = debounce(100, this.resetScrollbar);
-
-    // const { layoutType } = this.props.appContainer.config;
-    // const { path } = this.props.pageContainer.state;
-
-    // this.defaultRevisionTocTop = DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT;
-
-    // if (isUserPage(path)) {
-    //   this.defaultRevisionTocTop = DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT_USER_PAGE;
-    // }
-
-    // if (layoutType === 'kibela') {
-    //   this.defaultRevisionTocTop = DEFAULT_REVISION_TOC_TOP_FOR_KIBELA_LAYOUT;
-    // }
-  }
-
-  // init() {
-  //   /*
-  //    * set event listener
-  //    */
-  //   // resize
-  //   window.addEventListener('resize', (event) => {
-  //     this.resetScrollbarDebounced(this.defaultRevisionTocTop);
-  //   });
-
-  //   // sticky
-  //   // See: https://github.com/ryanwalters/sticky-events
-  //   const stickyEvents = new StickyEvents({
-  //     stickySelector: '#revision-toc',
-  //   });
-  //   const { stickySelector } = stickyEvents;
-  //   const elem = document.querySelector(stickySelector);
-  //   elem.addEventListener(StickyEvents.STUCK, (event) => {
-  //     logger.debug('StickyEvents.STUCK detected');
-  //     this.resetScrollbar();
-  //   });
-  //   elem.addEventListener(StickyEvents.UNSTUCK, (event) => {
-  //     logger.debug('StickyEvents.UNSTUCK detected');
-  //     this.resetScrollbar(this.defaultRevisionTocTop);
-  //   });
-  // }
-
-  getCurrentRevisionTocTop() {
+  getContainerTop() {
     // calculate absolute top of '#revision-toc' element
-    const revisionTocElem = document.querySelector('.revision-toc');
-    return revisionTocElem.getBoundingClientRect().top;
+    const containerElem = document.querySelector('#revision-toc');
+    return containerElem.getBoundingClientRect().top;
   }
 
   calcViewHeight() {
     // window height - revisionTocTop - .system-version height
-    return window.innerHeight - this.getCurrentRevisionTocTop() - 20;
+    return window.innerHeight - this.getContainerTop() - 20;
   }
 
   render() {
@@ -95,8 +39,8 @@ class TableOfContents extends React.Component {
     return (
       <StickyStretchableScroller
         contentsElemSelector=".revision-toc .markdownIt-TOC"
+        stickyElemSelector="#revision-toc"
         calcViewHeightFunc={() => this.calcViewHeight()}
-        calcContentsHeightFunc={contentsElem => contentsElem.getBoundingClientRect().height + 15} // add margin
       >
         <div
           id="revision-toc-content"

+ 21 - 0
src/client/js/services/NavigationContainer.js

@@ -4,6 +4,9 @@ import { Container } from 'unstated';
  * Service container related to options for Application
  * @extends {Container} unstated Container
  */
+
+const scrollThresForThrottling = 100;
+
 export default class NavigationContainer extends Container {
 
   constructor(appContainer) {
@@ -26,6 +29,8 @@ export default class NavigationContainer extends Container {
 
       sidebarContentsId: 'recent',
 
+      isScrollTop: true,
+
       isPageCreateModalShown: false,
     };
 
@@ -34,6 +39,7 @@ export default class NavigationContainer extends Container {
 
     this.initHotkeys();
     this.initDeviceSize();
+    this.initScrollEvent();
   }
 
   /**
@@ -82,6 +88,21 @@ export default class NavigationContainer extends Container {
     this.appContainer.addBreakpointListener('md', mdOrAvobeHandler, true);
   }
 
+  initScrollEvent() {
+    window.addEventListener('scroll', () => {
+      const currentYOffset = window.pageYOffset;
+
+      // original throttling
+      if (scrollThresForThrottling < currentYOffset) {
+        return;
+      }
+
+      this.setState({
+        isScrollTop: currentYOffset === 0,
+      });
+    });
+  }
+
   setEditorMode(editorMode) {
     this.setState({ editorMode });
     this.updateDrawerMode({ ...this.state, editorMode }); // generate newest state object

+ 0 - 20
src/client/js/services/PageContainer.js

@@ -16,9 +16,6 @@ import {
 } from '../util/interceptor/drawio-interceptor';
 
 const logger = loggerFactory('growi:services:PageContainer');
-const scrollThresForSticky = 0;
-const scrollThresForCompact = 30;
-const scrollThresForThrottling = 100;
 
 /**
  * Service container related to Page
@@ -73,9 +70,6 @@ export default class PageContainer extends Container {
       pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd') || null,
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       isHackmdDraftUpdatingInRealtime: false,
-
-      isHeaderSticky: false,
-      isSubnavCompact: false,
     };
 
     const { interceptorManager } = this.appContainer;
@@ -92,20 +86,6 @@ export default class PageContainer extends Container {
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
     this.addWebSocketEventHandlers();
 
-    window.addEventListener('scroll', () => {
-      const currentYOffset = window.pageYOffset;
-
-      // original throttling
-      if (this.state.isSubnavCompact && scrollThresForThrottling < currentYOffset) {
-        return;
-      }
-
-      this.setState({
-        isHeaderSticky: scrollThresForSticky < currentYOffset,
-        isSubnavCompact: scrollThresForCompact < currentYOffset,
-      });
-    });
-
     const unlinkPageButton = document.getElementById('unlink-page-button');
     if (unlinkPageButton != null) {
       unlinkPageButton.addEventListener('click', async() => {