TableOfContents.jsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import React, { useCallback, useEffect } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import loggerFactory from '~/utils/logger';
  5. import PageContainer from '~/client/services/PageContainer';
  6. import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
  7. import { blinkElem } from '~/client/util/blink-section-header';
  8. import { withUnstatedContainers } from './UnstatedUtils';
  9. import StickyStretchableScroller from './StickyStretchableScroller';
  10. // eslint-disable-next-line no-unused-vars
  11. const logger = loggerFactory('growi:TableOfContents');
  12. /**
  13. * @author Yuki Takei <yuki@weseek.co.jp>
  14. *
  15. */
  16. const TableOfContents = (props) => {
  17. const { t, pageContainer } = props;
  18. const { pageUser } = pageContainer.state;
  19. const isUserPage = pageUser != null;
  20. const calcViewHeight = useCallback(() => {
  21. // calculate absolute top of '#revision-toc' element
  22. const parentElem = document.querySelector('.grw-side-contents-container');
  23. const parentBottom = parentElem.getBoundingClientRect().bottom;
  24. const containerElem = document.querySelector('#revision-toc');
  25. const containerTop = containerElem.getBoundingClientRect().top;
  26. const containerComputedStyle = getComputedStyle(containerElem);
  27. const containerPaddingTop = parseFloat(containerComputedStyle['padding-top']);
  28. // get smaller bottom line of window height - .system-version height - margin 5px) and containerTop
  29. let bottom = Math.min(window.innerHeight - 20 - 5, parentBottom);
  30. if (isUserPage) {
  31. // raise the bottom line by the height and margin-top of UserContentLinks
  32. bottom -= 45;
  33. }
  34. // bottom - revisionToc top
  35. return bottom - (containerTop + containerPaddingTop);
  36. }, [isUserPage]);
  37. const { tocHtml } = pageContainer.state;
  38. // execute after generation toc html
  39. useEffect(() => {
  40. const tocDom = document.getElementById('revision-toc-content');
  41. const anchorsInToc = Array.from(tocDom.getElementsByTagName('a'));
  42. addSmoothScrollEvent(anchorsInToc, blinkElem);
  43. }, [tocHtml]);
  44. return (
  45. <StickyStretchableScroller
  46. contentsElemSelector=".revision-toc .markdownIt-TOC"
  47. stickyElemSelector=".grw-side-contents-sticky-container"
  48. calcViewHeightFunc={calcViewHeight}
  49. >
  50. { tocHtml !== ''
  51. ? (
  52. <div
  53. id="revision-toc-content"
  54. className="revision-toc-content mb-3"
  55. // eslint-disable-next-line react/no-danger
  56. dangerouslySetInnerHTML={{ __html: tocHtml }}
  57. />
  58. )
  59. : (
  60. <div
  61. id="revision-toc-content"
  62. className="revision-toc-content mb-2"
  63. >
  64. </div>
  65. ) }
  66. </StickyStretchableScroller>
  67. );
  68. };
  69. /**
  70. * Wrapper component for using unstated
  71. */
  72. const TableOfContentsWrapper = withUnstatedContainers(TableOfContents, [PageContainer]);
  73. TableOfContents.propTypes = {
  74. t: PropTypes.func.isRequired, // i18next
  75. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  76. };
  77. export default withTranslation()(TableOfContentsWrapper);