1
1
import { TurndownService } from './turndown.js' ;
2
2
import { turndownPluginGfm } from './turndown-plugin-gfm.js' ;
3
3
4
+ /**
5
+ * replaceBrackets replaces any square brackets in text with the character or escape
6
+ * sequence chosen in settings.
7
+ * @param {string } text - the text.
8
+ * @param {string } subBrackets - the setting for what to substitute any square brackets
9
+ * with.
10
+ * @returns {string }
11
+ */
12
+ export function replaceBrackets ( text , subBrackets ) {
13
+ if ( subBrackets === 'underlined' ) {
14
+ return text . replaceAll ( '[' , '⦋' ) . replaceAll ( ']' , '⦌' ) ;
15
+ } else if ( subBrackets === 'escaped' ) {
16
+ return text . replaceAll ( '[' , '\\[' ) . replaceAll ( ']' , '\\]' ) ;
17
+ } else {
18
+ return text ;
19
+ }
20
+ }
21
+
4
22
/**
5
23
* newTurndownService creates a new TurndownService instance. The instance has been
6
24
* customized in a way that depends on the `location` object.
7
25
* @param {string } bulletPoint - the setting for the bullet point character.
26
+ * @param {string } subBrackets - the Stardown setting for what to substitute square
27
+ * brackets with.
8
28
* @param {Function(string): string } turndownEscape - the markdown escape function for
9
29
* the Turndown service instance to use.
10
30
* @returns {TurndownService }
11
31
*/
12
- export function newTurndownService ( bulletPoint , turndownEscape ) {
32
+ export function newTurndownService ( bulletPoint , subBrackets , turndownEscape ) {
13
33
// https://github.com/mixmark-io/turndown
14
34
const t = new TurndownService ( {
15
35
bulletListMarker : bulletPoint ,
16
36
headingStyle : 'atx' ,
37
+ emDelimiter : '*' ,
17
38
codeBlockStyle : 'fenced' ,
18
- } ) . remove ( 'style' ) . remove ( 'script' ) . remove ( 'noscript' ) . remove ( 'link' ) ;
39
+ } ) ;
19
40
20
41
t . use ( turndownPluginGfm . gfm ) ; // GitHub Flavored Markdown
21
42
22
43
t . escape = turndownEscape ;
23
44
24
- addRules ( t ) ;
45
+ addRules ( t , subBrackets ) ;
46
+
47
+ t . keep ( [ 'u' , 'dl' , 'dt' , 'dd' ] ) ;
48
+
49
+ // Keep subscript and superscript nodes as HTML if and only if they don't contain
50
+ // HTML anchor elements because they are not clickable at least in Obsidian. Also,
51
+ // if URLs aren't processed, they can't be made absolute.
52
+ t . keep ( ( node ) => {
53
+ return (
54
+ ( node . nodeName === 'SUB' || node . nodeName === 'SUP' ) &&
55
+ ! node . querySelectorAll ( 'a' ) . length
56
+ ) ;
57
+ } ) ;
58
+
59
+ t . remove ( [ 'style' , 'script' , 'noscript' , 'link' ] ) ;
25
60
26
61
return t ;
27
62
}
28
63
29
64
/**
30
65
* addRules adds custom Turndown rules to a Turndown service instance.
31
66
* @param {TurndownService } t - the Turndown service instance.
67
+ * @param {string } subBrackets - the Stardown setting for what to substitute square
68
+ * brackets with.
32
69
* @returns {void }
33
70
*/
34
- function addRules ( t ) {
71
+ function addRules ( t , subBrackets ) {
35
72
// Each Turndown rule runs on each yet-unreplaced HTML element. If the element
36
73
// matches the rule's filter, the rule's replacement function runs on it.
37
74
38
75
t . addRule ( 'inlineLink' , {
39
76
filter : isInlineLink ,
40
- replacement : convertLinkToMarkdown ,
77
+ replacement : newConvertLinkToMarkdown ( subBrackets ) ,
41
78
} ) ;
42
79
43
80
t . addRule ( 'img' , {
44
81
filter : 'img' ,
45
82
replacement : convertImageToMarkdown ,
46
83
} ) ;
84
+
85
+ t . addRule ( 'strikethrough' , {
86
+ filter : [ 'del' , 's' , 'strike' ] ,
87
+ replacement : function ( content ) {
88
+ return '~~' + content + '~~' ;
89
+ } ,
90
+ } ) ;
91
+
92
+ t . addRule ( 'highlight' , {
93
+ filter : 'mark' ,
94
+ replacement : function ( content ) {
95
+ return '==' + content + '==' ;
96
+ } ,
97
+ } ) ;
47
98
}
48
99
49
100
/**
@@ -61,40 +112,49 @@ function isInlineLink(node, options) {
61
112
}
62
113
63
114
/**
64
- * convertLinkToMarkdown converts an HTML link to a markdown link.
65
- * @param { string } content - the page's content within the HTML anchor. If the anchor
66
- * contains some elements like inline SVGs, this variable will be falsy.
67
- * @param { * } node - the HTML element node .
68
- * @returns {string }
115
+ * newConvertLinkToMarkdown returns a function that converts an HTML link to a markdown
116
+ * link.
117
+ * @param { string } subBrackets - the Stardown setting for what to substitute square
118
+ * brackets with .
119
+ * @returns {Function(string, any): string }
69
120
*/
70
- function convertLinkToMarkdown ( content , node ) {
71
- if ( ! content ) { // if the link's title would be empty
72
- return '' ; // don't create the link
73
- }
74
-
75
- let href = node . getAttribute ( 'href' ) ;
76
- if ( href ) {
77
- // make the URL absolute
78
- if ( href . startsWith ( '/' ) ) {
79
- const url = new URL ( location . href ) ;
80
- const base = url . origin ;
81
- href = base + href ;
82
- } else if ( href . startsWith ( '#' ) ) {
83
- href = location . href + href ;
121
+ function newConvertLinkToMarkdown ( subBrackets ) {
122
+ /**
123
+ * @param {string } content - the page's content within the HTML anchor. If the anchor
124
+ * contains some elements like inline SVGs, this variable will be falsy.
125
+ * @param {* } node - the HTML element node.
126
+ * @returns {string }
127
+ */
128
+ return function ( content , node ) {
129
+ if ( ! content ) { // if the link's title would be empty
130
+ return '' ; // don't create the link
84
131
}
85
132
86
- // escape parentheses
87
- href = href . replace ( / ( [ ( ) ] ) / g, '\\$1' ) ;
88
- }
133
+ content = replaceBrackets ( content , subBrackets ) ;
89
134
90
- // remove excess whitespace and escape quotation marks
91
- let title = node . getAttribute ( 'title' ) ;
92
- if ( title ) {
93
- title = cleanAttribute ( title ) ;
94
- title = ' "' + title . replace ( / " / g, '\\"' ) + '"' ;
95
- }
135
+ let href = node . getAttribute ( 'href' ) || '' ;
136
+ if ( href ) {
137
+ href = href . replaceAll ( ' ' , '%20' ) . replaceAll ( '(' , '%28' ) . replaceAll ( ')' , '%29' ) ;
138
+
139
+ // make the URL absolute
140
+ if ( href . startsWith ( '/' ) ) {
141
+ const url = new URL ( location . href ) ;
142
+ const base = url . origin ;
143
+ href = base + href ;
144
+ } else if ( href . startsWith ( '#' ) ) {
145
+ href = location . href + href ;
146
+ }
147
+ }
148
+
149
+ // remove excess whitespace and escape quotation marks
150
+ let title = node . getAttribute ( 'title' ) || '' ;
151
+ if ( title ) {
152
+ title = cleanAttribute ( title ) ;
153
+ title = ' "' + title . replace ( / " / g, '\\"' ) + '"' ;
154
+ }
96
155
97
- return '[' + content + '](' + href + title + ')' ;
156
+ return '[' + content + '](' + href + title + ')' ;
157
+ } ;
98
158
}
99
159
100
160
/**
@@ -110,19 +170,19 @@ function convertImageToMarkdown(content, node) {
110
170
}
111
171
112
172
// remove excess whitespace
113
- let alt = cleanAttribute ( node . getAttribute ( 'alt' ) ) ;
173
+ let alt = cleanAttribute ( node . getAttribute ( 'alt' ) || '' ) ;
114
174
115
175
// make the URL absolute
116
176
if ( src . startsWith ( '//' ) ) {
117
- src = 'https:' + src ;
177
+ src = 'https:' + src . replaceAll ( ' ' , '%20' ) ;
118
178
} else if ( src . startsWith ( '/' ) ) {
119
179
const url = new URL ( location . href ) ;
120
180
const base = url . origin ;
121
181
src = base + src ;
122
182
}
123
183
124
184
// remove excess whitespace
125
- let title = cleanAttribute ( node . getAttribute ( 'title' ) ) ;
185
+ let title = cleanAttribute ( node . getAttribute ( 'title' ) || '' ) ;
126
186
let titlePart = title ? ' "' + title + '"' : '' ;
127
187
128
188
return '' ;
0 commit comments