@atlaskit/editor-plugin-paste 18.0.4 → 18.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/cjs/pm-plugins/main.js +2 -2
- package/dist/cjs/pm-plugins/util/handlers.js +224 -6
- package/dist/es2019/pm-plugins/main.js +2 -2
- package/dist/es2019/pm-plugins/util/handlers.js +229 -3
- package/dist/esm/pm-plugins/main.js +2 -2
- package/dist/esm/pm-plugins/util/handlers.js +225 -7
- package/dist/types/pm-plugins/util/handlers.d.ts +1 -1
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste
|
|
2
2
|
|
|
3
|
+
## 18.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`4a3a84237c82a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4a3a84237c82a) -
|
|
8
|
+
Behind the `platform_editor_paste_autoconvert_robustness` experiment: paste auto-conversion no
|
|
9
|
+
longer waits for the smart link provider before inserting pasted content, and the macro
|
|
10
|
+
replacement range is derived from the applied transaction steps instead of the slice size so the
|
|
11
|
+
pasted link can no longer be left behind alongside the macro. If the pasted link can no longer be
|
|
12
|
+
located when the request settles (for example it was unlinked in the meantime), nothing is
|
|
13
|
+
replaced — content is never matched by proximity to a stale position, which could otherwise
|
|
14
|
+
convert a different, unrelated link to the same URL. Behaviour is unchanged when the experiment is
|
|
15
|
+
off.
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
|
|
3
18
|
## 18.0.4
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -417,7 +417,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
// run macro autoconvert prior to other conversions
|
|
420
|
-
if ((0, _handlers.handleMacroAutoConvert)(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
420
|
+
if ((0, _handlers.handleMacroAutoConvert)(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
421
421
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
422
422
|
(0, _analytics2.sendPasteAnalyticsEvent)(editorAnalyticsAPI)(view, event, markdownSlice, {
|
|
423
423
|
type: _analytics.PasteTypes.markdown
|
|
@@ -515,7 +515,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
// run macro autoconvert prior to other conversions
|
|
518
|
-
if ((0, _handlers.handleMacroAutoConvert)(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 || (_pluginInjectionApi$c2 = _pluginInjectionApi$c2.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
518
|
+
if ((0, _handlers.handleMacroAutoConvert)(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 || (_pluginInjectionApi$c2 = _pluginInjectionApi$c2.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
519
519
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
520
520
|
(0, _analytics2.sendPasteAnalyticsEvent)(editorAnalyticsAPI)(view, event, slice, {
|
|
521
521
|
type: _analytics.PasteTypes.richText
|
|
@@ -642,7 +642,7 @@ function _getSmartLinkAdf() {
|
|
|
642
642
|
}));
|
|
643
643
|
return _getSmartLinkAdf.apply(this, arguments);
|
|
644
644
|
}
|
|
645
|
-
function
|
|
645
|
+
function insertAutoMacroOld(slice, macro, view, from, to) {
|
|
646
646
|
if (view) {
|
|
647
647
|
// insert the text or linkified/md-converted clipboard data
|
|
648
648
|
var selection = view.state.tr.selection;
|
|
@@ -669,7 +669,168 @@ function insertAutoMacro(slice, macro, view, from, to) {
|
|
|
669
669
|
}
|
|
670
670
|
return false;
|
|
671
671
|
}
|
|
672
|
-
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Replacement for `insertAutoMacroOld` behind
|
|
675
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
676
|
+
*
|
|
677
|
+
* Identical in shape, except that the range the macro replaces is derived from the steps
|
|
678
|
+
* that were actually applied rather than from `slice.size`. `slice.size` is the slice's
|
|
679
|
+
* own measure and does not account for ProseMirror's fitting behaviour in
|
|
680
|
+
* `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
|
|
681
|
+
* is inserted without removing the text it should replace, leaving both the pasted text
|
|
682
|
+
* and the macro in the document.
|
|
683
|
+
*/
|
|
684
|
+
function insertAutoMacroNew(slice, macro, view, from, to) {
|
|
685
|
+
if (!view) {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
var selection = view.state.selection;
|
|
689
|
+
var hasExplicitRange = typeof from === 'number' && typeof to === 'number';
|
|
690
|
+
var replaceFrom = hasExplicitRange ? from : selection.from;
|
|
691
|
+
var replaceTo = hasExplicitRange ? to : selection.to;
|
|
692
|
+
|
|
693
|
+
// insert the text or linkified/md-converted clipboard data
|
|
694
|
+
var tr = hasExplicitRange ? view.state.tr.replaceRange(replaceFrom, replaceTo, slice) : view.state.tr.replaceSelection(slice);
|
|
695
|
+
var insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
696
|
+
var insertedTo = tr.mapping.map(replaceTo, 1);
|
|
697
|
+
view.dispatch(tr);
|
|
698
|
+
|
|
699
|
+
// Leave the document alone rather than replacing an implausible range.
|
|
700
|
+
if (insertedFrom >= insertedTo || insertedTo > view.state.doc.content.size) {
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// replace the text with the macro as a separate transaction
|
|
705
|
+
// so the autoconversion generates 2 undo steps
|
|
706
|
+
var macroTr = (0, _closeHistory.closeHistory)(view.state.tr).replaceRangeWith(insertedFrom, insertedTo, macro).scrollIntoView();
|
|
707
|
+
(0, _card.addLinkMetadata)(view.state.selection, macroTr, {
|
|
708
|
+
inputMethod: _analytics.INPUT_METHOD.CLIPBOARD,
|
|
709
|
+
cardAction: 'AUTO_CONVERT'
|
|
710
|
+
});
|
|
711
|
+
view.dispatch(macroTr);
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
function insertAutoMacro(slice, macro, view, from, to) {
|
|
715
|
+
return (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Locates the range currently occupied by a link that we pasted earlier.
|
|
720
|
+
*
|
|
721
|
+
* Positions recorded before an async gap can drift or be invalidated entirely by
|
|
722
|
+
* concurrent edits, collaborative changes and `appendTransaction` hooks, so the
|
|
723
|
+
* recorded range is verified against the document before it is used. If it no longer
|
|
724
|
+
* holds the pasted link the link is re-located by href, preferring the occurrence
|
|
725
|
+
* closest to where it was last seen.
|
|
726
|
+
*
|
|
727
|
+
* Returns `undefined` when the link cannot be found, so callers can leave the
|
|
728
|
+
* document alone rather than replacing whatever happens to sit at a stale position.
|
|
729
|
+
*/
|
|
730
|
+
/**
|
|
731
|
+
* Whether every text node spanning [from, to) carries a `link` mark pointing at `url`,
|
|
732
|
+
* with no gaps or uncovered portions.
|
|
733
|
+
*/
|
|
734
|
+
function rangeIsExactLinkMark(doc, from, to, url, linkMarkType) {
|
|
735
|
+
var matches = true;
|
|
736
|
+
var coveredTo = from;
|
|
737
|
+
doc.nodesBetween(from, to, function (node, pos) {
|
|
738
|
+
if (!matches || pos >= to) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
if (!node.isLeaf) {
|
|
742
|
+
// A container node — e.g. the paragraph the link sits in. `nodesBetween` visits
|
|
743
|
+
// these on the way down to the text node(s) we actually care about; keep
|
|
744
|
+
// descending into it rather than treating it as a mismatch.
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
if (!node.isText) {
|
|
748
|
+
// A non-text leaf (e.g. an inline card) cannot carry the link mark.
|
|
749
|
+
matches = false;
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
var linkMark = linkMarkType.isInSet(node.marks);
|
|
753
|
+
if (!linkMark || linkMark.attrs.href !== url) {
|
|
754
|
+
matches = false;
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
coveredTo = Math.min(pos + node.nodeSize, to);
|
|
758
|
+
return true;
|
|
759
|
+
});
|
|
760
|
+
return matches && coveredTo === to;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* `from`/`to` are not a one-off snapshot: they come from `pastedMacroPositions`, which the
|
|
765
|
+
* paste plugin's `mapping` hook (`plugin-factory.ts`) maps through every transaction
|
|
766
|
+
* applied while the smart link request is in flight, via `tr.mapping.map(position)`. So
|
|
767
|
+
* this covers every edit ProseMirror can express as a position mapping — typing,
|
|
768
|
+
* deleting, or moving content elsewhere in the same edit all keep `from`/`to` pointing at
|
|
769
|
+
* the same logical span.
|
|
770
|
+
*
|
|
771
|
+
* It stops covering that span only when the span itself no longer represents our pasted
|
|
772
|
+
* link — the link was unlinked, or its content was replaced outright. Deliberately no
|
|
773
|
+
* fallback search for "the nearest link with the same href" is done: with multiple links
|
|
774
|
+
* to the same URL in the document, a proximity search cannot tell which one we pasted, so
|
|
775
|
+
* it risks silently replacing an occurrence we were never asked to touch, and it requires
|
|
776
|
+
* an O(doc size) scan on every fallback to do it. Leaving the tracked link as a link is
|
|
777
|
+
* the safe outcome here, and it's what happens when this function returns `undefined`.
|
|
778
|
+
*/
|
|
779
|
+
function findPastedLinkRange(state, url, from, to) {
|
|
780
|
+
var doc = state.doc,
|
|
781
|
+
schema = state.schema;
|
|
782
|
+
var linkMarkType = schema.marks.link;
|
|
783
|
+
var docSize = doc.content.size;
|
|
784
|
+
if (!linkMarkType || typeof from !== 'number' || typeof to !== 'number' || from < 0 || from >= to || to > docSize) {
|
|
785
|
+
return undefined;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Text equality alone is not enough: if the link was unlinked (mark removed) while
|
|
789
|
+
// the smart link request was in flight, the text can still read as the URL even
|
|
790
|
+
// though it is no longer a link, and replacing it would destroy content that is no
|
|
791
|
+
// longer ours to replace.
|
|
792
|
+
if (doc.textBetween(from, to) === url && rangeIsExactLinkMark(doc, from, to, url, linkMarkType)) {
|
|
793
|
+
return {
|
|
794
|
+
from: from,
|
|
795
|
+
to: to
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return undefined;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Swaps a link that was already pasted into the document for its macro equivalent.
|
|
803
|
+
*
|
|
804
|
+
* Used when smart links could not resolve the URL, so the macro autoconversion is the
|
|
805
|
+
* fallback. Does nothing if the link can no longer be found — for example because a
|
|
806
|
+
* smart card has already replaced it — which is preferable to replacing unrelated
|
|
807
|
+
* content at a stale position.
|
|
808
|
+
*/
|
|
809
|
+
function replacePastedLinkWithMacro(view, macro, url, from, to, editorAnalyticsAPI) {
|
|
810
|
+
var range = findPastedLinkRange(view.state, url, from, to);
|
|
811
|
+
if (!range) {
|
|
812
|
+
// Metric for how often the tracked pasted link could no longer be found once the
|
|
813
|
+
// smart link request settled, so the assumption that this is rare is measurable
|
|
814
|
+
// rather than assumed.
|
|
815
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
816
|
+
action: _analytics.ACTION.ERRORED,
|
|
817
|
+
actionSubject: _analytics.ACTION_SUBJECT.SMART_LINK,
|
|
818
|
+
eventType: _analytics.EVENT_TYPE.OPERATIONAL,
|
|
819
|
+
attributes: {
|
|
820
|
+
error: 'macro-auto-convert-pasted-link-not-found'
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
var macroTr = (0, _closeHistory.closeHistory)(view.state.tr).replaceRangeWith(range.from, range.to, macro).scrollIntoView();
|
|
826
|
+
(0, _card.addLinkMetadata)(view.state.selection, macroTr, {
|
|
827
|
+
inputMethod: _analytics.INPUT_METHOD.CLIPBOARD,
|
|
828
|
+
cardAction: 'AUTO_CONVERT'
|
|
829
|
+
});
|
|
830
|
+
view.dispatch(macroTr);
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, runMacroAutoConvert, cardsOptions, extensionAutoConverter, editorAnalyticsAPI) {
|
|
673
834
|
return function (state, dispatch, view) {
|
|
674
835
|
var macro = null;
|
|
675
836
|
|
|
@@ -697,23 +858,50 @@ function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, runMacroAu
|
|
|
697
858
|
if (!view) {
|
|
698
859
|
throw new Error('View is missing');
|
|
699
860
|
}
|
|
861
|
+
var autoConvertMacro = macro;
|
|
700
862
|
|
|
701
863
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
702
864
|
var trackingId = (0, _uuid.v4)();
|
|
703
865
|
var trackingFrom = "handleMacroAutoConvert-from-".concat(trackingId);
|
|
704
866
|
var trackingTo = "handleMacroAutoConvert-to-".concat(trackingId);
|
|
867
|
+
if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_paste_autoconvert_robustness')) {
|
|
868
|
+
// Insert the pasted content straight away. The smart link provider is only
|
|
869
|
+
// consulted to decide whether the inserted link should later be swapped for
|
|
870
|
+
// a macro, so it does not need to gate the insert — otherwise every
|
|
871
|
+
// autoconvertible paste appears to do nothing until a network round trip
|
|
872
|
+
// completes.
|
|
873
|
+
var insertedRange;
|
|
874
|
+
handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
|
|
875
|
+
insertedRange = range;
|
|
876
|
+
})(state, dispatch);
|
|
877
|
+
if (!insertedRange) {
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
(0, _commands.startTrackingPastedMacroPositions)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
|
|
881
|
+
getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
|
|
882
|
+
// Smart links could not resolve the URL, so fall back to the macro.
|
|
883
|
+
// we use view.state rather than state because state becomes a stale
|
|
884
|
+
// state reference after getSmartLinkAdf's async work
|
|
885
|
+
var _getPastePluginState = (0, _pluginFactory.getPluginState)(view.state),
|
|
886
|
+
pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
|
|
887
|
+
replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
|
|
888
|
+
}).finally(function () {
|
|
889
|
+
(0, _commands.stopTrackingPastedMacroPositions)([trackingFrom, trackingTo])(view.state, dispatch);
|
|
890
|
+
});
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
705
893
|
(0, _commands.startTrackingPastedMacroPositions)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, trackingFrom, state.selection.from), trackingTo, state.selection.to))(state, dispatch);
|
|
706
894
|
getSmartLinkAdf(text, 'inline', cardsOptions).then(function () {
|
|
707
895
|
// we use view.state rather than state because state becomes a stale
|
|
708
896
|
// state reference after getSmartLinkAdf's async work
|
|
709
|
-
var
|
|
710
|
-
pastedMacroPositions =
|
|
897
|
+
var _getPastePluginState2 = (0, _pluginFactory.getPluginState)(view.state),
|
|
898
|
+
pastedMacroPositions = _getPastePluginState2.pastedMacroPositions;
|
|
711
899
|
if (dispatch) {
|
|
712
900
|
handleMarkdown(slice, queueCardsFromChangedTr, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo])(view.state, dispatch);
|
|
713
901
|
}
|
|
714
902
|
}).catch(function () {
|
|
715
|
-
var
|
|
716
|
-
pastedMacroPositions =
|
|
903
|
+
var _getPastePluginState3 = (0, _pluginFactory.getPluginState)(view.state),
|
|
904
|
+
pastedMacroPositions = _getPastePluginState3.pastedMacroPositions;
|
|
717
905
|
insertAutoMacro(slice, macro, view, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo]);
|
|
718
906
|
}).finally(function () {
|
|
719
907
|
(0, _commands.stopTrackingPastedMacroPositions)([trackingFrom, trackingTo])(view.state, dispatch);
|
|
@@ -1007,6 +1195,36 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
|
|
|
1007
1195
|
}
|
|
1008
1196
|
return marks;
|
|
1009
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* Variant of `handleMarkdown` used by macro auto-conversion behind
|
|
1201
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
1202
|
+
*
|
|
1203
|
+
* Differs in two ways: the inserted range is derived from the steps that were applied
|
|
1204
|
+
* rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
|
|
1205
|
+
* behaviour; and that range is reported back through `onInsert` so the caller can track
|
|
1206
|
+
* the inserted link across the async gap before deciding whether to swap it for a macro.
|
|
1207
|
+
*/
|
|
1208
|
+
function handleMarkdownWithInsertedRange(markdownSlice, queueCardsFromChangedTr, onInsert) {
|
|
1209
|
+
return function (state, dispatch) {
|
|
1210
|
+
var tr = (0, _closeHistory.closeHistory)(state.tr);
|
|
1211
|
+
var replaceFrom = tr.selection.from;
|
|
1212
|
+
var replaceTo = tr.selection.to;
|
|
1213
|
+
tr.replaceSelection(markdownSlice);
|
|
1214
|
+
var insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
1215
|
+
var insertedTo = tr.mapping.map(replaceTo, 1);
|
|
1216
|
+
tr.setSelection(_state.TextSelection.near(tr.doc.resolve(Math.min(insertedTo, tr.doc.content.size)), -1));
|
|
1217
|
+
queueCardsFromChangedTr === null || queueCardsFromChangedTr === void 0 || queueCardsFromChangedTr(state, tr, _analytics.INPUT_METHOD.CLIPBOARD);
|
|
1218
|
+
if (dispatch) {
|
|
1219
|
+
dispatch(tr.scrollIntoView());
|
|
1220
|
+
onInsert({
|
|
1221
|
+
from: insertedFrom,
|
|
1222
|
+
to: insertedTo
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
return true;
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1010
1228
|
function handleParagraphBlockMarks(state, slice) {
|
|
1011
1229
|
var _findParentNodeOfType2;
|
|
1012
1230
|
if (slice.content.size === 0) {
|
|
@@ -376,7 +376,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// run macro autoconvert prior to other conversions
|
|
379
|
-
if (handleMacroAutoConvert(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 ? void 0 : (_pluginInjectionApi$c2 = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 ? void 0 : (_pluginInjectionApi$e2 = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
379
|
+
if (handleMacroAutoConvert(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 ? void 0 : (_pluginInjectionApi$c2 = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 ? void 0 : (_pluginInjectionApi$e2 = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
380
380
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
381
381
|
sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, markdownSlice, {
|
|
382
382
|
type: PasteTypes.markdown
|
|
@@ -476,7 +476,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
// run macro autoconvert prior to other conversions
|
|
479
|
-
if (handleMacroAutoConvert(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : (_pluginInjectionApi$c4 = _pluginInjectionApi$c3.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$e3 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e3 === void 0 ? void 0 : (_pluginInjectionApi$e4 = _pluginInjectionApi$e3.actions) === null || _pluginInjectionApi$e4 === void 0 ? void 0 : _pluginInjectionApi$e4.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
479
|
+
if (handleMacroAutoConvert(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : (_pluginInjectionApi$c4 = _pluginInjectionApi$c3.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$e3 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e3 === void 0 ? void 0 : (_pluginInjectionApi$e4 = _pluginInjectionApi$e3.actions) === null || _pluginInjectionApi$e4 === void 0 ? void 0 : _pluginInjectionApi$e4.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
480
480
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
481
481
|
sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, slice, {
|
|
482
482
|
type: PasteTypes.richText
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
import { transformContainerNodes } from '@atlaskit/adf-utils/transforms';
|
|
4
|
-
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
5
5
|
import { addLinkMetadata } from '@atlaskit/editor-common/card';
|
|
6
6
|
import { insideTable } from '@atlaskit/editor-common/core-utils';
|
|
7
7
|
import { getBlockMarkAttrs, getFirstParagraphBlockMarkAttrs, reconcileBlockMarkForContainerAtPos, reconcileBlockMarkForParagraphAtPos, reconcileBlockMarkInRange } from '@atlaskit/editor-common/lists';
|
|
@@ -608,7 +608,7 @@ async function getSmartLinkAdf(text, type, cardOptions) {
|
|
|
608
608
|
const provider = await cardOptions.provider;
|
|
609
609
|
return await provider.resolve(text, type);
|
|
610
610
|
}
|
|
611
|
-
function
|
|
611
|
+
function insertAutoMacroOld(slice, macro, view, from, to) {
|
|
612
612
|
if (view) {
|
|
613
613
|
// insert the text or linkified/md-converted clipboard data
|
|
614
614
|
const selection = view.state.tr.selection;
|
|
@@ -635,7 +635,172 @@ function insertAutoMacro(slice, macro, view, from, to) {
|
|
|
635
635
|
}
|
|
636
636
|
return false;
|
|
637
637
|
}
|
|
638
|
-
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Replacement for `insertAutoMacroOld` behind
|
|
641
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
642
|
+
*
|
|
643
|
+
* Identical in shape, except that the range the macro replaces is derived from the steps
|
|
644
|
+
* that were actually applied rather than from `slice.size`. `slice.size` is the slice's
|
|
645
|
+
* own measure and does not account for ProseMirror's fitting behaviour in
|
|
646
|
+
* `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
|
|
647
|
+
* is inserted without removing the text it should replace, leaving both the pasted text
|
|
648
|
+
* and the macro in the document.
|
|
649
|
+
*/
|
|
650
|
+
function insertAutoMacroNew(slice, macro, view, from, to) {
|
|
651
|
+
if (!view) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const {
|
|
655
|
+
selection
|
|
656
|
+
} = view.state;
|
|
657
|
+
const hasExplicitRange = typeof from === 'number' && typeof to === 'number';
|
|
658
|
+
const replaceFrom = hasExplicitRange ? from : selection.from;
|
|
659
|
+
const replaceTo = hasExplicitRange ? to : selection.to;
|
|
660
|
+
|
|
661
|
+
// insert the text or linkified/md-converted clipboard data
|
|
662
|
+
const tr = hasExplicitRange ? view.state.tr.replaceRange(replaceFrom, replaceTo, slice) : view.state.tr.replaceSelection(slice);
|
|
663
|
+
const insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
664
|
+
const insertedTo = tr.mapping.map(replaceTo, 1);
|
|
665
|
+
view.dispatch(tr);
|
|
666
|
+
|
|
667
|
+
// Leave the document alone rather than replacing an implausible range.
|
|
668
|
+
if (insertedFrom >= insertedTo || insertedTo > view.state.doc.content.size) {
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// replace the text with the macro as a separate transaction
|
|
673
|
+
// so the autoconversion generates 2 undo steps
|
|
674
|
+
const macroTr = closeHistory(view.state.tr).replaceRangeWith(insertedFrom, insertedTo, macro).scrollIntoView();
|
|
675
|
+
addLinkMetadata(view.state.selection, macroTr, {
|
|
676
|
+
inputMethod: INPUT_METHOD.CLIPBOARD,
|
|
677
|
+
cardAction: 'AUTO_CONVERT'
|
|
678
|
+
});
|
|
679
|
+
view.dispatch(macroTr);
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
function insertAutoMacro(slice, macro, view, from, to) {
|
|
683
|
+
return isExperimentEnabled('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Locates the range currently occupied by a link that we pasted earlier.
|
|
688
|
+
*
|
|
689
|
+
* Positions recorded before an async gap can drift or be invalidated entirely by
|
|
690
|
+
* concurrent edits, collaborative changes and `appendTransaction` hooks, so the
|
|
691
|
+
* recorded range is verified against the document before it is used. If it no longer
|
|
692
|
+
* holds the pasted link the link is re-located by href, preferring the occurrence
|
|
693
|
+
* closest to where it was last seen.
|
|
694
|
+
*
|
|
695
|
+
* Returns `undefined` when the link cannot be found, so callers can leave the
|
|
696
|
+
* document alone rather than replacing whatever happens to sit at a stale position.
|
|
697
|
+
*/
|
|
698
|
+
/**
|
|
699
|
+
* Whether every text node spanning [from, to) carries a `link` mark pointing at `url`,
|
|
700
|
+
* with no gaps or uncovered portions.
|
|
701
|
+
*/
|
|
702
|
+
function rangeIsExactLinkMark(doc, from, to, url, linkMarkType) {
|
|
703
|
+
let matches = true;
|
|
704
|
+
let coveredTo = from;
|
|
705
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
706
|
+
if (!matches || pos >= to) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
if (!node.isLeaf) {
|
|
710
|
+
// A container node — e.g. the paragraph the link sits in. `nodesBetween` visits
|
|
711
|
+
// these on the way down to the text node(s) we actually care about; keep
|
|
712
|
+
// descending into it rather than treating it as a mismatch.
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
if (!node.isText) {
|
|
716
|
+
// A non-text leaf (e.g. an inline card) cannot carry the link mark.
|
|
717
|
+
matches = false;
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
const linkMark = linkMarkType.isInSet(node.marks);
|
|
721
|
+
if (!linkMark || linkMark.attrs.href !== url) {
|
|
722
|
+
matches = false;
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
coveredTo = Math.min(pos + node.nodeSize, to);
|
|
726
|
+
return true;
|
|
727
|
+
});
|
|
728
|
+
return matches && coveredTo === to;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* `from`/`to` are not a one-off snapshot: they come from `pastedMacroPositions`, which the
|
|
733
|
+
* paste plugin's `mapping` hook (`plugin-factory.ts`) maps through every transaction
|
|
734
|
+
* applied while the smart link request is in flight, via `tr.mapping.map(position)`. So
|
|
735
|
+
* this covers every edit ProseMirror can express as a position mapping — typing,
|
|
736
|
+
* deleting, or moving content elsewhere in the same edit all keep `from`/`to` pointing at
|
|
737
|
+
* the same logical span.
|
|
738
|
+
*
|
|
739
|
+
* It stops covering that span only when the span itself no longer represents our pasted
|
|
740
|
+
* link — the link was unlinked, or its content was replaced outright. Deliberately no
|
|
741
|
+
* fallback search for "the nearest link with the same href" is done: with multiple links
|
|
742
|
+
* to the same URL in the document, a proximity search cannot tell which one we pasted, so
|
|
743
|
+
* it risks silently replacing an occurrence we were never asked to touch, and it requires
|
|
744
|
+
* an O(doc size) scan on every fallback to do it. Leaving the tracked link as a link is
|
|
745
|
+
* the safe outcome here, and it's what happens when this function returns `undefined`.
|
|
746
|
+
*/
|
|
747
|
+
function findPastedLinkRange(state, url, from, to) {
|
|
748
|
+
const {
|
|
749
|
+
doc,
|
|
750
|
+
schema
|
|
751
|
+
} = state;
|
|
752
|
+
const linkMarkType = schema.marks.link;
|
|
753
|
+
const docSize = doc.content.size;
|
|
754
|
+
if (!linkMarkType || typeof from !== 'number' || typeof to !== 'number' || from < 0 || from >= to || to > docSize) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Text equality alone is not enough: if the link was unlinked (mark removed) while
|
|
759
|
+
// the smart link request was in flight, the text can still read as the URL even
|
|
760
|
+
// though it is no longer a link, and replacing it would destroy content that is no
|
|
761
|
+
// longer ours to replace.
|
|
762
|
+
if (doc.textBetween(from, to) === url && rangeIsExactLinkMark(doc, from, to, url, linkMarkType)) {
|
|
763
|
+
return {
|
|
764
|
+
from,
|
|
765
|
+
to
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Swaps a link that was already pasted into the document for its macro equivalent.
|
|
773
|
+
*
|
|
774
|
+
* Used when smart links could not resolve the URL, so the macro autoconversion is the
|
|
775
|
+
* fallback. Does nothing if the link can no longer be found — for example because a
|
|
776
|
+
* smart card has already replaced it — which is preferable to replacing unrelated
|
|
777
|
+
* content at a stale position.
|
|
778
|
+
*/
|
|
779
|
+
function replacePastedLinkWithMacro(view, macro, url, from, to, editorAnalyticsAPI) {
|
|
780
|
+
const range = findPastedLinkRange(view.state, url, from, to);
|
|
781
|
+
if (!range) {
|
|
782
|
+
// Metric for how often the tracked pasted link could no longer be found once the
|
|
783
|
+
// smart link request settled, so the assumption that this is rare is measurable
|
|
784
|
+
// rather than assumed.
|
|
785
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({
|
|
786
|
+
action: ACTION.ERRORED,
|
|
787
|
+
actionSubject: ACTION_SUBJECT.SMART_LINK,
|
|
788
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
789
|
+
attributes: {
|
|
790
|
+
error: 'macro-auto-convert-pasted-link-not-found'
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
const macroTr = closeHistory(view.state.tr).replaceRangeWith(range.from, range.to, macro).scrollIntoView();
|
|
796
|
+
addLinkMetadata(view.state.selection, macroTr, {
|
|
797
|
+
inputMethod: INPUT_METHOD.CLIPBOARD,
|
|
798
|
+
cardAction: 'AUTO_CONVERT'
|
|
799
|
+
});
|
|
800
|
+
view.dispatch(macroTr);
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, runMacroAutoConvert, cardsOptions, extensionAutoConverter, editorAnalyticsAPI) {
|
|
639
804
|
return (state, dispatch, view) => {
|
|
640
805
|
let macro = null;
|
|
641
806
|
|
|
@@ -663,11 +828,42 @@ export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, run
|
|
|
663
828
|
if (!view) {
|
|
664
829
|
throw new Error('View is missing');
|
|
665
830
|
}
|
|
831
|
+
const autoConvertMacro = macro;
|
|
666
832
|
|
|
667
833
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
668
834
|
const trackingId = uuid();
|
|
669
835
|
const trackingFrom = `handleMacroAutoConvert-from-${trackingId}`;
|
|
670
836
|
const trackingTo = `handleMacroAutoConvert-to-${trackingId}`;
|
|
837
|
+
if (isExperimentEnabled('platform_editor_paste_autoconvert_robustness')) {
|
|
838
|
+
// Insert the pasted content straight away. The smart link provider is only
|
|
839
|
+
// consulted to decide whether the inserted link should later be swapped for
|
|
840
|
+
// a macro, so it does not need to gate the insert — otherwise every
|
|
841
|
+
// autoconvertible paste appears to do nothing until a network round trip
|
|
842
|
+
// completes.
|
|
843
|
+
let insertedRange;
|
|
844
|
+
handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, range => {
|
|
845
|
+
insertedRange = range;
|
|
846
|
+
})(state, dispatch);
|
|
847
|
+
if (!insertedRange) {
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
startTrackingPastedMacroPositions({
|
|
851
|
+
[trackingFrom]: insertedRange.from,
|
|
852
|
+
[trackingTo]: insertedRange.to
|
|
853
|
+
})(view.state, dispatch);
|
|
854
|
+
getSmartLinkAdf(text, 'inline', cardsOptions).catch(() => {
|
|
855
|
+
// Smart links could not resolve the URL, so fall back to the macro.
|
|
856
|
+
// we use view.state rather than state because state becomes a stale
|
|
857
|
+
// state reference after getSmartLinkAdf's async work
|
|
858
|
+
const {
|
|
859
|
+
pastedMacroPositions
|
|
860
|
+
} = getPastePluginState(view.state);
|
|
861
|
+
replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
|
|
862
|
+
}).finally(() => {
|
|
863
|
+
stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
|
|
864
|
+
});
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
671
867
|
startTrackingPastedMacroPositions({
|
|
672
868
|
[trackingFrom]: state.selection.from,
|
|
673
869
|
[trackingTo]: state.selection.to
|
|
@@ -984,6 +1180,36 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
|
|
|
984
1180
|
}
|
|
985
1181
|
return marks;
|
|
986
1182
|
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Variant of `handleMarkdown` used by macro auto-conversion behind
|
|
1186
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
1187
|
+
*
|
|
1188
|
+
* Differs in two ways: the inserted range is derived from the steps that were applied
|
|
1189
|
+
* rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
|
|
1190
|
+
* behaviour; and that range is reported back through `onInsert` so the caller can track
|
|
1191
|
+
* the inserted link across the async gap before deciding whether to swap it for a macro.
|
|
1192
|
+
*/
|
|
1193
|
+
function handleMarkdownWithInsertedRange(markdownSlice, queueCardsFromChangedTr, onInsert) {
|
|
1194
|
+
return (state, dispatch) => {
|
|
1195
|
+
const tr = closeHistory(state.tr);
|
|
1196
|
+
const replaceFrom = tr.selection.from;
|
|
1197
|
+
const replaceTo = tr.selection.to;
|
|
1198
|
+
tr.replaceSelection(markdownSlice);
|
|
1199
|
+
const insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
1200
|
+
const insertedTo = tr.mapping.map(replaceTo, 1);
|
|
1201
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(insertedTo, tr.doc.content.size)), -1));
|
|
1202
|
+
queueCardsFromChangedTr === null || queueCardsFromChangedTr === void 0 ? void 0 : queueCardsFromChangedTr(state, tr, INPUT_METHOD.CLIPBOARD);
|
|
1203
|
+
if (dispatch) {
|
|
1204
|
+
dispatch(tr.scrollIntoView());
|
|
1205
|
+
onInsert({
|
|
1206
|
+
from: insertedFrom,
|
|
1207
|
+
to: insertedTo
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
return true;
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
987
1213
|
export function handleParagraphBlockMarks(state, slice) {
|
|
988
1214
|
var _findParentNodeOfType2;
|
|
989
1215
|
if (slice.content.size === 0) {
|
|
@@ -409,7 +409,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
// run macro autoconvert prior to other conversions
|
|
412
|
-
if (handleMacroAutoConvert(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
412
|
+
if (handleMacroAutoConvert(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
413
413
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
414
414
|
sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, markdownSlice, {
|
|
415
415
|
type: PasteTypes.markdown
|
|
@@ -507,7 +507,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
// run macro autoconvert prior to other conversions
|
|
510
|
-
if (handleMacroAutoConvert(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 || (_pluginInjectionApi$c2 = _pluginInjectionApi$c2.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
|
|
510
|
+
if (handleMacroAutoConvert(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 || (_pluginInjectionApi$c2 = _pluginInjectionApi$c2.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter, editorAnalyticsAPI)(state, dispatch, view)) {
|
|
511
511
|
// TODO: ED-26959 - handleMacroAutoConvert dispatch twice, so we can't use the helper
|
|
512
512
|
sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, slice, {
|
|
513
513
|
type: PasteTypes.richText
|
|
@@ -10,7 +10,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
10
10
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
import { transformContainerNodes } from '@atlaskit/adf-utils/transforms';
|
|
13
|
-
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
13
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
14
14
|
import { addLinkMetadata } from '@atlaskit/editor-common/card';
|
|
15
15
|
import { insideTable } from '@atlaskit/editor-common/core-utils';
|
|
16
16
|
import { getBlockMarkAttrs, getFirstParagraphBlockMarkAttrs, reconcileBlockMarkForContainerAtPos, reconcileBlockMarkForParagraphAtPos, reconcileBlockMarkInRange } from '@atlaskit/editor-common/lists';
|
|
@@ -613,7 +613,7 @@ function _getSmartLinkAdf() {
|
|
|
613
613
|
}));
|
|
614
614
|
return _getSmartLinkAdf.apply(this, arguments);
|
|
615
615
|
}
|
|
616
|
-
function
|
|
616
|
+
function insertAutoMacroOld(slice, macro, view, from, to) {
|
|
617
617
|
if (view) {
|
|
618
618
|
// insert the text or linkified/md-converted clipboard data
|
|
619
619
|
var selection = view.state.tr.selection;
|
|
@@ -640,7 +640,168 @@ function insertAutoMacro(slice, macro, view, from, to) {
|
|
|
640
640
|
}
|
|
641
641
|
return false;
|
|
642
642
|
}
|
|
643
|
-
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Replacement for `insertAutoMacroOld` behind
|
|
646
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
647
|
+
*
|
|
648
|
+
* Identical in shape, except that the range the macro replaces is derived from the steps
|
|
649
|
+
* that were actually applied rather than from `slice.size`. `slice.size` is the slice's
|
|
650
|
+
* own measure and does not account for ProseMirror's fitting behaviour in
|
|
651
|
+
* `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
|
|
652
|
+
* is inserted without removing the text it should replace, leaving both the pasted text
|
|
653
|
+
* and the macro in the document.
|
|
654
|
+
*/
|
|
655
|
+
function insertAutoMacroNew(slice, macro, view, from, to) {
|
|
656
|
+
if (!view) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
var selection = view.state.selection;
|
|
660
|
+
var hasExplicitRange = typeof from === 'number' && typeof to === 'number';
|
|
661
|
+
var replaceFrom = hasExplicitRange ? from : selection.from;
|
|
662
|
+
var replaceTo = hasExplicitRange ? to : selection.to;
|
|
663
|
+
|
|
664
|
+
// insert the text or linkified/md-converted clipboard data
|
|
665
|
+
var tr = hasExplicitRange ? view.state.tr.replaceRange(replaceFrom, replaceTo, slice) : view.state.tr.replaceSelection(slice);
|
|
666
|
+
var insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
667
|
+
var insertedTo = tr.mapping.map(replaceTo, 1);
|
|
668
|
+
view.dispatch(tr);
|
|
669
|
+
|
|
670
|
+
// Leave the document alone rather than replacing an implausible range.
|
|
671
|
+
if (insertedFrom >= insertedTo || insertedTo > view.state.doc.content.size) {
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// replace the text with the macro as a separate transaction
|
|
676
|
+
// so the autoconversion generates 2 undo steps
|
|
677
|
+
var macroTr = closeHistory(view.state.tr).replaceRangeWith(insertedFrom, insertedTo, macro).scrollIntoView();
|
|
678
|
+
addLinkMetadata(view.state.selection, macroTr, {
|
|
679
|
+
inputMethod: INPUT_METHOD.CLIPBOARD,
|
|
680
|
+
cardAction: 'AUTO_CONVERT'
|
|
681
|
+
});
|
|
682
|
+
view.dispatch(macroTr);
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
function insertAutoMacro(slice, macro, view, from, to) {
|
|
686
|
+
return isExperimentEnabled('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Locates the range currently occupied by a link that we pasted earlier.
|
|
691
|
+
*
|
|
692
|
+
* Positions recorded before an async gap can drift or be invalidated entirely by
|
|
693
|
+
* concurrent edits, collaborative changes and `appendTransaction` hooks, so the
|
|
694
|
+
* recorded range is verified against the document before it is used. If it no longer
|
|
695
|
+
* holds the pasted link the link is re-located by href, preferring the occurrence
|
|
696
|
+
* closest to where it was last seen.
|
|
697
|
+
*
|
|
698
|
+
* Returns `undefined` when the link cannot be found, so callers can leave the
|
|
699
|
+
* document alone rather than replacing whatever happens to sit at a stale position.
|
|
700
|
+
*/
|
|
701
|
+
/**
|
|
702
|
+
* Whether every text node spanning [from, to) carries a `link` mark pointing at `url`,
|
|
703
|
+
* with no gaps or uncovered portions.
|
|
704
|
+
*/
|
|
705
|
+
function rangeIsExactLinkMark(doc, from, to, url, linkMarkType) {
|
|
706
|
+
var matches = true;
|
|
707
|
+
var coveredTo = from;
|
|
708
|
+
doc.nodesBetween(from, to, function (node, pos) {
|
|
709
|
+
if (!matches || pos >= to) {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
if (!node.isLeaf) {
|
|
713
|
+
// A container node — e.g. the paragraph the link sits in. `nodesBetween` visits
|
|
714
|
+
// these on the way down to the text node(s) we actually care about; keep
|
|
715
|
+
// descending into it rather than treating it as a mismatch.
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
if (!node.isText) {
|
|
719
|
+
// A non-text leaf (e.g. an inline card) cannot carry the link mark.
|
|
720
|
+
matches = false;
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
var linkMark = linkMarkType.isInSet(node.marks);
|
|
724
|
+
if (!linkMark || linkMark.attrs.href !== url) {
|
|
725
|
+
matches = false;
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
coveredTo = Math.min(pos + node.nodeSize, to);
|
|
729
|
+
return true;
|
|
730
|
+
});
|
|
731
|
+
return matches && coveredTo === to;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* `from`/`to` are not a one-off snapshot: they come from `pastedMacroPositions`, which the
|
|
736
|
+
* paste plugin's `mapping` hook (`plugin-factory.ts`) maps through every transaction
|
|
737
|
+
* applied while the smart link request is in flight, via `tr.mapping.map(position)`. So
|
|
738
|
+
* this covers every edit ProseMirror can express as a position mapping — typing,
|
|
739
|
+
* deleting, or moving content elsewhere in the same edit all keep `from`/`to` pointing at
|
|
740
|
+
* the same logical span.
|
|
741
|
+
*
|
|
742
|
+
* It stops covering that span only when the span itself no longer represents our pasted
|
|
743
|
+
* link — the link was unlinked, or its content was replaced outright. Deliberately no
|
|
744
|
+
* fallback search for "the nearest link with the same href" is done: with multiple links
|
|
745
|
+
* to the same URL in the document, a proximity search cannot tell which one we pasted, so
|
|
746
|
+
* it risks silently replacing an occurrence we were never asked to touch, and it requires
|
|
747
|
+
* an O(doc size) scan on every fallback to do it. Leaving the tracked link as a link is
|
|
748
|
+
* the safe outcome here, and it's what happens when this function returns `undefined`.
|
|
749
|
+
*/
|
|
750
|
+
function findPastedLinkRange(state, url, from, to) {
|
|
751
|
+
var doc = state.doc,
|
|
752
|
+
schema = state.schema;
|
|
753
|
+
var linkMarkType = schema.marks.link;
|
|
754
|
+
var docSize = doc.content.size;
|
|
755
|
+
if (!linkMarkType || typeof from !== 'number' || typeof to !== 'number' || from < 0 || from >= to || to > docSize) {
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Text equality alone is not enough: if the link was unlinked (mark removed) while
|
|
760
|
+
// the smart link request was in flight, the text can still read as the URL even
|
|
761
|
+
// though it is no longer a link, and replacing it would destroy content that is no
|
|
762
|
+
// longer ours to replace.
|
|
763
|
+
if (doc.textBetween(from, to) === url && rangeIsExactLinkMark(doc, from, to, url, linkMarkType)) {
|
|
764
|
+
return {
|
|
765
|
+
from: from,
|
|
766
|
+
to: to
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Swaps a link that was already pasted into the document for its macro equivalent.
|
|
774
|
+
*
|
|
775
|
+
* Used when smart links could not resolve the URL, so the macro autoconversion is the
|
|
776
|
+
* fallback. Does nothing if the link can no longer be found — for example because a
|
|
777
|
+
* smart card has already replaced it — which is preferable to replacing unrelated
|
|
778
|
+
* content at a stale position.
|
|
779
|
+
*/
|
|
780
|
+
function replacePastedLinkWithMacro(view, macro, url, from, to, editorAnalyticsAPI) {
|
|
781
|
+
var range = findPastedLinkRange(view.state, url, from, to);
|
|
782
|
+
if (!range) {
|
|
783
|
+
// Metric for how often the tracked pasted link could no longer be found once the
|
|
784
|
+
// smart link request settled, so the assumption that this is rare is measurable
|
|
785
|
+
// rather than assumed.
|
|
786
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
787
|
+
action: ACTION.ERRORED,
|
|
788
|
+
actionSubject: ACTION_SUBJECT.SMART_LINK,
|
|
789
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
790
|
+
attributes: {
|
|
791
|
+
error: 'macro-auto-convert-pasted-link-not-found'
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
var macroTr = closeHistory(view.state.tr).replaceRangeWith(range.from, range.to, macro).scrollIntoView();
|
|
797
|
+
addLinkMetadata(view.state.selection, macroTr, {
|
|
798
|
+
inputMethod: INPUT_METHOD.CLIPBOARD,
|
|
799
|
+
cardAction: 'AUTO_CONVERT'
|
|
800
|
+
});
|
|
801
|
+
view.dispatch(macroTr);
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, runMacroAutoConvert, cardsOptions, extensionAutoConverter, editorAnalyticsAPI) {
|
|
644
805
|
return function (state, dispatch, view) {
|
|
645
806
|
var macro = null;
|
|
646
807
|
|
|
@@ -668,23 +829,50 @@ export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, run
|
|
|
668
829
|
if (!view) {
|
|
669
830
|
throw new Error('View is missing');
|
|
670
831
|
}
|
|
832
|
+
var autoConvertMacro = macro;
|
|
671
833
|
|
|
672
834
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
673
835
|
var trackingId = uuid();
|
|
674
836
|
var trackingFrom = "handleMacroAutoConvert-from-".concat(trackingId);
|
|
675
837
|
var trackingTo = "handleMacroAutoConvert-to-".concat(trackingId);
|
|
838
|
+
if (isExperimentEnabled('platform_editor_paste_autoconvert_robustness')) {
|
|
839
|
+
// Insert the pasted content straight away. The smart link provider is only
|
|
840
|
+
// consulted to decide whether the inserted link should later be swapped for
|
|
841
|
+
// a macro, so it does not need to gate the insert — otherwise every
|
|
842
|
+
// autoconvertible paste appears to do nothing until a network round trip
|
|
843
|
+
// completes.
|
|
844
|
+
var insertedRange;
|
|
845
|
+
handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
|
|
846
|
+
insertedRange = range;
|
|
847
|
+
})(state, dispatch);
|
|
848
|
+
if (!insertedRange) {
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
startTrackingPastedMacroPositions(_defineProperty(_defineProperty({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
|
|
852
|
+
getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
|
|
853
|
+
// Smart links could not resolve the URL, so fall back to the macro.
|
|
854
|
+
// we use view.state rather than state because state becomes a stale
|
|
855
|
+
// state reference after getSmartLinkAdf's async work
|
|
856
|
+
var _getPastePluginState = getPastePluginState(view.state),
|
|
857
|
+
pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
|
|
858
|
+
replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
|
|
859
|
+
}).finally(function () {
|
|
860
|
+
stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
|
|
861
|
+
});
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
676
864
|
startTrackingPastedMacroPositions(_defineProperty(_defineProperty({}, trackingFrom, state.selection.from), trackingTo, state.selection.to))(state, dispatch);
|
|
677
865
|
getSmartLinkAdf(text, 'inline', cardsOptions).then(function () {
|
|
678
866
|
// we use view.state rather than state because state becomes a stale
|
|
679
867
|
// state reference after getSmartLinkAdf's async work
|
|
680
|
-
var
|
|
681
|
-
pastedMacroPositions =
|
|
868
|
+
var _getPastePluginState2 = getPastePluginState(view.state),
|
|
869
|
+
pastedMacroPositions = _getPastePluginState2.pastedMacroPositions;
|
|
682
870
|
if (dispatch) {
|
|
683
871
|
handleMarkdown(slice, queueCardsFromChangedTr, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo])(view.state, dispatch);
|
|
684
872
|
}
|
|
685
873
|
}).catch(function () {
|
|
686
|
-
var
|
|
687
|
-
pastedMacroPositions =
|
|
874
|
+
var _getPastePluginState3 = getPastePluginState(view.state),
|
|
875
|
+
pastedMacroPositions = _getPastePluginState3.pastedMacroPositions;
|
|
688
876
|
insertAutoMacro(slice, macro, view, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo]);
|
|
689
877
|
}).finally(function () {
|
|
690
878
|
stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
|
|
@@ -978,6 +1166,36 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
|
|
|
978
1166
|
}
|
|
979
1167
|
return marks;
|
|
980
1168
|
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Variant of `handleMarkdown` used by macro auto-conversion behind
|
|
1172
|
+
* `platform_editor_paste_autoconvert_robustness`.
|
|
1173
|
+
*
|
|
1174
|
+
* Differs in two ways: the inserted range is derived from the steps that were applied
|
|
1175
|
+
* rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
|
|
1176
|
+
* behaviour; and that range is reported back through `onInsert` so the caller can track
|
|
1177
|
+
* the inserted link across the async gap before deciding whether to swap it for a macro.
|
|
1178
|
+
*/
|
|
1179
|
+
function handleMarkdownWithInsertedRange(markdownSlice, queueCardsFromChangedTr, onInsert) {
|
|
1180
|
+
return function (state, dispatch) {
|
|
1181
|
+
var tr = closeHistory(state.tr);
|
|
1182
|
+
var replaceFrom = tr.selection.from;
|
|
1183
|
+
var replaceTo = tr.selection.to;
|
|
1184
|
+
tr.replaceSelection(markdownSlice);
|
|
1185
|
+
var insertedFrom = tr.mapping.map(replaceFrom, -1);
|
|
1186
|
+
var insertedTo = tr.mapping.map(replaceTo, 1);
|
|
1187
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(insertedTo, tr.doc.content.size)), -1));
|
|
1188
|
+
queueCardsFromChangedTr === null || queueCardsFromChangedTr === void 0 || queueCardsFromChangedTr(state, tr, INPUT_METHOD.CLIPBOARD);
|
|
1189
|
+
if (dispatch) {
|
|
1190
|
+
dispatch(tr.scrollIntoView());
|
|
1191
|
+
onInsert({
|
|
1192
|
+
from: insertedFrom,
|
|
1193
|
+
to: insertedTo
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
return true;
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
981
1199
|
export function handleParagraphBlockMarks(state, slice) {
|
|
982
1200
|
var _findParentNodeOfType2;
|
|
983
1201
|
if (slice.content.size === 0) {
|
|
@@ -16,7 +16,7 @@ export declare function handlePastePanelOrDecisionContentIntoList(slice: Slice,
|
|
|
16
16
|
export declare function handlePasteLinkOnSelectedText(slice: Slice): Command;
|
|
17
17
|
export declare function handlePasteAsPlainText(slice: Slice, _event: ClipboardEvent, editorAnalyticsAPI: EditorAnalyticsAPI | undefined): Command;
|
|
18
18
|
export declare function handlePastePreservingMarks(slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined): Command;
|
|
19
|
-
export declare function handleMacroAutoConvert(text: string, slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined, runMacroAutoConvert: RunMacroAutoConvert | undefined, cardsOptions?: CardOptions, extensionAutoConverter?: ExtensionAutoConvertHandler): Command;
|
|
19
|
+
export declare function handleMacroAutoConvert(text: string, slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined, runMacroAutoConvert: RunMacroAutoConvert | undefined, cardsOptions?: CardOptions, extensionAutoConverter?: ExtensionAutoConvertHandler, editorAnalyticsAPI?: EditorAnalyticsAPI): Command;
|
|
20
20
|
export declare function handleCodeBlock(text: string): Command;
|
|
21
21
|
export declare function handleMediaSingle(inputMethod: InputMethodInsertMedia, insertMediaAsMediaSingle: InsertMediaAsMediaSingle | undefined): (slice: Slice) => Command;
|
|
22
22
|
export declare function handleTableContentPasteInBodiedExtension(slice: Slice): Command;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-paste",
|
|
3
|
-
"version": "18.0.
|
|
3
|
+
"version": "18.0.5",
|
|
4
4
|
"description": "Paste plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -82,5 +82,11 @@
|
|
|
82
82
|
"platform_editor_flexible_list_kill_switch_1": {
|
|
83
83
|
"type": "boolean"
|
|
84
84
|
}
|
|
85
|
+
},
|
|
86
|
+
"platform-experiments": {
|
|
87
|
+
"platform_editor_paste_autoconvert_robustness": {
|
|
88
|
+
"param": "isEnabled",
|
|
89
|
+
"defaultValue": false
|
|
90
|
+
}
|
|
85
91
|
}
|
|
86
92
|
}
|