CopyDropdown.jsx 7.0 KB

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