UNPKG

@atlaskit/editor-plugin-copy-button

Version:

editor-plugin-copy-button for @atlaskit/editor-core

188 lines (183 loc) 9 kB
import { ACTION, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { copyHTMLToClipboard, copyHTMLToClipboardPolyfill, getAnalyticsPayload } from '@atlaskit/editor-common/clipboard'; import { copyDomNode, getSelectedNodeOrNodeParentByNodeType, toDOM } from '@atlaskit/editor-common/copy-button'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { copyButtonPluginKey } from './plugin-key'; export function createToolbarCopyCommandForMark(markType, editorAnalyticsApi) { function command(state, dispatch) { var textNode = state.tr.selection.$head.parent.maybeChild(state.tr.selection.$head.index()); if (!textNode) { return false; } if (dispatch) { // As calling copyHTMLToClipboard causes side effects -- we only run this when // dispatch is provided -- as otherwise the consumer is only testing to see if // the action is availble. var domNode = toDOM(textNode, state.schema); if (domNode) { var div = document.createElement('div'); var p = document.createElement('p'); div.appendChild(p); p.appendChild(domNode); // The "1 1" refers to the start and end depth of the slice // since we're copying the text inside a paragraph, it will always be 1 1 // https://github.com/ProseMirror/prosemirror-view/blob/master/src/clipboard.ts#L32 // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting div.firstChild.setAttribute('data-pm-slice', '1 1 []'); // If we're copying a hyperlink, we'd copy the url as the fallback plain text // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting var linkUrl = domNode.getAttribute('href'); copyHTMLToClipboard(div, markType.name === 'link' && linkUrl ? linkUrl : undefined); } var copyToClipboardTr = state.tr; copyToClipboardTr.setMeta(copyButtonPluginKey, { copied: true }); var analyticsPayload = getAnalyticsPayload(state, ACTION.COPIED); if (analyticsPayload && editorAnalyticsApi) { var _editorAnalyticsApi$a; analyticsPayload.attributes.inputMethod = INPUT_METHOD.FLOATING_TB; analyticsPayload.attributes.markType = markType.name; editorAnalyticsApi === null || editorAnalyticsApi === void 0 || (_editorAnalyticsApi$a = editorAnalyticsApi.attachAnalyticsEvent) === null || _editorAnalyticsApi$a === void 0 || _editorAnalyticsApi$a.call(editorAnalyticsApi, analyticsPayload)(copyToClipboardTr); } dispatch(copyToClipboardTr); } return true; } return command; } export function getProvideMarkVisualFeedbackForCopyButtonCommand(markType) { function provideMarkVisualFeedbackForCopyButtonCommand(state, dispatch) { var tr = state.tr; tr.setMeta(copyButtonPluginKey, { showSelection: true, markType: markType }); if (dispatch) { dispatch(tr); } return true; } return provideMarkVisualFeedbackForCopyButtonCommand; } export function removeMarkVisualFeedbackForCopyButtonCommand(state, dispatch) { var tr = state.tr; tr.setMeta(copyButtonPluginKey, { removeSelection: true }); var copyButtonState = copyButtonPluginKey.getState(state); if (copyButtonState !== null && copyButtonState !== void 0 && copyButtonState.copied) { tr.setMeta(copyButtonPluginKey, { copied: false }); } if (dispatch) { dispatch(tr); } return true; } export var createToolbarCopyCommandForNode = function createToolbarCopyCommandForNode(nodeType, editorAnalyticsApi, api, onClickMessage) { return function (state, dispatch) { var tr = state.tr, schema = state.schema; // This command should only be triggered by the Copy button in the floating toolbar // which is only visible when selection is inside the target node var contentNodeWithPos = getSelectedNodeOrNodeParentByNodeType({ nodeType: nodeType, selection: tr.selection }); if (!contentNodeWithPos) { return false; } var copyToClipboardTr = tr; copyToClipboardTr.setMeta(copyButtonPluginKey, { copied: true }); var analyticsPayload = getAnalyticsPayload(state, ACTION.COPIED); if (analyticsPayload && editorAnalyticsApi) { var _editorAnalyticsApi$a2; analyticsPayload.attributes.inputMethod = INPUT_METHOD.FLOATING_TB; analyticsPayload.attributes.nodeType = contentNodeWithPos.node.type.name; editorAnalyticsApi === null || editorAnalyticsApi === void 0 || (_editorAnalyticsApi$a2 = editorAnalyticsApi.attachAnalyticsEvent) === null || _editorAnalyticsApi$a2 === void 0 || _editorAnalyticsApi$a2.call(editorAnalyticsApi, analyticsPayload)(copyToClipboardTr); } if (dispatch) { // As calling copyHTMLToClipboard causes side effects -- we only run this when // dispatch is provided -- as otherwise the consumer is only testing to see if // the action is availble. var domNode = toDOM(contentNodeWithPos.node, schema); if (editorExperiment('platform_editor_block_menu', true)) { copyDomNode(domNode, contentNodeWithPos.node.type, tr.selection); } else { if (domNode) { var div = document.createElement('div'); var browser = getBrowserInfo(); div.appendChild(domNode); // if copying inline content if (contentNodeWithPos.node.type.inlineContent) { // The "1 1" refers to the start and end depth of the slice // since we're copying the text inside a paragraph, it will always be 1 1 // https://github.com/ProseMirror/prosemirror-view/blob/master/src/clipboard.ts#L32 // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting div.firstChild.setAttribute('data-pm-slice', '1 1 []'); } else { // The "0 0" refers to the start and end depth of the slice // since we're copying the block node only, it will always be 0 0 // https://github.com/ProseMirror/prosemirror-view/blob/master/src/clipboard.ts#L32 // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting div.firstChild.setAttribute('data-pm-slice', '0 0 []'); } // ED-17083 safari seems have bugs for extension copy because exntension do not have a child text(innerText) and it will not recognized as html in clipboard, this could be merge into one if this extension fixed children issue or safari fix the copy bug // MEX-2528 safari has a bug related to the mediaSingle node with border or link. The image tag within the clipboard is not recognized as HTML when using the ClipboardItem API. To address this, we have to switch to ClipboardPolyfill if (browser.safari && state.selection instanceof NodeSelection && (state.selection.node.type === state.schema.nodes.extension || state.selection.node.type === state.schema.nodes.mediaSingle)) { copyHTMLToClipboardPolyfill(div); } else { copyHTMLToClipboard(div); } } } copyToClipboardTr.setMeta('scrollIntoView', false); dispatch(copyToClipboardTr); } /** * A11y-10275 This prop api drilled from the plugin all the way down to the usage * because it's the safest way to ensure accessibilityUtils is available and not * undefined. * * see thread https://atlassian.slack.com/archives/C02CDUS3SUS/p1744256326459569 */ if (onClickMessage) { var _api$accessibilityUti; api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(onClickMessage, { priority: 'important' }); } return true; }; }; export var resetCopiedState = function resetCopiedState(nodeType, hoverDecoration, onMouseLeave) { return function (state, dispatch) { var customTr = state.tr; // Avoid multipe dispatch // https://product-fabric.atlassian.net/wiki/spaces/E/pages/2241659456/All+about+dispatch+and+why+there+shouldn+t+be+multiple#How-do-I-avoid-them%3F var customDispatch = function customDispatch(tr) { customTr = tr; }; onMouseLeave ? onMouseLeave(state, customDispatch) : hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false)(state, customDispatch); var copyButtonState = copyButtonPluginKey.getState(state); if (copyButtonState !== null && copyButtonState !== void 0 && copyButtonState.copied) { customTr.setMeta(copyButtonPluginKey, { copied: false }); } if (dispatch) { dispatch(customTr); } return true; }; };