CopyDropdown.jsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import React, {
  2. useState, useMemo, useCallback,
  3. } from 'react';
  4. import PropTypes from 'prop-types';
  5. import { withTranslation } from 'react-i18next';
  6. import {
  7. Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
  8. Tooltip,
  9. } from 'reactstrap';
  10. import { CopyToClipboard } from 'react-copy-to-clipboard';
  11. import { pagePathUtils } from '@growi/core';
  12. const { encodeSpaces } = pagePathUtils;
  13. /* eslint-disable react/prop-types */
  14. const DropdownItemContents = ({ title, contents }) => (
  15. <>
  16. <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
  17. <div className="card well mb-1 p-2">{contents}</div>
  18. </>
  19. );
  20. /* eslint-enable react/prop-types */
  21. const CopyDropdown = (props) => {
  22. const [dropdownOpen, setDropdownOpen] = useState(false);
  23. const [tooltipOpen, setTooltipOpen] = useState(false);
  24. const [isParamsAppended, setParamsAppended] = useState(!props.isShareLinkMode);
  25. /*
  26. * functions to construct labels and URLs
  27. */
  28. const getUriParams = useCallback(() => {
  29. if (!isParamsAppended || !dropdownOpen) {
  30. return '';
  31. }
  32. const {
  33. search, hash,
  34. } = window.location;
  35. return `${search}${hash}`;
  36. }, [isParamsAppended, dropdownOpen]);
  37. const pagePathWithParams = useMemo(() => {
  38. const { pagePath } = props;
  39. return decodeURI(`${pagePath}${getUriParams()}`);
  40. }, [props, getUriParams]);
  41. const pagePathUrl = useMemo(() => {
  42. const { origin } = window.location;
  43. return `${origin}${encodeSpaces(pagePathWithParams)}`;
  44. }, [pagePathWithParams]);
  45. const permalink = useMemo(() => {
  46. const { origin } = window.location;
  47. const { pageId, isShareLinkMode } = props;
  48. if (pageId == null) {
  49. return null;
  50. }
  51. if (isShareLinkMode) {
  52. return decodeURI(`${origin}/share/${pageId}`);
  53. }
  54. return encodeSpaces(decodeURI(`${origin}/${pageId}${getUriParams()}`));
  55. }, [props, getUriParams]);
  56. const markdownLink = useMemo(() => {
  57. const { pagePath } = props;
  58. const label = decodeURI(`${pagePath}${getUriParams()}`);
  59. // const permalink = generatePermalink();
  60. return `[${label}](${permalink})`;
  61. }, [props, getUriParams, permalink]);
  62. /**
  63. * control
  64. */
  65. const toggleDropdown = useCallback(() => {
  66. setDropdownOpen(!dropdownOpen);
  67. }, [dropdownOpen]);
  68. const toggleAppendParams = useCallback(() => {
  69. setParamsAppended(!isParamsAppended);
  70. }, [isParamsAppended]);
  71. const showToolTip = useCallback(() => {
  72. setTooltipOpen(true);
  73. setTimeout(() => {
  74. setTooltipOpen(false);
  75. }, 1000);
  76. }, []);
  77. /*
  78. * render
  79. */
  80. const {
  81. t, dropdownToggleId, pageId, dropdownToggleClassName, children, isShareLinkMode,
  82. } = props;
  83. const customSwitchForParamsId = `customSwitchForParams_${dropdownToggleId}`;
  84. return (
  85. <>
  86. <Dropdown className="grw-copy-dropdown" isOpen={dropdownOpen} toggle={toggleDropdown}>
  87. <DropdownToggle
  88. caret
  89. className={dropdownToggleClassName}
  90. >
  91. <span id={dropdownToggleId}>{children}</span>
  92. </DropdownToggle>
  93. <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
  94. <div className="d-flex align-items-center justify-content-between">
  95. <DropdownItem header className="px-3">
  96. { t('copy_to_clipboard.Copy to clipboard') }
  97. </DropdownItem>
  98. { !isShareLinkMode && (
  99. <div className="px-3 custom-control custom-switch custom-switch-sm">
  100. <input
  101. type="checkbox"
  102. id={customSwitchForParamsId}
  103. className="custom-control-input"
  104. checked={isParamsAppended}
  105. onChange={toggleAppendParams}
  106. />
  107. <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
  108. </div>
  109. ) }
  110. </div>
  111. <DropdownItem divider className="my-0"></DropdownItem>
  112. {/* Page path */}
  113. <CopyToClipboard text={pagePathWithParams} onCopy={showToolTip}>
  114. <DropdownItem className="px-3">
  115. <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
  116. </DropdownItem>
  117. </CopyToClipboard>
  118. <DropdownItem divider className="my-0"></DropdownItem>
  119. {/* Page path URL */}
  120. <CopyToClipboard text={pagePathUrl} onCopy={showToolTip}>
  121. <DropdownItem className="px-3">
  122. <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
  123. </DropdownItem>
  124. </CopyToClipboard>
  125. <DropdownItem divider className="my-0"></DropdownItem>
  126. {/* Permanent Link */}
  127. { pageId && (
  128. <CopyToClipboard text={permalink} onCopy={showToolTip}>
  129. <DropdownItem className="px-3">
  130. <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
  131. </DropdownItem>
  132. </CopyToClipboard>
  133. )}
  134. <DropdownItem divider className="my-0"></DropdownItem>
  135. {/* Page path + Permanent Link */}
  136. { pageId && (
  137. <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={showToolTip}>
  138. <DropdownItem className="px-3">
  139. <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
  140. </DropdownItem>
  141. </CopyToClipboard>
  142. )}
  143. <DropdownItem divider className="my-0"></DropdownItem>
  144. {/* Markdown Link */}
  145. { pageId && (
  146. <CopyToClipboard text={markdownLink} onCopy={showToolTip}>
  147. <DropdownItem className="px-3 text-wrap">
  148. <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
  149. </DropdownItem>
  150. </CopyToClipboard>
  151. )}
  152. </DropdownMenu>
  153. </Dropdown>
  154. <Tooltip placement="bottom" isOpen={tooltipOpen} target={dropdownToggleId} fade={false}>
  155. copied!
  156. </Tooltip>
  157. </>
  158. );
  159. };
  160. CopyDropdown.propTypes = {
  161. t: PropTypes.func.isRequired, // i18next
  162. children: PropTypes.node.isRequired,
  163. dropdownToggleId: PropTypes.string.isRequired,
  164. pagePath: PropTypes.string.isRequired,
  165. pageId: PropTypes.string,
  166. dropdownToggleClassName: PropTypes.string,
  167. isShareLinkMode: PropTypes.bool,
  168. };
  169. export default withTranslation()(CopyDropdown);