@ckeditor/ckeditor5-markdown-gfm 0.0.0-nightly-20240603.0 → 0.0.0-nightly-20240604.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/build/markdown-gfm.js +1 -1
- package/dist/index.js +148 -129
- package/dist/index.js.map +1 -1
- package/dist/types/gfmdataprocessor.d.ts +8 -0
- package/dist/types/html2markdown/html2markdown.d.ts +13 -4
- package/dist/types/markdown2html/markdown2html.d.ts +7 -7
- package/package.json +3 -3
- package/src/gfmdataprocessor.d.ts +8 -0
- package/src/gfmdataprocessor.js +7 -5
- package/src/html2markdown/html2markdown.d.ts +13 -4
- package/src/html2markdown/html2markdown.js +107 -99
- package/src/markdown2html/markdown2html.d.ts +7 -7
- package/src/markdown2html/markdown2html.js +37 -33
package/dist/index.js
CHANGED
|
@@ -5,155 +5,166 @@
|
|
|
5
5
|
import { Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
6
|
import { HtmlDataProcessor } from '@ckeditor/ckeditor5-engine/dist/index.js';
|
|
7
7
|
import { marked } from 'marked';
|
|
8
|
-
import
|
|
8
|
+
import Turndown from 'turndown';
|
|
9
9
|
import { gfm } from 'turndown-plugin-gfm';
|
|
10
10
|
import { ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard/dist/index.js';
|
|
11
11
|
|
|
12
|
-
// Overrides.
|
|
13
|
-
marked.use({
|
|
14
|
-
tokenizer: {
|
|
15
|
-
// Disable the autolink rule in the lexer.
|
|
16
|
-
autolink: ()=>null,
|
|
17
|
-
url: ()=>null
|
|
18
|
-
},
|
|
19
|
-
renderer: {
|
|
20
|
-
checkbox (...args) {
|
|
21
|
-
// Remove bogus space after <input type="checkbox"> because it would be preserved
|
|
22
|
-
// by DomConverter as it's next to an inline object.
|
|
23
|
-
return Object.getPrototypeOf(this).checkbox.call(this, ...args).trimRight();
|
|
24
|
-
},
|
|
25
|
-
code (...args) {
|
|
26
|
-
// Since marked v1.2.8, every <code> gets a trailing "\n" whether it originally
|
|
27
|
-
// ended with one or not (see https://github.com/markedjs/marked/issues/1884 to learn why).
|
|
28
|
-
// This results in a redundant soft break in the model when loaded into the editor, which
|
|
29
|
-
// is best prevented at this stage. See https://github.com/ckeditor/ckeditor5/issues/11124.
|
|
30
|
-
return Object.getPrototypeOf(this).code.call(this, ...args).replace('\n</code>', '</code>');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
12
|
/**
|
|
35
|
-
*
|
|
36
|
-
*/
|
|
37
|
-
|
|
13
|
+
* This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert Markdown to HTML.
|
|
14
|
+
*/ class MarkdownToHtml {
|
|
15
|
+
_parser;
|
|
16
|
+
_options = {
|
|
38
17
|
gfm: true,
|
|
39
18
|
breaks: true,
|
|
40
19
|
tables: true,
|
|
41
20
|
xhtml: true,
|
|
42
21
|
headerIds: false
|
|
43
22
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const index = match.index;
|
|
69
|
-
// Append the substring between the last match and the current one (if anything).
|
|
70
|
-
if (index > lastLinkEnd) {
|
|
71
|
-
escaped += escape(string.substring(lastLinkEnd, index));
|
|
72
|
-
}
|
|
73
|
-
const matchedURL = match[0];
|
|
74
|
-
escaped += matchedURL;
|
|
75
|
-
lastLinkEnd = index + matchedURL.length;
|
|
23
|
+
constructor(){
|
|
24
|
+
// Overrides.
|
|
25
|
+
marked.use({
|
|
26
|
+
tokenizer: {
|
|
27
|
+
// Disable the autolink rule in the lexer.
|
|
28
|
+
autolink: ()=>null,
|
|
29
|
+
url: ()=>null
|
|
30
|
+
},
|
|
31
|
+
renderer: {
|
|
32
|
+
checkbox (...args) {
|
|
33
|
+
// Remove bogus space after <input type="checkbox"> because it would be preserved
|
|
34
|
+
// by DomConverter as it's next to an inline object.
|
|
35
|
+
return Object.getPrototypeOf(this).checkbox.call(this, ...args).trimRight();
|
|
36
|
+
},
|
|
37
|
+
code (...args) {
|
|
38
|
+
// Since marked v1.2.8, every <code> gets a trailing "\n" whether it originally
|
|
39
|
+
// ended with one or not (see https://github.com/markedjs/marked/issues/1884 to learn why).
|
|
40
|
+
// This results in a redundant soft break in the model when loaded into the editor, which
|
|
41
|
+
// is best prevented at this stage. See https://github.com/ckeditor/ckeditor5/issues/11124.
|
|
42
|
+
return Object.getPrototypeOf(this).code.call(this, ...args).replace('\n</code>', '</code>');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
this._parser = marked;
|
|
76
47
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
escaped += escape(string.substring(lastLinkEnd, string.length));
|
|
48
|
+
parse(markdown) {
|
|
49
|
+
return this._parser.parse(markdown, this._options);
|
|
80
50
|
}
|
|
81
|
-
return escaped;
|
|
82
|
-
};
|
|
83
|
-
const turndownService = new TurndownService({
|
|
84
|
-
codeBlockStyle: 'fenced',
|
|
85
|
-
hr: '---',
|
|
86
|
-
headingStyle: 'atx'
|
|
87
|
-
});
|
|
88
|
-
turndownService.use([
|
|
89
|
-
gfm,
|
|
90
|
-
todoList
|
|
91
|
-
]);
|
|
92
|
-
/**
|
|
93
|
-
* Parses HTML to a markdown.
|
|
94
|
-
*/ function html2markdown(html) {
|
|
95
|
-
return turndownService.turndown(html);
|
|
96
|
-
}
|
|
97
|
-
// This is a copy of the original taskListItems rule from turdown-plugin-gfm, with minor changes.
|
|
98
|
-
function todoList(turndownService) {
|
|
99
|
-
turndownService.addRule('taskListItems', {
|
|
100
|
-
filter (node) {
|
|
101
|
-
return node.type === 'checkbox' && // Changes here as CKEditor outputs a deeper structure.
|
|
102
|
-
(node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI');
|
|
103
|
-
},
|
|
104
|
-
replacement (content, node) {
|
|
105
|
-
return (node.checked ? '[x]' : '[ ]') + ' ';
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
51
|
}
|
|
109
|
-
|
|
110
|
-
const
|
|
52
|
+
|
|
53
|
+
const autolinkRegex = /* #__PURE__ */ new RegExp(// Prefix.
|
|
111
54
|
/\b(?:(?:https?|ftp):\/\/|www\.)/.source + // Domain name.
|
|
112
55
|
/(?![-_])(?:[-_a-z0-9\u00a1-\uffff]{1,63}\.)+(?:[a-z\u00a1-\uffff]{2,63})/.source + // The rest.
|
|
113
56
|
/(?:[^\s<>]*)/.source, 'gi');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let length = string.length;
|
|
133
|
-
while(length > 0){
|
|
134
|
-
const char = string[length - 1];
|
|
135
|
-
if ('?!.,:*_~\'"'.includes(char)) {
|
|
136
|
-
length--;
|
|
137
|
-
} else if (char == ')') {
|
|
138
|
-
let openBrackets = 0;
|
|
139
|
-
for(let i = 0; i < length; i++){
|
|
140
|
-
if (string[i] == '(') {
|
|
141
|
-
openBrackets++;
|
|
142
|
-
} else if (string[i] == ')') {
|
|
143
|
-
openBrackets--;
|
|
144
|
-
}
|
|
57
|
+
class UpdatedTurndown extends Turndown {
|
|
58
|
+
escape(string) {
|
|
59
|
+
const originalEscape = super.escape;
|
|
60
|
+
function escape(string) {
|
|
61
|
+
string = originalEscape(string);
|
|
62
|
+
// Escape "<".
|
|
63
|
+
string = string.replace(/</g, '\\<');
|
|
64
|
+
return string;
|
|
65
|
+
}
|
|
66
|
+
// Urls should not be escaped. Our strategy is using a regex to find them and escape everything
|
|
67
|
+
// which is out of the matches parts.
|
|
68
|
+
let escaped = '';
|
|
69
|
+
let lastLinkEnd = 0;
|
|
70
|
+
for (const match of this._matchAutolink(string)){
|
|
71
|
+
const index = match.index;
|
|
72
|
+
// Append the substring between the last match and the current one (if anything).
|
|
73
|
+
if (index > lastLinkEnd) {
|
|
74
|
+
escaped += escape(string.substring(lastLinkEnd, index));
|
|
145
75
|
}
|
|
146
|
-
|
|
147
|
-
|
|
76
|
+
const matchedURL = match[0];
|
|
77
|
+
escaped += matchedURL;
|
|
78
|
+
lastLinkEnd = index + matchedURL.length;
|
|
79
|
+
}
|
|
80
|
+
// Add text after the last link or at the string start if no matches.
|
|
81
|
+
if (lastLinkEnd < string.length) {
|
|
82
|
+
escaped += escape(string.substring(lastLinkEnd, string.length));
|
|
83
|
+
}
|
|
84
|
+
return escaped;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Trimming end of link.
|
|
88
|
+
* https://github.github.com/gfm/#autolinks-extension-
|
|
89
|
+
*/ *_matchAutolink(string) {
|
|
90
|
+
for (const match of string.matchAll(autolinkRegex)){
|
|
91
|
+
const matched = match[0];
|
|
92
|
+
const length = this._autolinkFindEnd(matched);
|
|
93
|
+
yield Object.assign([
|
|
94
|
+
matched.substring(0, length)
|
|
95
|
+
], {
|
|
96
|
+
index: match.index
|
|
97
|
+
});
|
|
98
|
+
// We could adjust regex.lastIndex but it's not needed because what we skipped is for sure not a valid URL.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the new length of the link (after it would trim trailing characters).
|
|
103
|
+
*/ _autolinkFindEnd(string) {
|
|
104
|
+
let length = string.length;
|
|
105
|
+
while(length > 0){
|
|
106
|
+
const char = string[length - 1];
|
|
107
|
+
if ('?!.,:*_~\'"'.includes(char)) {
|
|
148
108
|
length--;
|
|
109
|
+
} else if (char == ')') {
|
|
110
|
+
let openBrackets = 0;
|
|
111
|
+
for(let i = 0; i < length; i++){
|
|
112
|
+
if (string[i] == '(') {
|
|
113
|
+
openBrackets++;
|
|
114
|
+
} else if (string[i] == ')') {
|
|
115
|
+
openBrackets--;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// If there is fewer opening brackets then closing ones we should remove a closing bracket.
|
|
119
|
+
if (openBrackets < 0) {
|
|
120
|
+
length--;
|
|
121
|
+
} else {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
149
124
|
} else {
|
|
150
125
|
break;
|
|
151
126
|
}
|
|
152
|
-
} else {
|
|
153
|
-
break;
|
|
154
127
|
}
|
|
128
|
+
return length;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert HTML to Markdown.
|
|
133
|
+
*/ class HtmlToMarkdown {
|
|
134
|
+
_parser;
|
|
135
|
+
constructor(){
|
|
136
|
+
this._parser = this._createParser();
|
|
137
|
+
}
|
|
138
|
+
parse(html) {
|
|
139
|
+
return this._parser.turndown(html);
|
|
140
|
+
}
|
|
141
|
+
keep(elements) {
|
|
142
|
+
this._parser.keep(elements);
|
|
143
|
+
}
|
|
144
|
+
_createParser() {
|
|
145
|
+
const parser = new UpdatedTurndown({
|
|
146
|
+
codeBlockStyle: 'fenced',
|
|
147
|
+
hr: '---',
|
|
148
|
+
headingStyle: 'atx'
|
|
149
|
+
});
|
|
150
|
+
parser.use([
|
|
151
|
+
gfm,
|
|
152
|
+
this._todoList
|
|
153
|
+
]);
|
|
154
|
+
return parser;
|
|
155
|
+
}
|
|
156
|
+
// This is a copy of the original taskListItems rule from turndown-plugin-gfm, with minor changes.
|
|
157
|
+
_todoList(turndown) {
|
|
158
|
+
turndown.addRule('taskListItems', {
|
|
159
|
+
filter (node) {
|
|
160
|
+
return node.type === 'checkbox' && // Changes here as CKEditor outputs a deeper structure.
|
|
161
|
+
(node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI');
|
|
162
|
+
},
|
|
163
|
+
replacement (content, node) {
|
|
164
|
+
return (node.checked ? '[x]' : '[ ]') + ' ';
|
|
165
|
+
}
|
|
166
|
+
});
|
|
155
167
|
}
|
|
156
|
-
return length;
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
/**
|
|
@@ -164,10 +175,18 @@ const regex = new RegExp(// Prefix.
|
|
|
164
175
|
/**
|
|
165
176
|
* HTML data processor used to process HTML produced by the Markdown-to-HTML converter and the other way.
|
|
166
177
|
*/ _htmlDP;
|
|
178
|
+
/**
|
|
179
|
+
* Helper for converting Markdown to HTML.
|
|
180
|
+
*/ _markdown2html;
|
|
181
|
+
/**
|
|
182
|
+
* Helper for converting HTML to Markdown.
|
|
183
|
+
*/ _html2markdown;
|
|
167
184
|
/**
|
|
168
185
|
* Creates a new instance of the Markdown data processor class.
|
|
169
186
|
*/ constructor(document){
|
|
170
187
|
this._htmlDP = new HtmlDataProcessor(document);
|
|
188
|
+
this._markdown2html = new MarkdownToHtml();
|
|
189
|
+
this._html2markdown = new HtmlToMarkdown();
|
|
171
190
|
}
|
|
172
191
|
/**
|
|
173
192
|
* Keeps the specified element in the output as HTML. This is useful if the editor contains
|
|
@@ -177,7 +196,7 @@ const regex = new RegExp(// Prefix.
|
|
|
177
196
|
*
|
|
178
197
|
* @param element The element name to be kept.
|
|
179
198
|
*/ keepHtml(element) {
|
|
180
|
-
|
|
199
|
+
this._html2markdown.keep([
|
|
181
200
|
element
|
|
182
201
|
]);
|
|
183
202
|
}
|
|
@@ -187,7 +206,7 @@ const regex = new RegExp(// Prefix.
|
|
|
187
206
|
* @param data A Markdown string.
|
|
188
207
|
* @returns The converted view element.
|
|
189
208
|
*/ toView(data) {
|
|
190
|
-
const html =
|
|
209
|
+
const html = this._markdown2html.parse(data);
|
|
191
210
|
return this._htmlDP.toView(html);
|
|
192
211
|
}
|
|
193
212
|
/**
|
|
@@ -197,7 +216,7 @@ const regex = new RegExp(// Prefix.
|
|
|
197
216
|
* @returns Markdown string.
|
|
198
217
|
*/ toData(viewFragment) {
|
|
199
218
|
const html = this._htmlDP.toData(viewFragment);
|
|
200
|
-
return
|
|
219
|
+
return this._html2markdown.parse(html);
|
|
201
220
|
}
|
|
202
221
|
/**
|
|
203
222
|
* Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
|