fix copy message with styles
This commit is contained in:
+28
-25
@@ -1,43 +1,46 @@
|
||||
// based on https://github.com/sindresorhus/copy-text-to-clipboard, but improved to copy text styles too
|
||||
export function copy(input: string): boolean {
|
||||
const element = document.createElement('textarea') as HTMLTextAreaElement;
|
||||
export function copy(content: string): boolean {
|
||||
// We use `div` instead of `input` or `textarea` because we want to copy styles
|
||||
const container = document.createElement('div');
|
||||
const previouslyFocusedElement = document.activeElement as HTMLElement;
|
||||
|
||||
element.value = input;
|
||||
container.innerHTML = content;
|
||||
|
||||
// Prevent keyboard from showing on mobile
|
||||
element.setAttribute('readonly', '');
|
||||
|
||||
Object.assign(element.style, {
|
||||
Object.assign(container.style, {
|
||||
contain: 'strict',
|
||||
position: 'absolute',
|
||||
left: '-9999px',
|
||||
fontSize: '12pt', // Prevent zooming on iOS
|
||||
});
|
||||
|
||||
const selection = document.getSelection();
|
||||
let originalRange: boolean | Range = false;
|
||||
document.body.appendChild(container);
|
||||
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
originalRange = selection.getRangeAt(0);
|
||||
let selection = window.getSelection();
|
||||
// save original selection
|
||||
const originalRange = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
|
||||
const range = document.createRange();
|
||||
|
||||
range.selectNodeContents(container);
|
||||
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
document.body.append(element);
|
||||
element.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
// Explicit selection workaround for iOS
|
||||
element.selectionStart = 0;
|
||||
element.selectionEnd = input.length;
|
||||
|
||||
let isSuccess = false;
|
||||
let success = false;
|
||||
try {
|
||||
isSuccess = document.execCommand('copy');
|
||||
} catch (_) {}
|
||||
success = document.execCommand('copy');
|
||||
} catch (err) {}
|
||||
|
||||
element.remove();
|
||||
|
||||
if (selection && originalRange) {
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
|
||||
document.body.removeChild(container);
|
||||
|
||||
// Put the selection back in case had it before
|
||||
if (originalRange && selection) {
|
||||
selection.addRange(originalRange);
|
||||
}
|
||||
|
||||
@@ -46,5 +49,5 @@ export function copy(input: string): boolean {
|
||||
previouslyFocusedElement.focus();
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Button } from 'components/button';
|
||||
import { Countdown } from 'components/countdown';
|
||||
import { getPreview, uploadImage } from 'common/api';
|
||||
import { postMessage } from 'utils/postMessage';
|
||||
import { FormattedMessage, useIntl, IntlShape, defineMessages } from 'react-intl';
|
||||
import { FormattedMessage, IntlShape, defineMessages } from 'react-intl';
|
||||
import { getVoteMessage, VoteMessagesTypes } from './getVoteMessage';
|
||||
import { getBlockingDurations } from './getBlockingDurations';
|
||||
import { boundActions } from './connected-comment';
|
||||
@@ -85,6 +85,10 @@ const messages = defineMessages({
|
||||
id: 'comment.expired-time',
|
||||
defaultMessage: 'Editing time has expired.',
|
||||
},
|
||||
commentTime: {
|
||||
id: 'comment.time',
|
||||
defaultMessage: '{day} at {time}',
|
||||
},
|
||||
});
|
||||
|
||||
export type CommentProps = {
|
||||
@@ -372,8 +376,8 @@ export class Comment extends Component<CommentProps, State> {
|
||||
|
||||
copyComment = () => {
|
||||
const username = this.props.data.user.name;
|
||||
const time = this.props.data.time;
|
||||
const text = this.textNode.current!.textContent || '';
|
||||
const time = getLocalDatetime(this.props.intl, new Date(this.props.data.time));
|
||||
const text = this.textNode.current?.textContent || '';
|
||||
|
||||
copy(`<b>${username}</b> ${time}<br>${text.replace(/\n+/g, '<br>')}`);
|
||||
|
||||
@@ -692,7 +696,7 @@ export class Comment extends Component<CommentProps, State> {
|
||||
)}
|
||||
|
||||
<a href={`${o.locator.url}#${COMMENT_NODE_CLASSNAME_PREFIX}${o.id}`} className="comment__time">
|
||||
<FormatTime time={o.time} />
|
||||
{getLocalDatetime(this.props.intl, o.time)}
|
||||
</a>
|
||||
|
||||
{!!props.level && props.level > 0 && props.view === 'main' && (
|
||||
@@ -881,17 +885,9 @@ function getTextSnippet(html: string) {
|
||||
return snippet.length === LENGTH && result.length !== LENGTH ? `${snippet}...` : snippet;
|
||||
}
|
||||
|
||||
function FormatTime({ time }: { time: Date }) {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="comment.time"
|
||||
defaultMessage="{day} at {time}"
|
||||
values={{
|
||||
day: intl.formatDate(time),
|
||||
time: intl.formatTime(time),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
function getLocalDatetime(intl: IntlShape, date: Date) {
|
||||
return intl.formatMessage(messages.commentTime, {
|
||||
day: intl.formatDate(date),
|
||||
time: intl.formatTime(date),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user