diff --git a/web/app/common/copy.js b/web/app/common/copy.js new file mode 100644 index 00000000..eae7206d --- /dev/null +++ b/web/app/common/copy.js @@ -0,0 +1,41 @@ +// https://github.com/sindresorhus/copy-text-to-clipboard +module.exports = input => { + const el = document.createElement('textarea'); + + el.value = input; + + // Prevent keyboard from showing on mobile + el.setAttribute('readonly', ''); + + el.style.contain = 'strict'; + el.style.position = 'absolute'; + el.style.left = '-9999px'; + el.style.fontSize = '12pt'; // Prevent zooming on iOS + + const selection = document.getSelection(); + let originalRange = false; + if (selection.rangeCount > 0) { + originalRange = selection.getRangeAt(0); + } + + document.body.appendChild(el); + el.select(); + + // Explicit selection workaround for iOS + el.selectionStart = 0; + el.selectionEnd = input.length; + + let success = false; + try { + success = document.execCommand('copy'); + } catch (err) {} + + document.body.removeChild(el); + + if (originalRange) { + selection.removeAllRanges(); + selection.addRange(originalRange); + } + + return success; +}; diff --git a/web/app/components/comment/__control/_view/_inactive/comment__control_view_inactive.scss b/web/app/components/comment/__control/_view/_inactive/comment__control_view_inactive.scss new file mode 100644 index 00000000..92654ba2 --- /dev/null +++ b/web/app/components/comment/__control/_view/_inactive/comment__control_view_inactive.scss @@ -0,0 +1,7 @@ +.comment__control_view_inactive { + &, + &:hover, + &:focus { + color: #999; + } +} diff --git a/web/app/components/comment/comment.jsx b/web/app/components/comment/comment.jsx index b47e8345..fe60b18e 100644 --- a/web/app/components/comment/comment.jsx +++ b/web/app/components/comment/comment.jsx @@ -6,6 +6,7 @@ import { getHandleClickProps } from 'common/accessibility'; import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX, BLOCKING_DURATIONS } from 'common/constants'; import { url } from 'common/settings'; import store from 'common/store'; +import copy from 'common/copy'; import Input from 'components/input'; @@ -16,6 +17,7 @@ export default class Comment extends Component { super(props); this.state = { + isCopied: false, isReplying: false, isEditing: false, isUserVerified: false, @@ -26,6 +28,7 @@ export default class Comment extends Component { this.updateState(props); + this.copyComment = this.copyComment.bind(this); this.decreaseScore = this.decreaseScore.bind(this); this.increaseScore = this.increaseScore.bind(this); this.toggleEditing = this.toggleEditing.bind(this); @@ -322,6 +325,16 @@ export default class Comment extends Component { } } + copyComment({ username, time }) { + const text = this.textNode.textContent; + + copy(username + '\n' + time + '\n' + text); + + this.setState({ isCopied: true }, () => { + setTimeout(() => this.setState({ isCopied: false }), 3000); + }); + } + render( props, { @@ -332,6 +345,7 @@ export default class Comment extends Component { scoreIncreased, scoreDecreased, deleted, + isCopied, isReplying, isEditing, isUserVerified, @@ -514,7 +528,11 @@ export default class Comment extends Component { -
+
(this.textNode = r)} + dangerouslySetInnerHTML={{ __html: o.text }} + />
{!deleted && @@ -547,12 +565,23 @@ export default class Comment extends Component { Delete ), - {editTimeLeft && `${editTimeLeft}`}, + {editTimeLeft && `${editTimeLeft}`}, ]} {!deleted && isAdmin && ( + {!isCopied && ( + this.copyComment({ username: o.username, time: o.time }))} + className="comment__control" + > + Copy + + )} + + {isCopied && Copied!} + this.togglePin(pinned))} className="comment__control"> {pinned ? 'Unpin' : 'Pin'} diff --git a/web/app/components/comment/index.js b/web/app/components/comment/index.js index a739f194..5ac52caa 100644 --- a/web/app/components/comment/index.js +++ b/web/app/components/comment/index.js @@ -15,6 +15,7 @@ require('./__body/comment__body.scss'); require('./__control/comment__control.scss'); require('./__control/_select/comment__control_select.scss'); require('./__control/_select-label/comment__control_select-label.scss'); +require('./__control/_view/_inactive/comment__control_view_inactive.scss'); require('./__controls/comment__controls.scss'); require('./__info/comment__info.scss'); require('./__input/comment__input.scss');