@atlaskit/editor-plugin-paste 15.0.2 → 15.1.0
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 +18 -0
- package/dist/cjs/pastePlugin.js +3 -2
- package/dist/cjs/pm-plugins/main.js +5 -1
- package/dist/cjs/pm-plugins/util/get-markdown-slice.js +40 -0
- package/dist/cjs/pm-plugins/util/index.js +5 -2
- package/dist/es2019/pastePlugin.js +3 -2
- package/dist/es2019/pm-plugins/main.js +5 -1
- package/dist/es2019/pm-plugins/util/get-markdown-slice.js +35 -0
- package/dist/es2019/pm-plugins/util/index.js +5 -2
- package/dist/esm/pastePlugin.js +3 -2
- package/dist/esm/pm-plugins/main.js +5 -1
- package/dist/esm/pm-plugins/util/get-markdown-slice.js +35 -0
- package/dist/esm/pm-plugins/util/index.js +5 -2
- package/dist/types/entry-points/paste-plugin-type.d.ts +1 -1
- package/dist/types/pastePluginType.d.ts +11 -1
- package/dist/types/pm-plugins/main.d.ts +2 -2
- package/dist/types/pm-plugins/util/get-markdown-slice.d.ts +14 -0
- package/dist/types/pm-plugins/util/index.d.ts +3 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste
|
|
2
2
|
|
|
3
|
+
## 15.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`a006cb2cc79f9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a006cb2cc79f9) -
|
|
8
|
+
Use the GFM markdown transformer for Cmd+V plain-text paste when
|
|
9
|
+
`platform_editor_paste_as_md_use_gfm` is enabled, matching the existing paste-as-markdown
|
|
10
|
+
(Shift+Cmd+V) path.
|
|
11
|
+
|
|
12
|
+
Consumers can inject a converter via `PastePluginOptions.markdownToPmConverter` and gate usage
|
|
13
|
+
with:
|
|
14
|
+
|
|
15
|
+
`expValEquals('platform_editor_paste_as_md_use_gfm', 'isEnabled', true)`.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
|
|
3
21
|
## 15.0.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/cjs/pastePlugin.js
CHANGED
|
@@ -20,7 +20,8 @@ var pastePlugin = exports.pastePlugin = function pastePlugin(_ref) {
|
|
|
20
20
|
cardOptions = _ref2.cardOptions,
|
|
21
21
|
sanitizePrivateContent = _ref2.sanitizePrivateContent,
|
|
22
22
|
isFullPage = _ref2.isFullPage,
|
|
23
|
-
pasteWarningOptions = _ref2.pasteWarningOptions
|
|
23
|
+
pasteWarningOptions = _ref2.pasteWarningOptions,
|
|
24
|
+
markdownToPmConverter = _ref2.markdownToPmConverter;
|
|
24
25
|
var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
|
|
25
26
|
var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
26
27
|
return {
|
|
@@ -34,7 +35,7 @@ var pastePlugin = exports.pastePlugin = function pastePlugin(_ref) {
|
|
|
34
35
|
dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
|
|
35
36
|
dispatch = _ref3.dispatch,
|
|
36
37
|
getIntl = _ref3.getIntl;
|
|
37
|
-
return (0, _main.createPlugin)(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions);
|
|
38
|
+
return (0, _main.createPlugin)(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter);
|
|
38
39
|
}
|
|
39
40
|
}, {
|
|
40
41
|
name: 'moveAnalyticsPlugin',
|
|
@@ -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) {
|
|
@@ -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
|
-
|
|
166
|
+
if (shouldEscapeLinks) {
|
|
167
|
+
escaped = escapeLinks(escaped);
|
|
168
|
+
}
|
|
166
169
|
return escaped;
|
|
167
170
|
}
|
|
168
171
|
}).join('\n');
|
|
@@ -14,7 +14,8 @@ export const pastePlugin = ({
|
|
|
14
14
|
cardOptions,
|
|
15
15
|
sanitizePrivateContent,
|
|
16
16
|
isFullPage,
|
|
17
|
-
pasteWarningOptions
|
|
17
|
+
pasteWarningOptions,
|
|
18
|
+
markdownToPmConverter
|
|
18
19
|
} = config !== null && config !== void 0 ? config : {};
|
|
19
20
|
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
21
|
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 +30,7 @@ export const pastePlugin = ({
|
|
|
29
30
|
dispatchAnalyticsEvent,
|
|
30
31
|
dispatch,
|
|
31
32
|
getIntl
|
|
32
|
-
}) => createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions)
|
|
33
|
+
}) => createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter)
|
|
33
34
|
}, {
|
|
34
35
|
name: 'moveAnalyticsPlugin',
|
|
35
36
|
plugin: ({
|
|
@@ -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) {
|
|
@@ -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
|
-
|
|
145
|
+
if (shouldEscapeLinks) {
|
|
146
|
+
escaped = escapeLinks(escaped);
|
|
147
|
+
}
|
|
145
148
|
return escaped;
|
|
146
149
|
}
|
|
147
150
|
}).join('\n');
|
package/dist/esm/pastePlugin.js
CHANGED
|
@@ -13,7 +13,8 @@ export var pastePlugin = function pastePlugin(_ref) {
|
|
|
13
13
|
cardOptions = _ref2.cardOptions,
|
|
14
14
|
sanitizePrivateContent = _ref2.sanitizePrivateContent,
|
|
15
15
|
isFullPage = _ref2.isFullPage,
|
|
16
|
-
pasteWarningOptions = _ref2.pasteWarningOptions
|
|
16
|
+
pasteWarningOptions = _ref2.pasteWarningOptions,
|
|
17
|
+
markdownToPmConverter = _ref2.markdownToPmConverter;
|
|
17
18
|
var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
|
|
18
19
|
var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
19
20
|
return {
|
|
@@ -27,7 +28,7 @@ export var pastePlugin = function pastePlugin(_ref) {
|
|
|
27
28
|
dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
|
|
28
29
|
dispatch = _ref3.dispatch,
|
|
29
30
|
getIntl = _ref3.getIntl;
|
|
30
|
-
return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions);
|
|
31
|
+
return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions, markdownToPmConverter);
|
|
31
32
|
}
|
|
32
33
|
}, {
|
|
33
34
|
name: 'moveAnalyticsPlugin',
|
|
@@ -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) {
|
|
@@ -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
|
-
|
|
146
|
+
if (shouldEscapeLinks) {
|
|
147
|
+
escaped = escapeLinks(escaped);
|
|
148
|
+
}
|
|
146
149
|
return escaped;
|
|
147
150
|
}
|
|
148
151
|
}).join('\n');
|
|
@@ -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
|
|
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
|
|
3
|
+
"version": "15.1.0",
|
|
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.
|
|
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": "^
|
|
46
|
-
"@atlaskit/tokens": "^16.
|
|
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.
|
|
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"
|