@atlaskit/editor-plugin-paste 15.0.2 → 15.1.1

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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 15.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`0b4753c0a8c07`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0b4753c0a8c07) -
8
+ Clean up feature gates from completed rollout
9
+ - Updated dependencies
10
+
11
+ ## 15.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`a006cb2cc79f9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a006cb2cc79f9) -
16
+ Use the GFM markdown transformer for Cmd+V plain-text paste when
17
+ `platform_editor_paste_as_md_use_gfm` is enabled, matching the existing paste-as-markdown
18
+ (Shift+Cmd+V) path.
19
+
20
+ Consumers can inject a converter via `PastePluginOptions.markdownToPmConverter` and gate usage
21
+ with:
22
+
23
+ `expValEquals('platform_editor_paste_as_md_use_gfm', 'isEnabled', true)`.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies
28
+
3
29
  ## 15.0.2
4
30
 
5
31
  ### Patch Changes
@@ -6,8 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.pastePlugin = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
10
- var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
11
9
  var _main = require("./pm-plugins/main");
12
10
  var _plugin = require("./pm-plugins/move-analytics/plugin");
13
11
  var _pluginFactory = require("./pm-plugins/plugin-factory");
@@ -20,7 +18,8 @@ var pastePlugin = exports.pastePlugin = function pastePlugin(_ref) {
20
18
  cardOptions = _ref2.cardOptions,
21
19
  sanitizePrivateContent = _ref2.sanitizePrivateContent,
22
20
  isFullPage = _ref2.isFullPage,
23
- pasteWarningOptions = _ref2.pasteWarningOptions;
21
+ pasteWarningOptions = _ref2.pasteWarningOptions,
22
+ markdownToPmConverter = _ref2.markdownToPmConverter;
24
23
  var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
25
24
  var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
26
25
  return {
@@ -34,7 +33,7 @@ var pastePlugin = exports.pastePlugin = function pastePlugin(_ref) {
34
33
  dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
35
34
  dispatch = _ref3.dispatch,
36
35
  getIntl = _ref3.getIntl;
37
- return (0, _main.createPlugin)(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions);
36
+ return (0, _main.createPlugin)(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter);
38
37
  }
39
38
  }, {
40
39
  name: 'moveAnalyticsPlugin',
@@ -44,7 +43,7 @@ var pastePlugin = exports.pastePlugin = function pastePlugin(_ref) {
44
43
  }
45
44
  }];
46
45
  },
