@ckeditor/ckeditor5-markdown-gfm 41.4.1 → 42.0.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/README.md +6 -0
- package/build/markdown-gfm.js +1 -1
- package/dist/index.js +248 -211
- 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
@@ -5,122 +5,130 @@
|
|
5
5
|
/**
|
6
6
|
* @module markdown-gfm/html2markdown/html2markdown
|
7
7
|
*/
|
8
|
-
|
9
|
-
// Importing types for this package is problematic, so it's omitted.
|
10
|
-
// @ts-ignore
|
11
|
-
import TurndownService from 'turndown/lib/turndown.browser.es.js';
|
8
|
+
import Turndown from 'turndown';
|
12
9
|
// There no avaialble types for 'turndown-plugin-gfm' module and it's not worth to generate them on our own.
|
10
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
13
11
|
// @ts-ignore
|
14
12
|
import { gfm } from 'turndown-plugin-gfm';
|
15
|
-
|
16
|
-
const originalEscape = TurndownService.prototype.escape;
|
17
|
-
function escape(string) {
|
18
|
-
string = originalEscape(string);
|
19
|
-
// Escape "<".
|
20
|
-
string = string.replace(/</g, '\\<');
|
21
|
-
return string;
|
22
|
-
}
|
23
|
-
TurndownService.prototype.escape = function (string) {
|
24
|
-
// Urls should not be escaped. Our strategy is using a regex to find them and escape everything
|
25
|
-
// which is out of the matches parts.
|
26
|
-
let escaped = '';
|
27
|
-
let lastLinkEnd = 0;
|
28
|
-
for (const match of matchAutolink(string)) {
|
29
|
-
const index = match.index;
|
30
|
-
// Append the substring between the last match and the current one (if anything).
|
31
|
-
if (index > lastLinkEnd) {
|
32
|
-
escaped += escape(string.substring(lastLinkEnd, index));
|
33
|
-
}
|
34
|
-
const matchedURL = match[0];
|
35
|
-
escaped += matchedURL;
|
36
|
-
lastLinkEnd = index + matchedURL.length;
|
37
|
-
}
|
38
|
-
// Add text after the last link or at the string start if no matches.
|
39
|
-
if (lastLinkEnd < string.length) {
|
40
|
-
escaped += escape(string.substring(lastLinkEnd, string.length));
|
41
|
-
}
|
42
|
-
return escaped;
|
43
|
-
};
|
44
|
-
const turndownService = new TurndownService({
|
45
|
-
codeBlockStyle: 'fenced',
|
46
|
-
hr: '---',
|
47
|
-
headingStyle: 'atx'
|
48
|
-
});
|
49
|
-
turndownService.use([
|
50
|
-
gfm,
|
51
|
-
todoList
|
52
|
-
]);
|
53
|
-
/**
|
54
|
-
* Parses HTML to a markdown.
|
55
|
-
*/
|
56
|
-
export default function html2markdown(html) {
|
57
|
-
return turndownService.turndown(html);
|
58
|
-
}
|
59
|
-
export { turndownService };
|
60
|
-
// This is a copy of the original taskListItems rule from turdown-plugin-gfm, with minor changes.
|
61
|
-
function todoList(turndownService) {
|
62
|
-
turndownService.addRule('taskListItems', {
|
63
|
-
filter(node) {
|
64
|
-
return node.type === 'checkbox' &&
|
65
|
-
// Changes here as CKEditor outputs a deeper structure.
|
66
|
-
(node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI');
|
67
|
-
},
|
68
|
-
replacement(content, node) {
|
69
|
-
return (node.checked ? '[x]' : '[ ]') + ' ';
|
70
|
-
}
|
71
|
-
});
|
72
|
-
}
|
73
|
-
// Autolink matcher.
|
74
|
-
const regex = new RegExp(
|
13
|
+
const autolinkRegex = /* #__PURE__ */ new RegExp(
|
75
14
|
// Prefix.
|
76
15
|
/\b(?:(?:https?|ftp):\/\/|www\.)/.source +
|
77
16
|
// Domain name.
|
78
17
|
/(?![-_])(?:[-_a-z0-9\u00a1-\uffff]{1,63}\.)+(?:[a-z\u00a1-\uffff]{2,63})/.source +
|
79
18
|
// The rest.
|
80
19
|
/(?:[^\s<>]*)/.source, 'gi');
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
//
|
20
|
+
class UpdatedTurndown extends Turndown {
|
21
|
+
escape(string) {
|
22
|
+
const originalEscape = super.escape;
|
23
|
+
function escape(string) {
|
24
|
+
string = originalEscape(string);
|
25
|
+
// Escape "<".
|
26
|
+
string = string.replace(/</g, '\\<');
|
27
|
+
return string;
|
28
|
+
}
|
29
|
+
// Urls should not be escaped. Our strategy is using a regex to find them and escape everything
|
30
|
+
// which is out of the matches parts.
|
31
|
+
let escaped = '';
|
32
|
+
let lastLinkEnd = 0;
|
33
|
+
for (const match of this._matchAutolink(string)) {
|
34
|
+
const index = match.index;
|
35
|
+
// Append the substring between the last match and the current one (if anything).
|
36
|
+
if (index > lastLinkEnd) {
|
37
|
+
escaped += escape(string.substring(lastLinkEnd, index));
|
38
|
+
}
|
39
|
+
const matchedURL = match[0];
|
40
|
+
escaped += matchedURL;
|
41
|
+
lastLinkEnd = index + matchedURL.length;
|
42
|
+
}
|
43
|
+
// Add text after the last link or at the string start if no matches.
|
44
|
+
if (lastLinkEnd < string.length) {
|
45
|
+
escaped += escape(string.substring(lastLinkEnd, string.length));
|
46
|
+
}
|
47
|
+
return escaped;
|
91
48
|
}
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
49
|
+
/**
|
50
|
+
* Trimming end of link.
|
51
|
+
* https://github.github.com/gfm/#autolinks-extension-
|
52
|
+
*/
|
53
|
+
*_matchAutolink(string) {
|
54
|
+
for (const match of string.matchAll(autolinkRegex)) {
|
55
|
+
const matched = match[0];
|
56
|
+
const length = this._autolinkFindEnd(matched);
|
57
|
+
yield Object.assign([matched.substring(0, length)], { index: match.index });
|
58
|
+
// We could adjust regex.lastIndex but it's not needed because what we skipped is for sure not a valid URL.
|
102
59
|
}
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
60
|
+
}
|
61
|
+
/**
|
62
|
+
* Returns the new length of the link (after it would trim trailing characters).
|
63
|
+
*/
|
64
|
+
_autolinkFindEnd(string) {
|
65
|
+
let length = string.length;
|
66
|
+
while (length > 0) {
|
67
|
+
const char = string[length - 1];
|
68
|
+
if ('?!.,:*_~\'"'.includes(char)) {
|
69
|
+
length--;
|
70
|
+
}
|
71
|
+
else if (char == ')') {
|
72
|
+
let openBrackets = 0;
|
73
|
+
for (let i = 0; i < length; i++) {
|
74
|
+
if (string[i] == '(') {
|
75
|
+
openBrackets++;
|
76
|
+
}
|
77
|
+
else if (string[i] == ')') {
|
78
|
+
openBrackets--;
|
79
|
+
}
|
108
80
|
}
|
109
|
-
|
110
|
-
|
81
|
+
// If there is fewer opening brackets then closing ones we should remove a closing bracket.
|
82
|
+
if (openBrackets < 0) {
|
83
|
+
length--;
|
84
|
+
}
|
85
|
+
else {
|
86
|
+
break;
|
111
87
|
}
|
112
|
-
}
|
113
|
-
// If there is fewer opening brackets then closing ones we should remove a closing bracket.
|
114
|
-
if (openBrackets < 0) {
|
115
|
-
length--;
|
116
88
|
}
|
117
89
|
else {
|
118
90
|
break;
|
119
91
|
}
|
120
92
|
}
|
121
|
-
|
122
|
-
|
123
|
-
|
93
|
+
return length;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
/**
|
97
|
+
* This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert HTML to Markdown.
|
98
|
+
*/
|
99
|
+
export class HtmlToMarkdown {
|
100
|
+
constructor() {
|
101
|
+
this._parser = this._createParser();
|
102
|
+
}
|
103
|
+
parse(html) {
|
104
|
+
return this._parser.turndown(html);
|
105
|
+
}
|
106
|
+
keep(elements) {
|
107
|
+
this._parser.keep(elements);
|
108
|
+
}
|
109
|
+
_createParser() {
|
110
|
+
const parser = new UpdatedTurndown({
|
111
|
+
codeBlockStyle: 'fenced',
|
112
|
+
hr: '---',
|
113
|
+
headingStyle: 'atx'
|
114
|
+
});
|
115
|
+
parser.use([
|
116
|
+
gfm,
|
117
|
+
this._todoList
|
118
|
+
]);
|
119
|
+
return parser;
|
120
|
+
}
|
121
|
+
// This is a copy of the original taskListItems rule from turndown-plugin-gfm, with minor changes.
|
122
|
+
_todoList(turndown) {
|
123
|
+
turndown.addRule('taskListItems', {
|
124
|
+
filter(node) {
|
125
|
+
return node.type === 'checkbox' &&
|
126
|
+
// Changes here as CKEditor outputs a deeper structure.
|
127
|
+
(node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI');
|
128
|
+
},
|
129
|
+
replacement(content, node) {
|
130
|
+
return (node.checked ? '[x]' : '[ ]') + ' ';
|
131
|
+
}
|
132
|
+
});
|
124
133
|
}
|
125
|
-
return length;
|
126
134
|
}
|
@@ -3,11 +3,11 @@
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
5
|
/**
|
6
|
-
* @module
|
6
|
+
* This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert Markdown to HTML.
|
7
7
|
*/
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
export declare class MarkdownToHtml {
|
9
|
+
private _parser;
|
10
|
+
private _options;
|
11
|
+
constructor();
|
12
|
+
parse(markdown: string): string;
|
13
|
+
}
|
@@ -6,39 +6,43 @@
|
|
6
6
|
* @module markdown-gfm/markdown2html/markdown2html
|
7
7
|
*/
|
8
8
|
import { marked } from 'marked';
|
9
|
-
// Overrides.
|
10
|
-
marked.use({
|
11
|
-
tokenizer: {
|
12
|
-
// Disable the autolink rule in the lexer.
|
13
|
-
autolink: () => null,
|
14
|
-
url: () => null
|
15
|
-
},
|
16
|
-
renderer: {
|
17
|
-
checkbox(...args) {
|
18
|
-
// Remove bogus space after <input type="checkbox"> because it would be preserved
|
19
|
-
// by DomConverter as it's next to an inline object.
|
20
|
-
return Object.getPrototypeOf(this).checkbox.call(this, ...args).trimRight();
|
21
|
-
},
|
22
|
-
code(...args) {
|
23
|
-
// Since marked v1.2.8, every <code> gets a trailing "\n" whether it originally
|
24
|
-
// ended with one or not (see https://github.com/markedjs/marked/issues/1884 to learn why).
|
25
|
-
// This results in a redundant soft break in the model when loaded into the editor, which
|
26
|
-
// is best prevented at this stage. See https://github.com/ckeditor/ckeditor5/issues/11124.
|
27
|
-
return Object.getPrototypeOf(this).code.call(this, ...args).replace('\n</code>', '</code>');
|
28
|
-
}
|
29
|
-
}
|
30
|
-
});
|
31
9
|
/**
|
32
|
-
*
|
10
|
+
* This is a helper class used by the {@link module:markdown-gfm/markdown Markdown feature} to convert Markdown to HTML.
|
33
11
|
*/
|
34
|
-
export
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
12
|
+
export class MarkdownToHtml {
|
13
|
+
constructor() {
|
14
|
+
this._options = {
|
15
|
+
gfm: true,
|
16
|
+
breaks: true,
|
17
|
+
tables: true,
|
18
|
+
xhtml: true,
|
19
|
+
headerIds: false
|
20
|
+
};
|
21
|
+
// Overrides.
|
22
|
+
marked.use({
|
23
|
+
tokenizer: {
|
24
|
+
// Disable the autolink rule in the lexer.
|
25
|
+
autolink: () => null,
|
26
|
+
url: () => null
|
27
|
+
},
|
28
|
+
renderer: {
|
29
|
+
checkbox(...args) {
|
30
|
+
// Remove bogus space after <input type="checkbox"> because it would be preserved
|
31
|
+
// by DomConverter as it's next to an inline object.
|
32
|
+
return Object.getPrototypeOf(this).checkbox.call(this, ...args).trimRight();
|
33
|
+
},
|
34
|
+
code(...args) {
|
35
|
+
// Since marked v1.2.8, every <code> gets a trailing "\n" whether it originally
|
36
|
+
// ended with one or not (see https://github.com/markedjs/marked/issues/1884 to learn why).
|
37
|
+
// This results in a redundant soft break in the model when loaded into the editor, which
|
38
|
+
// is best prevented at this stage. See https://github.com/ckeditor/ckeditor5/issues/11124.
|
39
|
+
return Object.getPrototypeOf(this).code.call(this, ...args).replace('\n</code>', '</code>');
|
40
|
+
}
|
41
|
+
}
|
42
|
+
});
|
43
|
+
this._parser = marked;
|
44
|
+
}
|
45
|
+
parse(markdown) {
|
46
|
+
return this._parser.parse(markdown, this._options);
|
47
|
+
}
|
43
48
|
}
|
44
|
-
export { marked };
|