RevisionPath.jsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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 rootStyle = {
  87. marginRight: '0.2em',
  88. };
  89. const separatorStyle = {
  90. marginLeft: '0.2em',
  91. marginRight: '0.2em',
  92. };
  93. const buttonStyle = {
  94. marginLeft: '0.5em',
  95. padding: '0 2px',
  96. };
  97. const { isInTrash } = this.state;
  98. const pageLength = this.state.pages.length;
  99. const afterElements = [];
  100. this.state.pages.forEach((page, index) => {
  101. const isLastElement = (index === pageLength - 1);
  102. // add elements for page
  103. afterElements.push(
  104. <span key={page.pagePath} className="path-segment">
  105. <a href={page.pagePath}>{page.pageName}</a>
  106. </span>,
  107. );
  108. // add elements for '/'
  109. afterElements.push(
  110. <span key={`${page.pagePath}/`} className="separator" style={separatorStyle}>
  111. {this.generateLinkElementToListPage(page.pagePath, this.state.isLinkToListPage, isLastElement)}
  112. </span>,
  113. );
  114. });
  115. return (
  116. <span className="d-flex align-items-center">
  117. { isInTrash && (
  118. <span className="path-segment">
  119. <a href="/trash"><i className="icon-trash"></i></a>
  120. </span>
  121. ) }
  122. <span className="separator" style={isInTrash ? separatorStyle : rootStyle}>
  123. <a href="/">/</a>
  124. </span>
  125. {afterElements}
  126. <CopyDropdown t={this.props.t} pagePath={this.props.pagePath} pageId={this.props.pageId} buttonStyle={buttonStyle}></CopyDropdown>
  127. <a href="#edit" className="btn btn-default btn-edit" style={buttonStyle}>
  128. <i className="icon-note" />
  129. </a>
  130. </span>
  131. );
  132. }
  133. }
  134. RevisionPath.propTypes = {
  135. t: PropTypes.func.isRequired, // i18next
  136. behaviorType: PropTypes.string.isRequired,
  137. pagePath: PropTypes.string.isRequired,
  138. pageId: PropTypes.string,
  139. };
  140. export default withTranslation()(RevisionPath);