47
- contentComponent: !(0, _experiments.editorExperiment)('platform_synced_block', true) && !(0, _platformFeatureFlags.fg)('platform_synced_block_unsupported_products') ? undefined : function () {
46
+ contentComponent: function contentComponent() {
48
47
  if (!pasteWarningOptions) {
49
48
  return null;
50
49
  }
@@ -40,6 +40,7 @@ var _createClipboardTextSerializer = require("./create-clipboard-text-serializer
40
40
  var _pluginFactory = require("./plugin-factory");
41
41
  var _util = require("./util");
42
42
  var _handleVSCodeBlock = require("./util/edge-cases/handleVSCodeBlock");
43
+ var _getMarkdownSlice = require("./util/get-markdown-slice");
43
44
  var _handlers = require("./util/handlers");
44
45
  var _normalizePastedCodeBlockAttrs = require("./util/normalize-pasted-code-block-attrs");
45
46
  var _syncBlock2 = require("./util/sync-block");
@@ -85,11 +86,14 @@ function isSharePointUrl(url) {
85
86
  return false;
86
87
  }
87
88
  }
88
- function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions) {
89
+ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter) {
89
90
  var _pluginInjectionApi$a;
90
91
  var editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
91
92
  var atlassianMarkDownParser = new _editorMarkdownTransformer.MarkdownTransformer(schema, _paste.md);
92
93
  function getMarkdownSlice(text, openStart, openEnd) {
94
+ if (markdownToPmConverter && (0, _expValEquals.expValEquals)('platform_editor_paste_as_md_use_gfm', 'isEnabled', true)) {
95
+ return (0, _getMarkdownSlice.getMarkdownSliceViaGfm)(text, schema, openStart, openEnd, markdownToPmConverter);
96
+ }
93
97
  var escapedTextInput = (0, _util.escapeBackslashAndLinksExceptCodeBlock)(text);
94
98
  var doc = atlassianMarkDownParser.parse(escapedTextInput);
95
99
  if (doc && doc.content) {
@@ -375,9 +379,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
375
379
  event: event,
376
380
  text: text
377
381
  });
378
- if ((0, _experiments.editorExperiment)('platform_synced_block', true) || (0, _platformFeatureFlags.fg)('platform_synced_block_unsupported_products')) {
379
- slice = (0, _syncBlock2.handleSyncBlocksPaste)(slice, schema, (0, _util.getPasteSource)(event), html, pasteWarningOptions, pluginInjectionApi);
380
- }
382
+ slice = (0, _syncBlock2.handleSyncBlocksPaste)(slice, schema, (0, _util.getPasteSource)(event), html, pasteWarningOptions, pluginInjectionApi);
381
383
  var plainTextPasteSlice = (0, _utils.linkifyContent)(state.schema)(slice);
382
384
  if ((0, _analytics2.handlePasteAsPlainTextWithAnalytics)(editorAnalyticsAPI)(view, event, plainTextPasteSlice)(state, dispatch, view)) {
383
385
  return true;
@@ -7,8 +7,6 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.reducer = void 0;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
- var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
12
10
  var _actions = require("../editor-actions/actions");
13
11
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14
12
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -39,9 +37,6 @@ var reducer = exports.reducer = function reducer(state, action) {
39
37
  }
40
38
  case _actions.PastePluginActionTypes.SET_ACTIVE_FLAG:
41
39
  {
42
- if (!(0, _experiments.editorExperiment)('platform_synced_block', true) && !(0, _platformFeatureFlags.fg)('platform_synced_block_unsupported_products')) {
43
- return state;
44
- }
45
40
  return _objectSpread(_objectSpread({}, state), {}, {
46
41
  activeFlag: action.activeFlag
47
42
  });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getMarkdownSliceViaGfm = getMarkdownSliceViaGfm;
7
+ var _monitoring = require("@atlaskit/editor-common/monitoring");
8
+ var _model = require("@atlaskit/editor-prosemirror/model");
9
+ var _index = require("./index");
10
+ /**
11
+ * Convert plain-text markdown to a ProseMirror Slice using the GFM converter.
12
+ *
13
+ * Only called when `platform_editor_paste_as_md_use_gfm` is enabled AND a
14
+ * `markdownToPmConverter` is provided. Returns `undefined` if the converter
15
+ * throws or produces an empty document — the caller will propagate `undefined`
16
+ * without falling back to the legacy MarkdownTransformer.
17
+ */
18
+ function getMarkdownSliceViaGfm(text, schema, openStart, openEnd, markdownToPmConverter) {
19
+ // GFM parsers (e.g. markdownToAdf) natively recognise raw URLs as autolinks per
20
+ // the CommonMark spec — they do not need the `<url>` angle-bracket wrapping that
21
+ // the legacy MarkdownTransformer requires. Passing skipLinkEscaping: true avoids
22
+ // double-processing URLs and lets the GFM parser handle them correctly.
23
+ var escapedTextInput = (0, _index.escapeBackslashAndLinksExceptCodeBlock)(text, {
24
+ skipLinkEscaping: true
25
+ });
26
+ try {
27
+ var doc = schema.nodes.doc.createAndFill({}, markdownToPmConverter({
28
+ markdown: escapedTextInput,
29
+ schema: schema
30
+ }));
31
+ if (doc && doc.content) {
32
+ return new _model.Slice(doc.content, openStart, openEnd);
33
+ }
34
+ } catch (e) {
35
+ void (0, _monitoring.logException)(e instanceof Error ? e : new Error(String(e)), {
36
+ location: 'editor-plugin-paste/getMarkdownSlice'
37
+ });
38
+ }
39
+ return undefined;
40
+ }
@@ -140,7 +140,8 @@ function escapeLinks(text) {
140
140
  * const input = 'This is a link: https://example.com and a backslash: \\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```';
141
141
  * const output = escapeBackslashAndLinksExceptCodeBlock(input); // 'This is a link: <https://example.com> and a backslash: \\\\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```'
142
142
  */
143
- function escapeBackslashAndLinksExceptCodeBlock(textInput) {
143
+ function escapeBackslashAndLinksExceptCodeBlock(textInput, options) {
144
+ var shouldEscapeLinks = !(options !== null && options !== void 0 && options.skipLinkEscaping);
144
145
  // ref: https://spec.commonmark.org/0.31.2/#fenced-code-blocks
145
146
  var isInsideCodeBlock = false;
146
147
  var lines = textInput.split('\n');
@@ -162,7 +163,9 @@ function escapeBackslashAndLinksExceptCodeBlock(textInput) {
162
163
  // Ignored via go/ees005
163
164
  // eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
164
165
  var escaped = line.replace(/\\/g, '\\\\');
165
- escaped = escapeLinks(escaped);
166
+ if (shouldEscapeLinks) {
167
+ escaped = escapeLinks(escaped);
168
+ }
166
169
  return escaped;
167
170
  }
168
171
  }).join('\n');
@@ -72,7 +72,7 @@ var showWarningFlag = function showWarningFlag(_ref) {
72
72
  // Check if rawHtml contains a synced block
73
73
  // example: "<meta charset='utf-8'><html><head></head><body><div data-sync-block=\"\" data-local-id=\"\" data-resource-id=\"d64883c8-1270-431d-a1d3-51d36a1ed5f4\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"syncBlock\" data-prosemirror-node-block=\"true\" data-pm-slice=\"0 0 []\"></div></body></html>"
74
74
  var hasSyncedBlockInRawHtml = function hasSyncedBlockInRawHtml(rawHtml) {
75
- return rawHtml.includes('data-sync-block="');
75
+ return Boolean(rawHtml === null || rawHtml === void 0 ? void 0 : rawHtml.includes('data-sync-block="'));
76
76
  };
77
77
 
78
78
  /**
@@ -1,6 +1,4 @@
1
1
  import React from 'react';
2
- import { fg } from '@atlaskit/platform-feature-flags';
3
- import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
2
  import { createPlugin } from './pm-plugins/main';
5
3
  import { createPlugin as createMoveAnalyticsPlugin } from './pm-plugins/move-analytics/plugin';
6
4
  import { pluginKey } from './pm-plugins/plugin-factory';
@@ -14,7 +12,8 @@ export const pastePlugin = ({
14
12
  cardOptions,
15
13
  sanitizePrivateContent,
16
14
  isFullPage,
17
- pasteWarningOptions
15
+ pasteWarningOptions,
16
+ markdownToPmConverter
18
17
  } = config !== null && config !== void 0 ? config : {};
19
18
  const featureFlags = (api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
20
19
  const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
@@ -29,7 +28,7 @@ export const pastePlugin = ({
29
28
  dispatchAnalyticsEvent,
30
29
  dispatch,
31
30
  getIntl
32
- }) => createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions)
31
+ }) => createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter)
33
32
  }, {
34
33
  name: 'moveAnalyticsPlugin',
35
34
  plugin: ({
@@ -39,7 +38,7 @@ export const pastePlugin = ({
39
38
  }
40
39
  }];
41
40
  },
42
- contentComponent: !editorExperiment('platform_synced_block', true) && !fg('platform_synced_block_unsupported_products') ? undefined : () => {
41
+ contentComponent: () => {
43
42
  if (!pasteWarningOptions) {
44
43
  return null;
45
44
  }
@@ -30,6 +30,7 @@ import { createClipboardTextSerializer } from './create-clipboard-text-serialize
30
30
  import { createPluginState, pluginKey as stateKey } from './plugin-factory';
31
31
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
32
32
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
33
+ import { getMarkdownSliceViaGfm } from './util/get-markdown-slice';
33
34
  import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
34
35
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
35
36
  import { handleSyncBlocksPaste } from './util/sync-block';
@@ -74,11 +75,14 @@ export function isSharePointUrl(url) {
74
75
  return false;
75
76
  }
76
77
  }
77
- export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions) {
78
+ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter) {
78
79
  var _pluginInjectionApi$a;
79
80
  const editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
80
81
  const atlassianMarkDownParser = new MarkdownTransformer(schema, md);
81
82
  function getMarkdownSlice(text, openStart, openEnd) {
83
+ if (markdownToPmConverter && expValEquals('platform_editor_paste_as_md_use_gfm', 'isEnabled', true)) {
84
+ return getMarkdownSliceViaGfm(text, schema, openStart, openEnd, markdownToPmConverter);
85
+ }
82
86
  const escapedTextInput = escapeBackslashAndLinksExceptCodeBlock(text);
83
87
  const doc = atlassianMarkDownParser.parse(escapedTextInput);
84
88
  if (doc && doc.content) {
@@ -334,9 +338,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
334
338
  event,
335
339
  text
336
340
  });
337
- if (editorExperiment('platform_synced_block', true) || fg('platform_synced_block_unsupported_products')) {
338
- slice = handleSyncBlocksPaste(slice, schema, getPasteSource(event), html, pasteWarningOptions, pluginInjectionApi);
339
- }
341
+ slice = handleSyncBlocksPaste(slice, schema, getPasteSource(event), html, pasteWarningOptions, pluginInjectionApi);
340
342
  const plainTextPasteSlice = linkifyContent(state.schema)(slice);
341
343
  if (handlePasteAsPlainTextWithAnalytics(editorAnalyticsAPI)(view, event, plainTextPasteSlice)(state, dispatch, view)) {
342
344
  return true;
@@ -1,5 +1,3 @@
1
- import { fg } from '@atlaskit/platform-feature-flags';
2
- import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
1
  import { PastePluginActionTypes as ActionTypes } from '../editor-actions/actions';
4
2
  export const reducer = (state, action) => {
5
3
  switch (action.type) {
@@ -30,9 +28,6 @@ export const reducer = (state, action) => {
30
28
  }
31
29
  case ActionTypes.SET_ACTIVE_FLAG:
32
30
  {
33
- if (!editorExperiment('platform_synced_block', true) && !fg('platform_synced_block_unsupported_products')) {
34
- return state;
35
- }
36
31
  return {
37
32
  ...state,
38
33
  activeFlag: action.activeFlag
@@ -0,0 +1,35 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { Slice } from '@atlaskit/editor-prosemirror/model';
3
+ import { escapeBackslashAndLinksExceptCodeBlock } from './index';
4
+
5
+ /**
6
+ * Convert plain-text markdown to a ProseMirror Slice using the GFM converter.
7
+ *
8
+ * Only called when `platform_editor_paste_as_md_use_gfm` is enabled AND a
9
+ * `markdownToPmConverter` is provided. Returns `undefined` if the converter
10
+ * throws or produces an empty document — the caller will propagate `undefined`
11
+ * without falling back to the legacy MarkdownTransformer.
12
+ */
13
+ export function getMarkdownSliceViaGfm(text, schema, openStart, openEnd, markdownToPmConverter) {
14
+ // GFM parsers (e.g. markdownToAdf) natively recognise raw URLs as autolinks per
15
+ // the CommonMark spec — they do not need the `<url>` angle-bracket wrapping that
16
+ // the legacy MarkdownTransformer requires. Passing skipLinkEscaping: true avoids
17
+ // double-processing URLs and lets the GFM parser handle them correctly.
18
+ const escapedTextInput = escapeBackslashAndLinksExceptCodeBlock(text, {
19
+ skipLinkEscaping: true
20
+ });
21
+ try {
22
+ const doc = schema.nodes.doc.createAndFill({}, markdownToPmConverter({
23
+ markdown: escapedTextInput,
24
+ schema
25
+ }));
26
+ if (doc && doc.content) {
27
+ return new Slice(doc.content, openStart, openEnd);
28
+ }
29
+ } catch (e) {
30
+ void logException(e instanceof Error ? e : new Error(String(e)), {
31
+ location: 'editor-plugin-paste/getMarkdownSlice'
32
+ });
33
+ }
34
+ return undefined;
35
+ }
@@ -119,7 +119,8 @@ export function escapeLinks(text) {
119
119
  * const input = 'This is a link: https://example.com and a backslash: \\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```';
120
120
  * const output = escapeBackslashAndLinksExceptCodeBlock(input); // 'This is a link: <https://example.com> and a backslash: \\\\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```'
121
121
  */
122
- export function escapeBackslashAndLinksExceptCodeBlock(textInput) {
122
+ export function escapeBackslashAndLinksExceptCodeBlock(textInput, options) {
123
+ const shouldEscapeLinks = !(options !== null && options !== void 0 && options.skipLinkEscaping);
123
124
  // ref: https://spec.commonmark.org/0.31.2/#fenced-code-blocks
124
125
  let isInsideCodeBlock = false;
125
126
  const lines = textInput.split('\n');
@@ -141,7 +142,9 @@ export function escapeBackslashAndLinksExceptCodeBlock(textInput) {
141
142
  // Ignored via go/ees005
142
143
  // eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
143
144
  let escaped = line.replace(/\\/g, '\\\\');
144
- escaped = escapeLinks(escaped);
145
+ if (shouldEscapeLinks) {
146
+ escaped = escapeLinks(escaped);
147
+ }
145
148
  return escaped;
146
149
  }
147
150
  }).join('\n');
@@ -65,7 +65,7 @@ const showWarningFlag = ({
65
65
  // Check if rawHtml contains a synced block
66
66
  // example: "<meta charset='utf-8'><html><head></head><body><div data-sync-block=\"\" data-local-id=\"\" data-resource-id=\"d64883c8-1270-431d-a1d3-51d36a1ed5f4\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"syncBlock\" data-prosemirror-node-block=\"true\" data-pm-slice=\"0 0 []\"></div></body></html>"
67
67
  const hasSyncedBlockInRawHtml = rawHtml => {
68
- return rawHtml.includes('data-sync-block="');
68
+ return Boolean(rawHtml === null || rawHtml === void 0 ? void 0 : rawHtml.includes('data-sync-block="'));
69
69
  };
70
70
 
71
71
  /**
@@ -1,6 +1,4 @@
1
1
  import React from 'react';
2
- import { fg } from '@atlaskit/platform-feature-flags';
3
- import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
2
  import { createPlugin } from './pm-plugins/main';
5
3
  import { createPlugin as createMoveAnalyticsPlugin } from './pm-plugins/move-analytics/plugin';
6
4
  import { pluginKey } from './pm-plugins/plugin-factory';
@@ -13,7 +11,8 @@ export var pastePlugin = function pastePlugin(_ref) {
13
11
  cardOptions = _ref2.cardOptions,
14
12
  sanitizePrivateContent = _ref2.sanitizePrivateContent,
15
13
  isFullPage = _ref2.isFullPage,
16
- pasteWarningOptions = _ref2.pasteWarningOptions;
14
+ pasteWarningOptions = _ref2.pasteWarningOptions,
15
+ markdownToPmConverter = _ref2.markdownToPmConverter;
17
16
  var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
18
17
  var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
19
18
  return {
@@ -27,7 +26,7 @@ export var pastePlugin = function pastePlugin(_ref) {
27
26
  dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
28
27
  dispatch = _ref3.dispatch,
29
28
  getIntl = _ref3.getIntl;
30
- return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions);
29
+ return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter);
31
30
  }
32
31
  }, {
33
32
  name: 'moveAnalyticsPlugin',
@@ -37,7 +36,7 @@ export var pastePlugin = function pastePlugin(_ref) {
37
36
  }
38
37
  }];
39
38
  },
40
- contentComponent: !editorExperiment('platform_synced_block', true) && !fg('platform_synced_block_unsupported_products') ? undefined : function () {
39
+ contentComponent: function contentComponent() {
41
40
  if (!pasteWarningOptions) {
42
41
  return null;
43
42
  }
@@ -35,6 +35,7 @@ import { createClipboardTextSerializer } from './create-clipboard-text-serialize
35
35
  import { createPluginState, pluginKey as stateKey } from './plugin-factory';
36
36
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
37
37
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
38
+ import { getMarkdownSliceViaGfm } from './util/get-markdown-slice';
38
39
  import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
39
40
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
40
41
  import { handleSyncBlocksPaste } from './util/sync-block';
@@ -77,11 +78,14 @@ export function isSharePointUrl(url) {
77
78
  return false;
78
79
  }
79
80
  }
80
- export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions) {
81
+ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter) {
81
82
  var _pluginInjectionApi$a;
82
83
  var editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
83
84
  var atlassianMarkDownParser = new MarkdownTransformer(schema, md);
84
85
  function getMarkdownSlice(text, openStart, openEnd) {
86
+ if (markdownToPmConverter && expValEquals('platform_editor_paste_as_md_use_gfm', 'isEnabled', true)) {
87
+ return getMarkdownSliceViaGfm(text, schema, openStart, openEnd, markdownToPmConverter);
88
+ }
85
89
  var escapedTextInput = escapeBackslashAndLinksExceptCodeBlock(text);
86
90
  var doc = atlassianMarkDownParser.parse(escapedTextInput);
87
91
  if (doc && doc.content) {
@@ -367,9 +371,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
367
371
  event: event,
368
372
  text: text
369
373
  });
370
- if (editorExperiment('platform_synced_block', true) || fg('platform_synced_block_unsupported_products')) {
371
- slice = handleSyncBlocksPaste(slice, schema, getPasteSource(event), html, pasteWarningOptions, pluginInjectionApi);
372
- }
374
+ slice = handleSyncBlocksPaste(slice, schema, getPasteSource(event), html, pasteWarningOptions, pluginInjectionApi);
373
375
  var plainTextPasteSlice = linkifyContent(state.schema)(slice);
374
376
  if (handlePasteAsPlainTextWithAnalytics(editorAnalyticsAPI)(view, event, plainTextPasteSlice)(state, dispatch, view)) {
375
377
  return true;
@@ -2,8 +2,6 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
3
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
4
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
- import { fg } from '@atlaskit/platform-feature-flags';
6
- import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
7
5
  import { PastePluginActionTypes as ActionTypes } from '../editor-actions/actions';
8
6
  export var reducer = function reducer(state, action) {
9
7
  switch (action.type) {
@@ -32,9 +30,6 @@ export var reducer = function reducer(state, action) {
32
30
  }
33
31
  case ActionTypes.SET_ACTIVE_FLAG:
34
32
  {
35
- if (!editorExperiment('platform_synced_block', true) && !fg('platform_synced_block_unsupported_products')) {
36
- return state;
37
- }
38
33
  return _objectSpread(_objectSpread({}, state), {}, {
39
34
  activeFlag: action.activeFlag
40
35
  });
@@ -0,0 +1,35 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { Slice } from '@atlaskit/editor-prosemirror/model';
3
+ import { escapeBackslashAndLinksExceptCodeBlock } from './index';
4
+
5
+ /**
6
+ * Convert plain-text markdown to a ProseMirror Slice using the GFM converter.
7
+ *
8
+ * Only called when `platform_editor_paste_as_md_use_gfm` is enabled AND a
9
+ * `markdownToPmConverter` is provided. Returns `undefined` if the converter
10
+ * throws or produces an empty document — the caller will propagate `undefined`
11
+ * without falling back to the legacy MarkdownTransformer.
12
+ */
13
+ export function getMarkdownSliceViaGfm(text, schema, openStart, openEnd, markdownToPmConverter) {
14
+ // GFM parsers (e.g. markdownToAdf) natively recognise raw URLs as autolinks per
15
+ // the CommonMark spec — they do not need the `<url>` angle-bracket wrapping that
16
+ // the legacy MarkdownTransformer requires. Passing skipLinkEscaping: true avoids
17
+ // double-processing URLs and lets the GFM parser handle them correctly.
18
+ var escapedTextInput = escapeBackslashAndLinksExceptCodeBlock(text, {
19
+ skipLinkEscaping: true
20
+ });
21
+ try {
22
+ var doc = schema.nodes.doc.createAndFill({}, markdownToPmConverter({
23
+ markdown: escapedTextInput,
24
+ schema: schema
25
+ }));
26
+ if (doc && doc.content) {
27
+ return new Slice(doc.content, openStart, openEnd);
28
+ }
29
+ } catch (e) {
30
+ void logException(e instanceof Error ? e : new Error(String(e)), {
31
+ location: 'editor-plugin-paste/getMarkdownSlice'
32
+ });
33
+ }
34
+ return undefined;
35
+ }
@@ -120,7 +120,8 @@ export function escapeLinks(text) {
120
120
  * const input = 'This is a link: https://example.com and a backslash: \\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```';
121
121
  * const output = escapeBackslashAndLinksExceptCodeBlock(input); // 'This is a link: <https://example.com> and a backslash: \\\\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```'
122
122
  */
123
- export function escapeBackslashAndLinksExceptCodeBlock(textInput) {
123
+ export function escapeBackslashAndLinksExceptCodeBlock(textInput, options) {
124
+ var shouldEscapeLinks = !(options !== null && options !== void 0 && options.skipLinkEscaping);
124
125
  // ref: https://spec.commonmark.org/0.31.2/#fenced-code-blocks
125
126
  var isInsideCodeBlock = false;
126
127
  var lines = textInput.split('\n');
@@ -142,7 +143,9 @@ export function escapeBackslashAndLinksExceptCodeBlock(textInput) {
142
143
  // Ignored via go/ees005
143
144
  // eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
144
145
  var escaped = line.replace(/\\/g, '\\\\');
145
- escaped = escapeLinks(escaped);
146
+ if (shouldEscapeLinks) {
147
+ escaped = escapeLinks(escaped);
148
+ }
146
149
  return escaped;
147
150
  }
148
151
  }).join('\n');
@@ -66,7 +66,7 @@ var showWarningFlag = function showWarningFlag(_ref) {
66
66
  // Check if rawHtml contains a synced block
67
67
  // example: "<meta charset='utf-8'><html><head></head><body><div data-sync-block=\"\" data-local-id=\"\" data-resource-id=\"d64883c8-1270-431d-a1d3-51d36a1ed5f4\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"syncBlock\" data-prosemirror-node-block=\"true\" data-pm-slice=\"0 0 []\"></div></body></html>"
68
68
  var hasSyncedBlockInRawHtml = function hasSyncedBlockInRawHtml(rawHtml) {
69
- return rawHtml.includes('data-sync-block="');
69
+ return Boolean(rawHtml === null || rawHtml === void 0 ? void 0 : rawHtml.includes('data-sync-block="'));
70
70
  };
71
71
 
72
72
  /**
@@ -1 +1 @@
1
- export type { PastePlugin, PastePluginOptions, PastePluginState, LastContentPasted, PastePluginDependencies, ActiveFlag, } from '../pastePluginType';
1
+ export type { PastePlugin, PastePluginOptions, PastePluginState, LastContentPasted, PastePluginDependencies, ActiveFlag, MarkdownToPmConverter, } from '../pastePluginType';
@@ -12,7 +12,7 @@ import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
12
12
  import type { ListPlugin } from '@atlaskit/editor-plugin-list';
13
13
  import type { MediaPlugin } from '@atlaskit/editor-plugin-media';
14
14
  import type { MentionsPlugin } from '@atlaskit/editor-plugin-mentions';
15
- import type { Slice } from '@atlaskit/editor-prosemirror/model';
15
+ import type { Fragment, Schema, Slice } from '@atlaskit/editor-prosemirror/model';
16
16
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
17
17
  export declare enum FLAG_TYPE {
18
18
  WARNING = "warning",
@@ -62,9 +62,19 @@ export type LastContentPasted = {
62
62
  sourcePastedSlice?: Slice;
63
63
  text?: string;
64
64
  };
65
+ export type MarkdownToPmConverter = (params: {
66
+ markdown: string;
67
+ schema: Schema;
68
+ }) => Fragment;
65
69
  export type PastePluginOptions = {
66
70
  cardOptions?: CardOptions;
67
71
  isFullPage?: boolean;
72
+ /**
73
+ * Optional markdown → ProseMirror fragment converter used for plain-text
74
+ * Cmd+V paste when `platform_editor_paste_as_md_use_gfm` is enabled.
75
+ * When omitted or the experiment is off, the legacy MarkdownTransformer is used.
76
+ */
77
+ markdownToPmConverter?: MarkdownToPmConverter;
68
78
  pasteWarningOptions?: PasteWarningOptions;
69
79
  sanitizePrivateContent?: boolean;
70
80
  };
@@ -8,7 +8,7 @@ import type { ExtractInjectionAPI, FeatureFlags, PasteWarningOptions } from '@at
8
8
  import type { Schema } from '@atlaskit/editor-prosemirror/model';
9
9
  import type { EditorState } from '@atlaskit/editor-prosemirror/state';
10
10
  import type { PastePlugin } from '../index';
11
- import type { PastePluginState } from '../pastePluginType';
11
+ import type { MarkdownToPmConverter, PastePluginState } from '../pastePluginType';
12
12
  export declare const isInsideBlockQuote: (state: EditorState) => boolean;
13
13
  export declare function isSharePointUrl(url: string | undefined): boolean;
14
- export declare function createPlugin(schema: Schema, dispatchAnalyticsEvent: DispatchAnalyticsEvent, dispatch: Dispatch, featureFlags: FeatureFlags, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined, getIntl: () => IntlShape, cardOptions?: CardOptions, sanitizePrivateContent?: boolean, providerFactory?: ProviderFactory, pasteWarningOptions?: PasteWarningOptions): SafePlugin<PastePluginState>;
14
+ export declare function createPlugin(schema: Schema, dispatchAnalyticsEvent: DispatchAnalyticsEvent, dispatch: Dispatch, featureFlags: FeatureFlags, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined, getIntl: () => IntlShape, cardOptions?: CardOptions, sanitizePrivateContent?: boolean, providerFactory?: ProviderFactory, pasteWarningOptions?: PasteWarningOptions, markdownToPmConverter?: MarkdownToPmConverter): SafePlugin<PastePluginState>;
@@ -0,0 +1,14 @@
1
+ import type { Fragment, Schema } from '@atlaskit/editor-prosemirror/model';
2
+ import { Slice } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Convert plain-text markdown to a ProseMirror Slice using the GFM converter.
5
+ *
6
+ * Only called when `platform_editor_paste_as_md_use_gfm` is enabled AND a
7
+ * `markdownToPmConverter` is provided. Returns `undefined` if the converter
8
+ * throws or produces an empty document — the caller will propagate `undefined`
9
+ * without falling back to the legacy MarkdownTransformer.
10
+ */
11
+ export declare function getMarkdownSliceViaGfm(text: string, schema: Schema, openStart: number, openEnd: number, markdownToPmConverter: (params: {
12
+ markdown: string;
13
+ schema: Schema;
14
+ }) => Fragment): Slice | undefined;
@@ -33,7 +33,9 @@ export declare function escapeLinks(text: string): string;
33
33
  * const input = 'This is a link: https://example.com and a backslash: \\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```';
34
34
  * const output = escapeBackslashAndLinksExceptCodeBlock(input); // 'This is a link: <https://example.com> and a backslash: \\\\\n```\ncode block https://example.com not escaped\ncode block \\ not escaped\n```'
35
35
  */
36
- export declare function escapeBackslashAndLinksExceptCodeBlock(textInput: string): string;
36
+ export declare function escapeBackslashAndLinksExceptCodeBlock(textInput: string, options?: {
37
+ skipLinkEscaping?: boolean;
38
+ }): string;
37
39
  export declare function hasOnlyNodesOfType(...nodeTypes: NodeType[]): (slice: Slice) => boolean;
38
40
  export declare function applyTextMarksToSlice(schema: Schema, marks?: readonly Mark[]): (slice: Slice) => Slice;
39
41
  export declare function isEmptyNode(node: PMNode | null | undefined): boolean | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "15.0.2",
3
+ "version": "15.1.1",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "@atlaskit/editor-plugin-feature-flags": "^13.0.0",
32
32
  "@atlaskit/editor-plugin-list": "^16.0.0",
33
33
  "@atlaskit/editor-plugin-media": "^16.0.0",
34
- "@atlaskit/editor-plugin-mentions": "^16.0.0",
34
+ "@atlaskit/editor-plugin-mentions": "^16.1.0",
35
35
  "@atlaskit/editor-prosemirror": "^8.0.0",
36
36
  "@atlaskit/editor-tables": "^3.0.0",
37
37
  "@atlaskit/flag": "^18.2.0",
@@ -42,14 +42,14 @@
42
42
  "@atlaskit/platform-feature-experiments": "^0.3.0",
43
43
  "@atlaskit/platform-feature-flags": "^2.1.0",
44
44
  "@atlaskit/prosemirror-history": "^1.1.0",
45
- "@atlaskit/tmp-editor-statsig": "^144.0.0",
46
- "@atlaskit/tokens": "^16.4.0",
45
+ "@atlaskit/tmp-editor-statsig": "^145.0.0",
46
+ "@atlaskit/tokens": "^16.5.0",
47
47
  "@babel/runtime": "^7.0.0",
48
48
  "lodash": "^4.17.21",
49
49
  "uuid": "^3.1.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@atlaskit/editor-common": "^118.1.0",
52
+ "@atlaskit/editor-common": "^118.2.0",
53
53
  "react": "^18.2.0 || ^19.2.0",
54
54
  "react-dom": "^18.2.0 || ^19.2.0",
55
55
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -116,9 +116,6 @@
116
116
  "platform_media_cross_client_copy_with_auth": {
117
117
  "type": "boolean"
118
118
  },
119
- "platform_synced_block_unsupported_products": {
120
- "type": "boolean"
121
- },
122
119
  "platform_editor_agent_mentions": {
123
120
  "type": "experiment"
124
121
  },