TableOfContents.jsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import React, { useCallback, useEffect, useState } from 'react';
  2. import PropTypes from 'prop-types';
  3. import PageContainer from '~/client/services/PageContainer';
  4. import { blinkElem } from '~/client/util/blink-section-header';
  5. import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
  6. import loggerFactory from '~/utils/logger';
  7. import { StickyStretchableScroller } from './StickyStretchableScroller';
  8. import { withUnstatedContainers } from './UnstatedUtils';
  9. // eslint-disable-next-line no-unused-vars
  10. const logger = loggerFactory('growi:TableOfContents');
  11. /**
  12. * @author Yuki Takei <yuki@weseek.co.jp>
  13. *
  14. */
  15. const TableOfContents = (props) => {
  16. const { pageContainer } = props;
  17. const { pageUser } = pageContainer.state;
  18. const isUserPage = pageUser != null;
  19. const [tocHtml, setTocHtml] = useState('');
  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. useEffect(() => {
  38. const tocDom = document.getElementById('revision-toc-content');
  39. const anchorsInToc = Array.from(tocDom.getElementsByTagName('a'));
  40. addSmoothScrollEvent(anchorsInToc, blinkElem);
  41. }, [tocHtml]);
  42. // == TODO: render ToC without globalEmitter -- Yuki Takei
  43. //
  44. // set handler to render ToC
  45. // useEffect(() => {
  46. // const handler = html => setTocHtml(html);
  47. // globalEmitter.on('renderTocHtml', handler);
  48. // return function cleanup() {
  49. // globalEmitter.removeListener('renderTocHtml', handler);
  50. // };
  51. // }, [globalEmitter]);
  52. return (
  53. <StickyStretchableScroller
  54. stickyElemSelector=".grw-side-contents-sticky-container"
  55. calcViewHeight={calcViewHeight}
  56. >
  57. { tocHtml !== ''
  58. ? (
  59. <div
  60. id="revision-toc-content"
  61. className="revision-toc-content mb-3"
  62. // eslint-disable-next-line react/no-danger
  63. dangerouslySetInnerHTML={{ __html: tocHtml }}
  64. />
  65. )
  66. : (
  67. <div
  68. id="revision-toc-content"
  69. className="revision-toc-content mb-2"
  70. >
  71. </div>
  72. ) }
  73. </StickyStretchableScroller>
  74. );
  75. };
  76. /**
  77. * Wrapper component for using unstated
  78. */
  79. const TableOfContentsWrapper = withUnstatedContainers(TableOfContents, [PageContainer]);
  80. TableOfContents.propTypes = {
  81. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  82. };
  83. export default TableOfContentsWrapper;