TableOfContents.jsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import { debounce } from 'throttle-debounce';
  5. import AppContainer from '../services/AppContainer';
  6. import PageContainer from '../services/PageContainer';
  7. import { createSubscribedElement } from './UnstatedUtils';
  8. // get these value with
  9. // document.querySelector('.revision-toc').getBoundingClientRect().top
  10. const DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT = 190;
  11. const DEFAULT_REVISION_TOC_TOP_FOR_KIBELA_LAYOUT = 105;
  12. /**
  13. * @author Yuki Takei <yuki@weseek.co.jp>
  14. *
  15. * @export
  16. * @class TableOfContents
  17. * @extends {React.Component}
  18. */
  19. class TableOfContents extends React.Component {
  20. constructor(props) {
  21. super(props);
  22. this.resetScrollbarDebounced = debounce(100, this.resetScrollbar);
  23. }
  24. componentDidUpdate() {
  25. const { layoutType } = this.props.appContainer.config;
  26. if (layoutType === 'crowi') {
  27. return;
  28. }
  29. let defaultRevisionTocTop = DEFAULT_REVISION_TOC_TOP_FOR_GROWI_LAYOUT;
  30. if (layoutType === 'kibela') {
  31. defaultRevisionTocTop = DEFAULT_REVISION_TOC_TOP_FOR_KIBELA_LAYOUT;
  32. }
  33. // initialize
  34. this.resetScrollbar(defaultRevisionTocTop);
  35. /*
  36. * set event listener
  37. */
  38. // resize
  39. window.addEventListener('resize', (event) => {
  40. this.resetScrollbarDebounced(defaultRevisionTocTop);
  41. });
  42. // affix on
  43. $('#revision-toc').on('affixed.bs.affix', () => {
  44. this.resetScrollbar(this.getCurrentRevisionTocTop());
  45. });
  46. // affix off
  47. $('#revision-toc').on('affixed-top.bs.affix', () => {
  48. this.resetScrollbar(defaultRevisionTocTop);
  49. });
  50. }
  51. getCurrentRevisionTocTop() {
  52. // calculate absolute top of '#revision-toc' element
  53. const revisionTocElem = document.querySelector('.revision-toc');
  54. return revisionTocElem.getBoundingClientRect().top;
  55. }
  56. resetScrollbar(revisionTocTop) {
  57. const tocContentElem = document.querySelector('.revision-toc .markdownIt-TOC');
  58. // window height - revisionTocTop - .system-version height
  59. let h = window.innerHeight - revisionTocTop - 20;
  60. const tocContentHeight = tocContentElem.getBoundingClientRect().height + 15; // add margin
  61. h = Math.min(h, tocContentHeight);
  62. $('#revision-toc-content').slimScroll({
  63. railVisible: true,
  64. position: 'right',
  65. height: h,
  66. });
  67. }
  68. render() {
  69. const { tocHtml } = this.props.pageContainer.state;
  70. return (
  71. <div
  72. id="revision-toc-content"
  73. className="revision-toc-content"
  74. // eslint-disable-next-line react/no-danger
  75. dangerouslySetInnerHTML={{
  76. __html: tocHtml,
  77. }}
  78. />
  79. );
  80. }
  81. }
  82. /**
  83. * Wrapper component for using unstated
  84. */
  85. const TableOfContentsWrapper = (props) => {
  86. return createSubscribedElement(TableOfContents, props, [AppContainer, PageContainer]);
  87. };
  88. TableOfContents.propTypes = {
  89. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  90. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  91. };
  92. export default withTranslation()(TableOfContentsWrapper);