CopyDropdown.jsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import {
  5. UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem,
  6. Tooltip,
  7. } from 'reactstrap';
  8. import { CopyToClipboard } from 'react-copy-to-clipboard';
  9. class CopyDropdown extends React.Component {
  10. constructor(props) {
  11. super(props);
  12. this.state = {
  13. tooltipOpen: false,
  14. isParamsAppended: true,
  15. pagePathWithParams: '',
  16. pagePathUrl: '',
  17. permalink: '',
  18. markdownLink: '',
  19. };
  20. this.id = (Math.random() * 1000).toString();
  21. this.showToolTip = this.showToolTip.bind(this);
  22. this.generateItemContents = this.generateItemContents.bind(this);
  23. this.generatePagePathWithParams = this.generatePagePathWithParams.bind(this);
  24. this.generatePagePathUrl = this.generatePagePathUrl.bind(this);
  25. this.generatePermalink = this.generatePermalink.bind(this);
  26. this.generateMarkdownLink = this.generateMarkdownLink.bind(this);
  27. }
  28. showToolTip() {
  29. this.setState({ tooltipOpen: true });
  30. setTimeout(() => {
  31. this.setState({ tooltipOpen: false });
  32. }, 1000);
  33. }
  34. get uriParams() {
  35. const { isParamsAppended } = this.state;
  36. if (!isParamsAppended) {
  37. return '';
  38. }
  39. const {
  40. search, hash,
  41. } = window.location;
  42. return `${search}${hash}`;
  43. }
  44. encodeSpaces(str) {
  45. if (str == null) {
  46. return null;
  47. }
  48. // Encode SPACE and IDEOGRAPHIC SPACE
  49. return str.replace(/ /g, '%20').replace(/\u3000/g, '%E3%80%80');
  50. }
  51. generateItemContents() {
  52. const pagePathWithParams = this.generatePagePathWithParams();
  53. const pagePathUrl = this.generatePagePathUrl();
  54. const permalink = this.generatePermalink();
  55. const markdownLink = this.generateMarkdownLink();
  56. this.setState({
  57. pagePathWithParams, pagePathUrl, permalink, markdownLink,
  58. });
  59. }
  60. generatePagePathWithParams() {
  61. const { pagePath } = this.props;
  62. return decodeURI(`${pagePath}${this.uriParams}`);
  63. }
  64. generatePagePathUrl() {
  65. const { origin } = window.location;
  66. return `${origin}${this.encodeSpaces(this.generatePagePathWithParams())}`;
  67. }
  68. generatePermalink() {
  69. const { origin } = window.location;
  70. const { pageId, isShareLinkMode } = this.props;
  71. if (pageId == null) {
  72. return null;
  73. }
  74. if (isShareLinkMode) {
  75. return decodeURI(`${origin}/share/${pageId}`);
  76. }
  77. return this.encodeSpaces(decodeURI(`${origin}/${pageId}${this.uriParams}`));
  78. }
  79. generateMarkdownLink() {
  80. const { pagePath } = this.props;
  81. const label = decodeURI(`${pagePath}${this.uriParams}`);
  82. const permalink = this.generatePermalink();
  83. return `[${label}](${permalink})`;
  84. }
  85. DropdownItemContents = ({ title, contents }) => (
  86. <>
  87. <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
  88. <div className="card well mb-1 p-2">{contents}</div>
  89. </>
  90. );
  91. render() {
  92. const {
  93. t, pageId, isShareLinkMode,
  94. } = this.props;
  95. const {
  96. isParamsAppended, pagePathWithParams, pagePathUrl, permalink, markdownLink,
  97. } = this.state;
  98. const copyTarget = isShareLinkMode ? `copyShareLink${pageId}` : 'copyPagePathDropdown';
  99. const dropdownToggleStyle = isShareLinkMode ? 'btn btn-secondary' : 'd-block text-muted bg-transparent btn-copy border-0';
  100. const { id, DropdownItemContents } = this;
  101. const customSwitchForParamsId = `customSwitchForParams_${id}`;
  102. return (
  103. <>
  104. <UncontrolledDropdown id={copyTarget} className="grw-copy-dropdown">
  105. <DropdownToggle
  106. caret
  107. className={dropdownToggleStyle}
  108. style={this.props.buttonStyle}
  109. onClick={this.generateItemContents}
  110. >
  111. { isShareLinkMode ? (
  112. <>Copy Link</>
  113. ) : (<i className="ti-clipboard"></i>)}
  114. </DropdownToggle>
  115. <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
  116. <div className="d-flex align-items-center justify-content-between">
  117. <DropdownItem header className="px-3">
  118. { t('copy_to_clipboard.Copy to clipboard') }
  119. </DropdownItem>
  120. <div className="px-3 custom-control custom-switch custom-switch-sm">
  121. <input
  122. type="checkbox"
  123. id={customSwitchForParamsId}
  124. className="custom-control-input"
  125. checked={isParamsAppended}
  126. onChange={e => this.setState({ isParamsAppended: !isParamsAppended })}
  127. />
  128. <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
  129. </div>
  130. </div>
  131. <DropdownItem divider className="my-0"></DropdownItem>
  132. {/* Page path */}
  133. <CopyToClipboard text={pagePathWithParams} onCopy={this.showToolTip}>
  134. <DropdownItem className="px-3">
  135. <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
  136. </DropdownItem>
  137. </CopyToClipboard>
  138. <DropdownItem divider className="my-0"></DropdownItem>
  139. {/* Page path URL */}
  140. <CopyToClipboard text={pagePathUrl} onCopy={this.showToolTip}>
  141. <DropdownItem className="px-3">
  142. <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
  143. </DropdownItem>
  144. </CopyToClipboard>
  145. <DropdownItem divider className="my-0"></DropdownItem>
  146. {/* Permanent Link */}
  147. { pageId && (
  148. <CopyToClipboard text={permalink} onCopy={this.showToolTip}>
  149. <DropdownItem className="px-3">
  150. <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
  151. </DropdownItem>
  152. </CopyToClipboard>
  153. )}
  154. <DropdownItem divider className="my-0"></DropdownItem>
  155. {/* Page path + Permanent Link */}
  156. { pageId && (
  157. <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={this.showToolTip}>
  158. <DropdownItem className="px-3">
  159. <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
  160. </DropdownItem>
  161. </CopyToClipboard>
  162. )}
  163. <DropdownItem divider className="my-0"></DropdownItem>
  164. {/* Markdown Link */}
  165. { pageId && (
  166. <CopyToClipboard text={markdownLink} onCopy={this.showToolTip}>
  167. <DropdownItem className="px-3 text-wrap">
  168. <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
  169. </DropdownItem>
  170. </CopyToClipboard>
  171. )}
  172. </DropdownMenu>
  173. </UncontrolledDropdown>
  174. <Tooltip placement="bottom" isOpen={this.state.tooltipOpen} target={copyTarget} fade={false}>
  175. copied!
  176. </Tooltip>
  177. </>
  178. );
  179. }
  180. }
  181. CopyDropdown.propTypes = {
  182. t: PropTypes.func.isRequired, // i18next
  183. pagePath: PropTypes.string.isRequired,
  184. pageId: PropTypes.string,
  185. buttonStyle: PropTypes.object,
  186. isShareLinkMode: PropTypes.bool,
  187. };
  188. export default withTranslation()(CopyDropdown);