RevisionPath.jsx 4.5 KB

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