TableOfContents.jsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import React, { useCallback, useEffect, useMemo } from 'react';
  2. import PropTypes from 'prop-types';
  3. import loggerFactory from '@alias/logger';
  4. import { withTranslation } from 'react-i18next';
  5. import PageContainer from '../services/PageContainer';
  6. import NavigationContainer from '../services/NavigationContainer';
  7. import { withUnstatedContainers } from './UnstatedUtils';
  8. import StickyStretchableScroller from './StickyStretchableScroller';
  9. import RecentlyCreatedIcon from './Icons/RecentlyCreatedIcon';
  10. // eslint-disable-next-line no-unused-vars
  11. const logger = loggerFactory('growi:TableOfContents');
  12. const WIKI_HEADER_LINK = 120;
  13. /**
  14. * @author Yuki Takei <yuki@weseek.co.jp>
  15. *
  16. */
  17. const TableOfContents = (props) => {
  18. const { pageContainer, navigationContainer } = props;
  19. const { pageUser } = pageContainer.state;
  20. const isUserPage = pageUser != null;
  21. const calcViewHeight = useCallback(() => {
  22. // calculate absolute top of '#revision-toc' element
  23. const containerElem = document.querySelector('#revision-toc');
  24. const containerTop = containerElem.getBoundingClientRect().top;
  25. // window height - revisionToc top - .system-version - .grw-fab-container height - top-of-table-contents height
  26. if (isUserPage) {
  27. return window.innerHeight - containerTop - 20 - 155 - 26 - 40;
  28. }
  29. return window.innerHeight - containerTop - 20 - 155 - 26;
  30. }, [isUserPage]);
  31. const { tocHtml } = pageContainer.state;
  32. // execute after generation toc html
  33. useEffect(() => {
  34. const tocDom = document.getElementById('revision-toc-content');
  35. const anchorsInToc = Array.from(tocDom.getElementsByTagName('a'));
  36. navigationContainer.addSmoothScrollEvent(anchorsInToc);
  37. }, [tocHtml, navigationContainer]);
  38. // get element for smoothScroll
  39. const getBookMarkListHeaderDom = useMemo(() => { return document.getElementById('bookmarks-list') }, []);
  40. const getRecentlyCreatedListHeaderDom = useMemo(() => { return document.getElementById('recently-created-list') }, []);
  41. return (
  42. <>
  43. <StickyStretchableScroller
  44. contentsElemSelector=".revision-toc .markdownIt-TOC"
  45. stickyElemSelector="#revision-toc"
  46. calcViewHeightFunc={calcViewHeight}
  47. >
  48. <div
  49. id="revision-toc-content"
  50. className="revision-toc-content top-of-table-contents"
  51. // eslint-disable-next-line react/no-danger
  52. dangerouslySetInnerHTML={{
  53. __html: tocHtml,
  54. }}
  55. />
  56. </StickyStretchableScroller>
  57. { isUserPage && (
  58. <div className="mt-3 d-flex justify-content-around">
  59. <button
  60. type="button"
  61. className="btn btn-outline-secondary btn-sm"
  62. onClick={() => navigationContainer.smoothScrollIntoView(getBookMarkListHeaderDom, WIKI_HEADER_LINK)}
  63. >
  64. <i className="mr-2 icon-star"></i>
  65. <span>Bookmarks</span>
  66. </button>
  67. <button
  68. type="button"
  69. className="btn btn-outline-secondary btn-sm"
  70. onClick={() => navigationContainer.smoothScrollIntoView(getRecentlyCreatedListHeaderDom, WIKI_HEADER_LINK)}
  71. >
  72. <i className="grw-icon-container-recently-created mr-2"><RecentlyCreatedIcon /></i>
  73. <span>Recently Created</span>
  74. </button>
  75. </div>
  76. )}
  77. </>
  78. );
  79. };
  80. /**
  81. * Wrapper component for using unstated
  82. */
  83. const TableOfContentsWrapper = withUnstatedContainers(TableOfContents, [PageContainer, NavigationContainer]);
  84. TableOfContents.propTypes = {
  85. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  86. navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
  87. };
  88. export default withTranslation()(TableOfContentsWrapper);