@ckeditor/ckeditor5-markdown-gfm 48.2.0 → 48.3.0-alpha.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/dist/index.js CHANGED
@@ -2,438 +2,441 @@
2
2
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
- import { Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { HtmlDataProcessor } from '@ckeditor/ckeditor5-engine/dist/index.js';
7
- import { unified } from 'unified';
8
- import remarkGfm from 'remark-gfm';
9
- import remarkParse from 'remark-parse';
10
- import remarkRehype from 'remark-rehype';
11
- import remarkBreaks from 'remark-breaks';
12
- import rehypeStringify from 'rehype-dom-stringify';
13
- import { visit } from 'unist-util-visit';
14
- import { toHtml } from 'hast-util-to-html';
15
- import { fromDom } from 'hast-util-from-dom';
16
- import rehypeParse from 'rehype-dom-parse';
17
- import rehypeRemark from 'rehype-remark';
18
- import remarkStringify from 'remark-stringify';
19
- import { h } from 'hastscript';
20
- import { ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard/dist/index.js';
5
+ import { Plugin } from "@ckeditor/ckeditor5-core";
6
+ import { HtmlDataProcessor } from "@ckeditor/ckeditor5-engine";
7
+ import { unified } from "unified";
8
+ import remarkGfm from "remark-gfm";
9
+ import remarkParse from "remark-parse";
10
+ import remarkRehype from "remark-rehype";
11
+ import remarkBreaks from "remark-breaks";
12
+ import rehypeStringify from "rehype-dom-stringify";
13
+ import { visit } from "unist-util-visit";
14
+ import { toHtml } from "hast-util-to-html";
15
+ import { fromDom } from "hast-util-from-dom";
16
+ import rehypeParse from "rehype-dom-parse";
17
+ import rehypeRemark from "rehype-remark";
18
+ import remarkStringify from "remark-stringify";
19
+ import { h } from "hastscript";
20
+ import { ClipboardPipeline } from "@ckeditor/ckeditor5-clipboard";
21
21
 
22
22
  /**
23
- * The default `unified()` plugin chain used by {@link module:markdown-gfm/markdown2html/markdown2html~MarkdownGfmMdToHtml}.
24
- * This object is frozen and must not be mutated. Pass a copy to the constructor if you need to customize the plugin chain.
25
- *
26
- * Learn more about the `unified()` plugin chain in the [unified](https://github.com/unifiedjs/unified) documentation.
27
- */ const MarkdownGfmMdToHtmlDefaultPlugins = Object.freeze({
28
- // Parses Markdown to an abstract syntax tree (AST).
29
- remarkParse,
30
- // Adds support for GitHub Flavored Markdown (GFM).
31
- remarkGfm: [
32
- remarkGfm,
33
- {
34
- singleTilde: true
35
- }
36
- ],
37
- // Replaces line breaks with `<br>` tags.
38
- remarkBreaks,
39
- // Turns markdown syntax tree to HTML syntax tree, ignoring embedded HTML.
40
- remarkRehype: [
41
- remarkRehype,
42
- {
43
- allowDangerousHtml: true
44
- }
45
- ],
46
- // Handles HTML embedded in Markdown.
47
- rehypeDomRaw,
48
- // Removes classes from list elements.
49
- deleteClassesFromToDoLists,
50
- // Serializes HTML syntax tree to HTML string.
51
- rehypeStringify
23
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
24
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
25
+ */
26
+ /**
27
+ * @module markdown-gfm/markdown2html/markdown2html
28
+ */
29
+ /**
30
+ * The default `unified()` plugin chain used by {@link module:markdown-gfm/markdown2html/markdown2html~MarkdownGfmMdToHtml}.
31
+ * This object is frozen and must not be mutated. Pass a copy to the constructor if you need to customize the plugin chain.
32
+ *
33
+ * Learn more about the `unified()` plugin chain in the [unified](https://github.com/unifiedjs/unified) documentation.
34
+ */
35
+ const MarkdownGfmMdToHtmlDefaultPlugins = Object.freeze({
36
+ remarkParse,
37
+ remarkGfm: [remarkGfm, { singleTilde: true }],
38
+ remarkBreaks,
39
+ remarkRehype: [remarkRehype, { allowDangerousHtml: true }],
40
+ rehypeDomRaw,
41
+ deleteClassesFromToDoLists,
42
+ rehypeStringify
52
43
  });
53
44
  /**
54
- * This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert Markdown to HTML.
55
- */ class MarkdownGfmMdToHtml {
56
- _processor;
57
- /**
58
- * Creates a new instance of MarkdownGfmMdToHtml.
59
- * @param {Object} options - The options for the MarkdownGfmMdToHtml instance.
60
- * @param {Record<string, Pluggable>} options.plugins - The plugins to be used by the `unified().use()` processor for converting
61
- * Markdown to HTML. By default, {@link ~MarkdownGfmMdToHtmlDefaultPlugins} is used.
62
- * You can override the defaults by passing your own plugins.
63
- *
64
- * Learn more about the `unified()` plugin chain in the [unified](https://github.com/unifiedjs/unified) documentation.
65
- */ constructor({ plugins = MarkdownGfmMdToHtmlDefaultPlugins } = {}){
66
- this._processor = unified().use({
67
- plugins: Object.values(plugins)
68
- });
69
- }
70
- parse(markdown) {
71
- return this._processor.processSync(markdown).toString().replaceAll('\n</code>', '</code>');
72
- }
73
- }
45
+ * This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert Markdown to HTML.
46
+ */
47
+ var MarkdownGfmMdToHtml = class {
48
+ _processor;
49
+ /**
50
+ * Creates a new instance of MarkdownGfmMdToHtml.
51
+ * @param {Object} options - The options for the MarkdownGfmMdToHtml instance.
52
+ * @param {Record<string, Pluggable>} options.plugins - The plugins to be used by the `unified().use()` processor for converting
53
+ * Markdown to HTML. By default, {@link ~MarkdownGfmMdToHtmlDefaultPlugins} is used.
54
+ * You can override the defaults by passing your own plugins.
55
+ *
56
+ * Learn more about the `unified()` plugin chain in the [unified](https://github.com/unifiedjs/unified) documentation.
57
+ */
58
+ constructor({ plugins = MarkdownGfmMdToHtmlDefaultPlugins } = {}) {
59
+ this._processor = unified().use({ plugins: Object.values(plugins) });
60
+ }
61
+ parse(markdown) {
62
+ return this._processor.processSync(markdown).toString().replaceAll("\n</code>", "</code>");
63
+ }
64
+ };
74
65
  /**
75
- * Rehype plugin that improves handling of the To-do lists by removing:
76
- * * default classes added to `<ul>`, `<ol>`, and `<li>` elements.
77
- * * bogus space after <input type="checkbox"> because it would be preserved by ViewDomConverter as it's next to an inline object.
78
- */ function deleteClassesFromToDoLists() {
79
- return (tree)=>{
80
- visit(tree, 'element', (node)=>{
81
- if (node.tagName === 'ul' || node.tagName === 'ol' || node.tagName === 'li') {
82
- node.children = node.children.filter((child)=>child.type !== 'text' || !!child.value.trim());
83
- delete node.properties.className;
84
- }
85
- });
86
- };
66
+ * Rehype plugin that improves handling of the To-do lists by removing:
67
+ * * default classes added to `<ul>`, `<ol>`, and `<li>` elements.
68
+ * * bogus space after <input type="checkbox"> because it would be preserved by ViewDomConverter as it's next to an inline object.
69
+ */
70
+ function deleteClassesFromToDoLists() {
71
+ return (tree) => {
72
+ visit(tree, "element", (node) => {
73
+ if (node.tagName === "ul" || node.tagName === "ol" || node.tagName === "li") {
74
+ node.children = node.children.filter((child) => child.type !== "text" || !!child.value.trim());
75
+ delete node.properties.className;
76
+ }
77
+ });
78
+ };
87
79
  }
88
80
  /**
89
- * Rehype plugin to parse raw HTML nodes inside Markdown. This plugin is used instead of `rehype-raw` or `rehype-stringify`,
90
- * because those plugins rely on `parse5` DOM parser which is heavy and redundant in the browser environment where we can
91
- * use the native DOM APIs.
92
- *
93
- * This plugins finds any node (root or element) whose children include `raw` nodes and reparses them like so:
94
- * 1. Serializes its children to an HTML string.
95
- * 2. Reparses the HTML string using a `<template>` element.
96
- * 3. Converts each parsed DOM node back into HAST nodes.
97
- * 4. Replaces the original children with the newly created HAST nodes.
98
- */ function rehypeDomRaw() {
99
- return (tree)=>{
100
- visit(tree, [
101
- 'root',
102
- 'element'
103
- ], (node)=>{
104
- /* istanbul ignore next -- @preserve */ if (!isNodeRootOrElement(node)) {
105
- return;
106
- }
107
- // Only act on nodes with at least one raw child.
108
- if (!node.children.some((child)=>child.type === 'raw')) {
109
- return;
110
- }
111
- const template = document.createElement('template');
112
- // Serialize all children to an HTML fragment.
113
- template.innerHTML = toHtml({
114
- type: 'root',
115
- children: node.children
116
- }, {
117
- allowDangerousHtml: true
118
- });
119
- // Convert each parsed DOM node back into HAST and replace the original children.
120
- node.children = Array.from(template.content.childNodes).map((domNode)=>fromDom(domNode));
121
- });
122
- };
81
+ * Rehype plugin to parse raw HTML nodes inside Markdown. This plugin is used instead of `rehype-raw` or `rehype-stringify`,
82
+ * because those plugins rely on `parse5` DOM parser which is heavy and redundant in the browser environment where we can
83
+ * use the native DOM APIs.
84
+ *
85
+ * This plugins finds any node (root or element) whose children include `raw` nodes and reparses them like so:
86
+ * 1. Serializes its children to an HTML string.
87
+ * 2. Reparses the HTML string using a `<template>` element.
88
+ * 3. Converts each parsed DOM node back into HAST nodes.
89
+ * 4. Replaces the original children with the newly created HAST nodes.
90
+ */
91
+ function rehypeDomRaw() {
92
+ return (tree) => {
93
+ visit(tree, ["root", "element"], (node) => {
94
+ /* v8 ignore next -- @preserve */
95
+ if (!isNodeRootOrElement(node)) return;
96
+ if (!node.children.some((child) => child.type === "raw")) return;
97
+ const template = document.createElement("template");
98
+ template.innerHTML = toHtml({
99
+ type: "root",
100
+ children: node.children
101
+ }, { allowDangerousHtml: true });
102
+ node.children = Array.from(template.content.childNodes).map((domNode) => fromDom(domNode));
103
+ });
104
+ };
123
105
  }
124
106
  /**
125
- * Only needed for the type guard.
126
- */ function isNodeRootOrElement(node) {
127
- return (node.type === 'root' || node.type === 'element') && node.children;
107
+ * Only needed for the type guard.
108
+ */
109
+ function isNodeRootOrElement(node) {
110
+ return (node.type === "root" || node.type === "element") && node.children;
128
111
  }
129
112
 
130
- class MarkdownGfmHtmlToMd {
131
- _processor;
132
- _keepRawTags = [];
133
- constructor(){
134
- this._buildProcessor();
135
- }
136
- keep(tagName) {
137
- this._keepRawTags.push(tagName.toLowerCase());
138
- this._buildProcessor();
139
- }
140
- parse(html) {
141
- return this._processor.processSync(html).toString().trim();
142
- }
143
- /**
144
- * Returns handlers for raw HTML tags that should be kept in the Markdown output.
145
- */ _getRawTagsHandlers() {
146
- return this._keepRawTags.reduce((handlers, tagName)=>{
147
- handlers[tagName] = (state, node)=>{
148
- const tag = toHtml(h(node.tagName, node.properties), {
149
- allowDangerousHtml: true,
150
- closeSelfClosing: true
151
- });
152
- const endOfOpeningTagIndex = tag.indexOf('>');
153
- const openingTag = tag.slice(0, endOfOpeningTagIndex + 1);
154
- const closingTag = tag.slice(endOfOpeningTagIndex + 1);
155
- return [
156
- {
157
- type: 'html',
158
- value: openingTag
159
- },
160
- ...state.all(node),
161
- {
162
- type: 'html',
163
- value: closingTag
164
- }
165
- ];
166
- };
167
- return handlers;
168
- }, {});
169
- }
170
- _buildProcessor() {
171
- this._processor = unified()// Parse HTML to an abstract syntax tree (AST).
172
- .use(rehypeParse)// Removes `<label>` element from TODO lists.
173
- .use(removeLabelFromCheckboxes)// Turns HTML syntax tree into Markdown syntax tree.
174
- .use(rehypeRemark, {
175
- // Keeps allowed HTML tags.
176
- handlers: this._getRawTagsHandlers()
177
- })// Adds support for GitHub Flavored Markdown (GFM).
178
- .use(remarkGfm, {
179
- singleTilde: true
180
- })// Replaces line breaks with `<br>` tags.
181
- .use(remarkBreaks)// Serializes Markdown syntax tree to Markdown string.
182
- .use(remarkStringify, {
183
- resourceLink: true,
184
- emphasis: '_',
185
- rule: '-',
186
- handlers: {
187
- break: ()=>'\n'
188
- },
189
- unsafe: [
190
- {
191
- character: '<'
192
- }
193
- ]
194
- });
195
- }
196
- }
197
113
  /**
198
- * Removes `<label>` element from TODO lists, so that `<input>` and `text` are direct children of `<li>`.
199
- */ function removeLabelFromCheckboxes() {
200
- return function(tree) {
201
- visit(tree, 'element', (node, index, parent)=>{
202
- if (index !== null && node.tagName === 'label' && parent.type === 'element' && parent.tagName === 'li') {
203
- parent.children.splice(index, 1, ...node.children);
204
- }
205
- });
206
- };
114
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
115
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
116
+ */
117
+ /**
118
+ * @module markdown-gfm/html2markdown/html2markdown
119
+ */
120
+ var MarkdownGfmHtmlToMd = class {
121
+ _processor;
122
+ _keepRawTags = [];
123
+ constructor() {
124
+ this._buildProcessor();
125
+ }
126
+ keep(tagName) {
127
+ this._keepRawTags.push(tagName.toLowerCase());
128
+ this._buildProcessor();
129
+ }
130
+ parse(html) {
131
+ return this._processor.processSync(html).toString().trim();
132
+ }
133
+ /**
134
+ * Returns handlers for raw HTML tags that should be kept in the Markdown output.
135
+ */
136
+ _getRawTagsHandlers() {
137
+ return this._keepRawTags.reduce((handlers, tagName) => {
138
+ handlers[tagName] = (state, node) => {
139
+ const tag = toHtml(h(node.tagName, node.properties), {
140
+ allowDangerousHtml: true,
141
+ closeSelfClosing: true
142
+ });
143
+ const endOfOpeningTagIndex = tag.indexOf(">");
144
+ const openingTag = tag.slice(0, endOfOpeningTagIndex + 1);
145
+ const closingTag = tag.slice(endOfOpeningTagIndex + 1);
146
+ return [
147
+ {
148
+ type: "html",
149
+ value: openingTag
150
+ },
151
+ ...state.all(node),
152
+ {
153
+ type: "html",
154
+ value: closingTag
155
+ }
156
+ ];
157
+ };
158
+ return handlers;
159
+ }, {});
160
+ }
161
+ _buildProcessor() {
162
+ this._processor = unified().use(rehypeParse).use(removeLabelFromCheckboxes).use(rehypeRemark, { handlers: this._getRawTagsHandlers() }).use(remarkGfm, { singleTilde: true }).use(remarkBreaks).use(remarkStringify, {
163
+ resourceLink: true,
164
+ emphasis: "_",
165
+ rule: "-",
166
+ handlers: { break: () => "\n" },
167
+ unsafe: [{ character: "<" }]
168
+ });
169
+ }
170
+ };
171
+ /**
172
+ * Removes `<label>` element from TODO lists, so that `<input>` and `text` are direct children of `<li>`.
173
+ */
174
+ function removeLabelFromCheckboxes() {
175
+ return function(tree) {
176
+ visit(tree, "element", (node, index, parent) => {
177
+ if (index !== null && node.tagName === "label" && parent.type === "element" && parent.tagName === "li") parent.children.splice(index, 1, ...node.children);
178
+ });
179
+ };
207
180
  }
208
181
 
209
182
  /**
210
- * This data processor implementation uses GitHub Flavored Markdown as input/output data.
211
- *
212
- * See the {@glink features/markdown Markdown output} guide to learn more on how to enable it.
213
- */ class MarkdownGfmDataProcessor {
214
- /**
215
- * HTML data processor used to process HTML produced by the Markdown-to-HTML converter and the other way.
216
- */ _htmlDP;
217
- /**
218
- * Helper for converting Markdown to HTML.
219
- */ _markdown2html;
220
- /**
221
- * Helper for converting HTML to Markdown.
222
- */ _html2markdown;
223
- /**
224
- * Creates a new instance of the Markdown data processor class.
225
- */ constructor(document){
226
- this._htmlDP = new HtmlDataProcessor(document);
227
- this._markdown2html = new MarkdownGfmMdToHtml();
228
- this._html2markdown = new MarkdownGfmHtmlToMd();
229
- }
230
- /**
231
- * Keeps the specified element in the output as HTML. This is useful if the editor contains
232
- * features producing HTML that is not a part of the Markdown standard.
233
- *
234
- * By default, all HTML tags are removed.
235
- *
236
- * @param element The element name to be kept.
237
- */ keepHtml(element) {
238
- this._html2markdown.keep(element);
239
- }
240
- /**
241
- * Converts the provided Markdown string to a view tree.
242
- *
243
- * @param data A Markdown string.
244
- * @returns The converted view element.
245
- */ toView(data) {
246
- const html = this._markdown2html.parse(data);
247
- return this._htmlDP.toView(html);
248
- }
249
- /**
250
- * Converts the provided {@link module:engine/view/documentfragment~ViewDocumentFragment} to data format &ndash; in this
251
- * case to a Markdown string.
252
- *
253
- * @returns Markdown string.
254
- */ toData(viewFragment) {
255
- const html = this._htmlDP.toData(viewFragment);
256
- return this._html2markdown.parse(html);
257
- }
258
- /**
259
- * Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
260
- * and not processed during the conversion from Markdown to view elements.
261
- *
262
- * The raw data can be later accessed by a
263
- * {@link module:engine/view/element~ViewElement#getCustomProperty custom property of a view element} called `"$rawContent"`.
264
- *
265
- * @param pattern The pattern matching all view elements whose content should
266
- * be treated as raw data.
267
- */ registerRawContentMatcher(pattern) {
268
- this._htmlDP.registerRawContentMatcher(pattern);
269
- }
270
- /**
271
- * This method does not have any effect on the data processor result. It exists for compatibility with the
272
- * {@link module:engine/dataprocessor/dataprocessor~DataProcessor `DataProcessor` interface}.
273
- */ useFillerType() {}
274
- }
183
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
184
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
185
+ */
186
+ /**
187
+ * @module markdown-gfm/gfmdataprocessor
188
+ */
189
+ /**
190
+ * This data processor implementation uses GitHub Flavored Markdown as input/output data.
191
+ *
192
+ * See the {@glink features/markdown Markdown output} guide to learn more on how to enable it.
193
+ */
194
+ var MarkdownGfmDataProcessor = class {
195
+ /**
196
+ * HTML data processor used to process HTML produced by the Markdown-to-HTML converter and the other way.
197
+ */
198
+ _htmlDP;
199
+ /**
200
+ * Helper for converting Markdown to HTML.
201
+ */
202
+ _markdown2html;
203
+ /**
204
+ * Helper for converting HTML to Markdown.
205
+ */
206
+ _html2markdown;
207
+ /**
208
+ * Creates a new instance of the Markdown data processor class.
209
+ */
210
+ constructor(document) {
211
+ this._htmlDP = new HtmlDataProcessor(document);
212
+ this._markdown2html = new MarkdownGfmMdToHtml();
213
+ this._html2markdown = new MarkdownGfmHtmlToMd();
214
+ }
215
+ /**
216
+ * Keeps the specified element in the output as HTML. This is useful if the editor contains
217
+ * features producing HTML that is not a part of the Markdown standard.
218
+ *
219
+ * By default, all HTML tags are removed.
220
+ *
221
+ * @param element The element name to be kept.
222
+ */
223
+ keepHtml(element) {
224
+ this._html2markdown.keep(element);
225
+ }
226
+ /**
227
+ * Converts the provided Markdown string to a view tree.
228
+ *
229
+ * @param data A Markdown string.
230
+ * @returns The converted view element.
231
+ */
232
+ toView(data) {
233
+ const html = this._markdown2html.parse(data);
234
+ return this._htmlDP.toView(html);
235
+ }
236
+ /**
237
+ * Converts the provided {@link module:engine/view/documentfragment~ViewDocumentFragment} to data format &ndash; in this
238
+ * case to a Markdown string.
239
+ *
240
+ * @returns Markdown string.
241
+ */
242
+ toData(viewFragment) {
243
+ const html = this._htmlDP.toData(viewFragment);
244
+ return this._html2markdown.parse(html);
245
+ }
246
+ /**
247
+ * Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
248
+ * and not processed during the conversion from Markdown to view elements.
249
+ *
250
+ * The raw data can be later accessed by a
251
+ * {@link module:engine/view/element~ViewElement#getCustomProperty custom property of a view element} called `"$rawContent"`.
252
+ *
253
+ * @param pattern The pattern matching all view elements whose content should
254
+ * be treated as raw data.
255
+ */
256
+ registerRawContentMatcher(pattern) {
257
+ this._htmlDP.registerRawContentMatcher(pattern);
258
+ }
259
+ /**
260
+ * This method does not have any effect on the data processor result. It exists for compatibility with the
261
+ * {@link module:engine/dataprocessor/dataprocessor~DataProcessor `DataProcessor` interface}.
262
+ */
263
+ useFillerType() {}
264
+ };
275
265
 
276
266
  /**
277
- * The GitHub Flavored Markdown (GFM) plugin.
278
- *
279
- * For a detailed overview, check the {@glink features/markdown Markdown feature} guide.
280
- */ class Markdown extends Plugin {
281
- /**
282
- * @inheritDoc
283
- */ constructor(editor){
284
- super(editor);
285
- editor.data.processor = new MarkdownGfmDataProcessor(editor.data.viewDocument);
286
- }
287
- /**
288
- * @inheritDoc
289
- */ static get pluginName() {
290
- return 'Markdown';
291
- }
292
- /**
293
- * @inheritDoc
294
- */ static get isOfficialPlugin() {
295
- return true;
296
- }
297
- }
267
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
268
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
269
+ */
270
+ /**
271
+ * @module markdown-gfm/markdown
272
+ */
273
+ /**
274
+ * The GitHub Flavored Markdown (GFM) plugin.
275
+ *
276
+ * For a detailed overview, check the {@glink features/markdown Markdown feature} guide.
277
+ */
278
+ var Markdown = class extends Plugin {
279
+ /**
280
+ * @inheritDoc
281
+ */
282
+ constructor(editor) {
283
+ super(editor);
284
+ editor.data.processor = new MarkdownGfmDataProcessor(editor.data.viewDocument);
285
+ }
286
+ /**
287
+ * @inheritDoc
288
+ */
289
+ static get pluginName() {
290
+ return "Markdown";
291
+ }
292
+ /**
293
+ * @inheritDoc
294
+ */
295
+ static get isOfficialPlugin() {
296
+ return true;
297
+ }
298
+ };
298
299
 
300
+ /**
301
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
302
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
303
+ */
304
+ /**
305
+ * @module markdown-gfm/pastefrommarkdownexperimental
306
+ */
299
307
  const ALLOWED_MARKDOWN_FIRST_LEVEL_TAGS = [
300
- 'SPAN',
301
- 'BR',
302
- 'PRE',
303
- 'CODE'
308
+ "SPAN",
309
+ "BR",
310
+ "PRE",
311
+ "CODE"
304
312
  ];
305
313
  /**
306
- * The GitHub Flavored Markdown (GFM) paste plugin.
307
- *
308
- * For a detailed overview, check the {@glink features/pasting/paste-markdown Paste Markdown feature} guide.
309
- */ class PasteFromMarkdownExperimental extends Plugin {
310
- /**
311
- * @internal
312
- */ _gfmDataProcessor;
313
- /**
314
- * @inheritDoc
315
- */ constructor(editor){
316
- super(editor);
317
- this._gfmDataProcessor = new MarkdownGfmDataProcessor(editor.data.viewDocument);
318
- }
319
- /**
320
- * @inheritDoc
321
- */ static get pluginName() {
322
- return 'PasteFromMarkdownExperimental';
323
- }
324
- /**
325
- * @inheritDoc
326
- */ static get isOfficialPlugin() {
327
- return true;
328
- }
329
- /**
330
- * @inheritDoc
331
- */ static get requires() {
332
- return [
333
- ClipboardPipeline
334
- ];
335
- }
336
- /**
337
- * @inheritDoc
338
- */ init() {
339
- const editor = this.editor;
340
- const view = editor.editing.view;
341
- const viewDocument = view.document;
342
- const clipboardPipeline = editor.plugins.get('ClipboardPipeline');
343
- let shiftPressed = false;
344
- this.listenTo(viewDocument, 'keydown', (evt, data)=>{
345
- shiftPressed = data.shiftKey;
346
- });
347
- this.listenTo(clipboardPipeline, 'inputTransformation', (evt, data)=>{
348
- if (shiftPressed) {
349
- return;
350
- }
351
- const dataAsTextHtml = data.dataTransfer.getData('text/html');
352
- if (!dataAsTextHtml) {
353
- const dataAsTextPlain = data.dataTransfer.getData('text/plain');
354
- data.content = this._gfmDataProcessor.toView(dataAsTextPlain);
355
- return;
356
- }
357
- const markdownFromHtml = this._parseMarkdownFromHtml(dataAsTextHtml);
358
- if (markdownFromHtml) {
359
- data.content = this._gfmDataProcessor.toView(markdownFromHtml);
360
- }
361
- });
362
- }
363
- /**
364
- * Determines if the code copied from a website in the `text/html` type can be parsed as Markdown.
365
- * It removes any OS-specific HTML tags, for example, <meta> on macOS and <!--StartFragment--> on Windows.
366
- * Then removes a single wrapper HTML tag or wrappers for sibling tags, and if there are no more tags left,
367
- * returns the remaining text. Returns null if there are any remaining HTML tags detected.
368
- *
369
- * @param htmlString Clipboard content in the `text/html` type format.
370
- */ _parseMarkdownFromHtml(htmlString) {
371
- const withoutOsSpecificTags = this._removeOsSpecificTags(htmlString);
372
- if (!this._containsOnlyAllowedFirstLevelTags(withoutOsSpecificTags)) {
373
- return null;
374
- }
375
- const withoutWrapperTag = this._removeFirstLevelWrapperTagsAndBrs(withoutOsSpecificTags);
376
- if (this._containsAnyRemainingHtmlTags(withoutWrapperTag)) {
377
- return null;
378
- }
379
- return this._replaceHtmlReservedEntitiesWithCharacters(withoutWrapperTag);
380
- }
381
- /**
382
- * Removes OS-specific tags.
383
- *
384
- * @param htmlString Clipboard content in the `text/html` type format.
385
- */ _removeOsSpecificTags(htmlString) {
386
- // Removing the <meta> tag present on Mac.
387
- const withoutMetaTag = htmlString.replace(/^<meta\b[^>]*>/, '').trim();
388
- // Removing the <html> tag present on Windows.
389
- const withoutHtmlTag = withoutMetaTag.replace(/^<html>/, '').replace(/<\/html>$/, '').trim();
390
- // Removing the <body> tag present on Windows.
391
- const withoutBodyTag = withoutHtmlTag.replace(/^<body>/, '').replace(/<\/body>$/, '').trim();
392
- // Removing the <!--StartFragment--> tag present on Windows.
393
- return withoutBodyTag.replace(/^<!--StartFragment-->/, '').replace(/<!--EndFragment-->$/, '').trim();
394
- }
395
- /**
396
- * If the input HTML string contains any first-level formatting tags
397
- * like <b>, <strong>, or <i>, we should not treat it as Markdown.
398
- *
399
- * @param htmlString Clipboard content.
400
- */ _containsOnlyAllowedFirstLevelTags(htmlString) {
401
- const parser = new DOMParser();
402
- const { body: tempElement } = parser.parseFromString(htmlString, 'text/html');
403
- const tagNames = Array.from(tempElement.children).map((el)=>el.tagName);
404
- return tagNames.every((el)=>ALLOWED_MARKDOWN_FIRST_LEVEL_TAGS.includes(el));
405
- }
406
- /**
407
- * Removes multiple HTML wrapper tags from a list of sibling HTML tags.
408
- *
409
- * @param htmlString Clipboard content without any OS-specific tags.
410
- */ _removeFirstLevelWrapperTagsAndBrs(htmlString) {
411
- const parser = new DOMParser();
412
- const { body: tempElement } = parser.parseFromString(htmlString, 'text/html');
413
- const brElements = tempElement.querySelectorAll('br');
414
- for (const br of brElements){
415
- br.replaceWith('\n');
416
- }
417
- const outerElements = tempElement.querySelectorAll(':scope > *');
418
- for (const element of outerElements){
419
- const elementClone = element.cloneNode(true);
420
- element.replaceWith(...elementClone.childNodes);
421
- }
422
- return tempElement.innerHTML;
423
- }
424
- /**
425
- * Determines if a string contains any HTML tags.
426
- */ _containsAnyRemainingHtmlTags(str) {
427
- return str.includes('<');
428
- }
429
- /**
430
- * Replaces the reserved HTML entities with the actual characters.
431
- *
432
- * @param htmlString Clipboard content without any tags.
433
- */ _replaceHtmlReservedEntitiesWithCharacters(htmlString) {
434
- return htmlString.replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&nbsp;/g, ' ');
435
- }
436
- }
314
+ * The GitHub Flavored Markdown (GFM) paste plugin.
315
+ *
316
+ * For a detailed overview, check the {@glink features/pasting/paste-markdown Paste Markdown feature} guide.
317
+ */
318
+ var PasteFromMarkdownExperimental = class extends Plugin {
319
+ /**
320
+ * @internal
321
+ */
322
+ _gfmDataProcessor;
323
+ /**
324
+ * @inheritDoc
325
+ */
326
+ constructor(editor) {
327
+ super(editor);
328
+ this._gfmDataProcessor = new MarkdownGfmDataProcessor(editor.data.viewDocument);
329
+ }
330
+ /**
331
+ * @inheritDoc
332
+ */
333
+ static get pluginName() {
334
+ return "PasteFromMarkdownExperimental";
335
+ }
336
+ /**
337
+ * @inheritDoc
338
+ */
339
+ static get isOfficialPlugin() {
340
+ return true;
341
+ }
342
+ /**
343
+ * @inheritDoc
344
+ */
345
+ static get requires() {
346
+ return [ClipboardPipeline];
347
+ }
348
+ /**
349
+ * @inheritDoc
350
+ */
351
+ init() {
352
+ const editor = this.editor;
353
+ const viewDocument = editor.editing.view.document;
354
+ const clipboardPipeline = editor.plugins.get("ClipboardPipeline");
355
+ let shiftPressed = false;
356
+ this.listenTo(viewDocument, "keydown", (evt, data) => {
357
+ shiftPressed = data.shiftKey;
358
+ });
359
+ this.listenTo(clipboardPipeline, "inputTransformation", (evt, data) => {
360
+ if (shiftPressed) return;
361
+ const dataAsTextHtml = data.dataTransfer.getData("text/html");
362
+ if (!dataAsTextHtml) {
363
+ const dataAsTextPlain = data.dataTransfer.getData("text/plain");
364
+ data.content = this._gfmDataProcessor.toView(dataAsTextPlain);
365
+ return;
366
+ }
367
+ const markdownFromHtml = this._parseMarkdownFromHtml(dataAsTextHtml);
368
+ if (markdownFromHtml) data.content = this._gfmDataProcessor.toView(markdownFromHtml);
369
+ });
370
+ }
371
+ /**
372
+ * Determines if the code copied from a website in the `text/html` type can be parsed as Markdown.
373
+ * It removes any OS-specific HTML tags, for example, <meta> on macOS and <!--StartFragment--> on Windows.
374
+ * Then removes a single wrapper HTML tag or wrappers for sibling tags, and if there are no more tags left,
375
+ * returns the remaining text. Returns null if there are any remaining HTML tags detected.
376
+ *
377
+ * @param htmlString Clipboard content in the `text/html` type format.
378
+ */
379
+ _parseMarkdownFromHtml(htmlString) {
380
+ const withoutOsSpecificTags = this._removeOsSpecificTags(htmlString);
381
+ if (!this._containsOnlyAllowedFirstLevelTags(withoutOsSpecificTags)) return null;
382
+ const withoutWrapperTag = this._removeFirstLevelWrapperTagsAndBrs(withoutOsSpecificTags);
383
+ if (this._containsAnyRemainingHtmlTags(withoutWrapperTag)) return null;
384
+ return this._replaceHtmlReservedEntitiesWithCharacters(withoutWrapperTag);
385
+ }
386
+ /**
387
+ * Removes OS-specific tags.
388
+ *
389
+ * @param htmlString Clipboard content in the `text/html` type format.
390
+ */
391
+ _removeOsSpecificTags(htmlString) {
392
+ return htmlString.replace(/^<meta\b[^>]*>/, "").trim().replace(/^<html>/, "").replace(/<\/html>$/, "").trim().replace(/^<body>/, "").replace(/<\/body>$/, "").trim().replace(/^<!--StartFragment-->/, "").replace(/<!--EndFragment-->$/, "").trim();
393
+ }
394
+ /**
395
+ * If the input HTML string contains any first-level formatting tags
396
+ * like <b>, <strong>, or <i>, we should not treat it as Markdown.
397
+ *
398
+ * @param htmlString Clipboard content.
399
+ */
400
+ _containsOnlyAllowedFirstLevelTags(htmlString) {
401
+ const { body: tempElement } = new DOMParser().parseFromString(htmlString, "text/html");
402
+ return Array.from(tempElement.children).map((el) => el.tagName).every((el) => ALLOWED_MARKDOWN_FIRST_LEVEL_TAGS.includes(el));
403
+ }
404
+ /**
405
+ * Removes multiple HTML wrapper tags from a list of sibling HTML tags.
406
+ *
407
+ * @param htmlString Clipboard content without any OS-specific tags.
408
+ */
409
+ _removeFirstLevelWrapperTagsAndBrs(htmlString) {
410
+ const { body: tempElement } = new DOMParser().parseFromString(htmlString, "text/html");
411
+ const brElements = tempElement.querySelectorAll("br");
412
+ for (const br of brElements) br.replaceWith("\n");
413
+ const outerElements = tempElement.querySelectorAll(":scope > *");
414
+ for (const element of outerElements) {
415
+ const elementClone = element.cloneNode(true);
416
+ element.replaceWith(...elementClone.childNodes);
417
+ }
418
+ return tempElement.innerHTML;
419
+ }
420
+ /**
421
+ * Determines if a string contains any HTML tags.
422
+ */
423
+ _containsAnyRemainingHtmlTags(str) {
424
+ return str.includes("<");
425
+ }
426
+ /**
427
+ * Replaces the reserved HTML entities with the actual characters.
428
+ *
429
+ * @param htmlString Clipboard content without any tags.
430
+ */
431
+ _replaceHtmlReservedEntitiesWithCharacters(htmlString) {
432
+ return htmlString.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&nbsp;/g, " ");
433
+ }
434
+ };
435
+
436
+ /**
437
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
438
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
439
+ */
437
440
 
438
441
  export { Markdown, MarkdownGfmDataProcessor, MarkdownGfmHtmlToMd, MarkdownGfmMdToHtml, MarkdownGfmMdToHtmlDefaultPlugins, PasteFromMarkdownExperimental };
439
- //# sourceMappingURL=index.js.map
442
+ //# sourceMappingURL=index.js.map