@ckeditor/ckeditor5-markdown-gfm 48.2.0 → 48.3.0-alpha.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/dist/augmentation.d.ts +9 -9
- package/dist/gfmdataprocessor.d.ts +65 -65
- package/dist/html2markdown/html2markdown.d.ts +13 -13
- package/dist/index-content.css +1 -0
- package/dist/index-editor.css +1 -0
- package/dist/index.css +0 -2
- package/dist/index.d.ts +11 -11
- package/dist/index.js +415 -412
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +22 -22
- package/dist/markdown2html/markdown2html.d.ts +27 -27
- package/dist/pastefrommarkdownexperimental.d.ts +73 -73
- package/package.json +4 -4
- package/dist/index.css.map +0 -1
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
|
|
6
|
-
import { HtmlDataProcessor } from
|
|
7
|
-
import { unified } from
|
|
8
|
-
import remarkGfm from
|
|
9
|
-
import remarkParse from
|
|
10
|
-
import remarkRehype from
|
|
11
|
-
import remarkBreaks from
|
|
12
|
-
import rehypeStringify from
|
|
13
|
-
import { visit } from
|
|
14
|
-
import { toHtml } from
|
|
15
|
-
import { fromDom } from
|
|
16
|
-
import rehypeParse from
|
|
17
|
-
import rehypeRemark from
|
|
18
|
-
import remarkStringify from
|
|
19
|
-
import { h } from
|
|
20
|
-
import { ClipboardPipeline } from
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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 – 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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
308
|
+
"SPAN",
|
|
309
|
+
"BR",
|
|
310
|
+
"PRE",
|
|
311
|
+
"CODE"
|
|
304
312
|
];
|
|
305
313
|
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
* @param htmlString Clipboard content without any tags.
|
|
433
|
-
*/ _replaceHtmlReservedEntitiesWithCharacters(htmlString) {
|
|
434
|
-
return htmlString.replace(/>/g, '>').replace(/</g, '<').replace(/ /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(/>/g, ">").replace(/</g, "<").replace(/ /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
|