RevisionPath.jsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import urljoin from 'url-join';
  5. import CopyDropdown from './CopyDropdown';
  6. class RevisionPath extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. this.state = {
  10. pages: [],
  11. isListPage: false,
  12. isLinkToListPage: true,
  13. };
  14. // retrieve xss library from window
  15. this.xss = window.xss;
  16. }
  17. componentWillMount() {
  18. // whether list page or not
  19. const isListPage = this.props.pagePath.match(/\/$/);
  20. this.setState({ isListPage });
  21. // whether set link to '/'
  22. const { behaviorType } = this.props;
  23. const isLinkToListPage = (behaviorType === 'crowi');
  24. this.setState({ isLinkToListPage });
  25. this.generateHierarchyData();
  26. }
  27. /**
  28. * 1. split `pagePath` with '/'
  29. * 2. list hierararchical page paths
  30. *
  31. * e.g.
  32. * when `pagePath` is '/foo/bar/baz`
  33. * return:
  34. * [
  35. * { pagePath: '/foo', pageName: 'foo' },
  36. * { pagePath: '/foo/bar', pageName: 'bar' },
  37. * { pagePath: '/foo/bar/baz', pageName: 'baz' },
  38. * ]
  39. */
  40. generateHierarchyData() {
  41. // generate pages obj
  42. const splitted = this.props.pagePath.split(/\//);
  43. splitted.shift(); // omit first element with shift()
  44. if (splitted[splitted.length - 1] === '') {
  45. splitted.pop(); // omit last element with unshift()
  46. }
  47. const pages = [];
  48. const pagePaths = [];
  49. splitted.forEach((pageName) => {
  50. pagePaths.push(encodeURIComponent(pageName));
  51. pages.push({
  52. pagePath: urljoin('/', ...pagePaths),
  53. pageName: this.xss.process(pageName),
  54. });
  55. });
  56. this.setState({ pages });
  57. }
  58. showToolTip() {
  59. const buttonId = '#copyPagePathDropdown';
  60. $(buttonId).tooltip('show');
  61. setTimeout(() => {
  62. $(buttonId).tooltip('hide');
  63. }, 1000);
  64. }
  65. generateLinkElementToListPage(pagePath, isLinkToListPage, isLastElement) {
  66. /* eslint-disable no-else-return */
  67. if (isLinkToListPage) {
  68. return <a href={`${pagePath}/`} className={(isLastElement && !this.state.isListPage) ? 'last-path' : ''}>/</a>;
  69. }
  70. else if (!isLastElement) {
  71. return <span>/</span>;
  72. }
  73. else {
  74. return <span></span>;
  75. }
  76. /* eslint-enable no-else-return */
  77. }
  78. render() {
  79. // define styles
  80. const separatorStyle = {
  81. marginLeft: '0.2em',
  82. marginRight: '0.2em',
  83. };
  84. const buttonStyle = {
  85. marginLeft: '0.5em',
  86. padding: '0 2px',
  87. };
  88. const { isPageInTrash, isPageForbidden } = this.props;
  89. const pageLength = this.state.pages.length;
  90. const rootElement = isPageInTrash
  91. ? (
  92. <>
  93. <span className="path-segment">
  94. <a href="/trash"><i className="icon-trash"></i></a>
  95. </span>
  96. <span className="separator" style={separatorStyle}><a href="/">/</a></span>
  97. </>
  98. )
  99. : (
  100. <>
  101. <span className="path-segment">
  102. <a href="/">
  103. <i className="icon-home"></i>
  104. <span className="separator" style={separatorStyle}>/</span>
  105. </a>
  106. </span>
  107. </>
  108. );
  109. const afterElements = [];
  110. this.state.pages.forEach((page, index) => {
  111. const isLastElement = (index === pageLength - 1);
  112. // add elements for page
  113. afterElements.push(
  114. <span key={page.pagePath} className="path-segment">
  115. <a href={page.pagePath}>{page.pageName}</a>
  116. </span>,
  117. );
  118. // add elements for '/'
  119. afterElements.push(
  120. <span key={`${page.pagePath}/`} className="separator" style={separatorStyle}>
  121. {this.generateLinkElementToListPage(page.pagePath, this.state.isLinkToListPage, isLastElement)}
  122. </span>,
  123. );
  124. });
  125. return (
  126. <span className="d-flex align-items-center flex-wrap">
  127. {rootElement}
  128. {afterElements}
  129. <CopyDropdown t={this.props.t} pagePath={this.props.pagePath} pageId={this.props.pageId} buttonStyle={buttonStyle}></CopyDropdown>
  130. { !isPageInTrash && !isPageForbidden && (
  131. <a href="#edit" className="d-block text-muted btn btn-secondary bg-transparent btn-edit border-0" style={buttonStyle}>
  132. <i className="icon-note" />
  133. </a>
  134. ) }
  135. </span>
  136. );
  137. }
  138. }
  139. RevisionPath.propTypes = {
  140. t: PropTypes.func.isRequired, // i18next
  141. behaviorType: PropTypes.string.isRequired,
  142. pagePath: PropTypes.string.isRequired,
  143. pageId: PropTypes.string,
  144. isPageNotFound: PropTypes.bool,
  145. isPageForbidden: PropTypes.bool,
  146. isPageInTrash: PropTypes.bool,
  147. };
  148. RevisionPath.defaultProps = {
  149. isPageNotFound: false,
  150. isPageForbidden: false,
  151. isPageInTrash: false,
  152. };
  153. export default withTranslation()(RevisionPath);