@@ -16,19 +16,55 @@ const WORDPRESS_TO_PRISM_LANGUAGE_TRANSLATION = {
16
16
markup : "html" ,
17
17
} ;
18
18
19
+ const TAGS_TO_KEEP = [
20
+ "abbr" ,
21
+ "address" ,
22
+ "audio" ,
23
+ "cite" ,
24
+ "dd" ,
25
+ "del" ,
26
+ "details" ,
27
+ // "dialog",
28
+ "dfn" ,
29
+ // "figure",
30
+ "form" ,
31
+ "iframe" ,
32
+ "ins" ,
33
+ "kbd" ,
34
+ "object" ,
35
+ "q" ,
36
+ "sub" ,
37
+ "s" ,
38
+ "samp" ,
39
+ "svg" ,
40
+ "table" ,
41
+ "time" ,
42
+ "var" ,
43
+ "video" ,
44
+ "wbr" ,
45
+ ] ;
46
+
19
47
class MarkdownToHtml {
20
48
#prettierLanguages;
21
49
#initStarted;
22
50
23
51
constructor ( ) {
24
52
this . assetsToKeep = new Set ( ) ;
25
53
this . assetsToDelete = new Set ( ) ;
54
+ this . preservedSelectors = new Set ( ) ;
26
55
this . isVerbose = true ;
27
56
this . counts = {
28
57
cleaned : 0
29
58
}
30
59
}
31
60
61
+ addPreservedSelector ( selector ) {
62
+ if ( ! selector . startsWith ( "." ) ) {
63
+ throw new Error ( "Invalid preserved selector. Only class names are supported." ) ;
64
+ }
65
+ this . preservedSelectors . add ( selector ) ;
66
+ }
67
+
32
68
async asyncInit ( ) {
33
69
if ( this . #initStarted) {
34
70
return ;
@@ -118,6 +154,42 @@ class MarkdownToHtml {
118
154
return `\`\`\`${ language || "" } \n${ content . trim ( ) } \n\`\`\`\n\n`
119
155
}
120
156
157
+ // Supports .className selectors
158
+ static hasClass ( node , className ) {
159
+ if ( className . startsWith ( "." ) ) {
160
+ className = className . slice ( 1 ) ;
161
+ }
162
+ return this . hasAttribute ( node , "class" , className ) ;
163
+ }
164
+
165
+ static matchAttributeEntry ( value , expected ) {
166
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#attrvalue_3
167
+ if ( expected . startsWith ( "|=" ) ) {
168
+ let actual = expected . slice ( 2 ) ;
169
+ // |= is equal to or starts with (and a hyphen)
170
+ return value === actual || value . startsWith ( `${ actual } -` ) ;
171
+ }
172
+
173
+ return value === expected ;
174
+ }
175
+
176
+ static hasAttribute ( node , attrName , attrValueMatch ) {
177
+ if ( node . _attrKeys ?. includes ( `|${ attrName } ` ) ) {
178
+ let attrValue = node . _attrsByQName ?. [ attrName ] ?. data ;
179
+ // [class] is special, space separated values
180
+ if ( attrName === "class" ) {
181
+ return attrValue . split ( " " ) . find ( entry => {
182
+ return this . matchAttributeEntry ( entry , attrValueMatch ) ;
183
+ } ) ;
184
+ }
185
+
186
+ // not [class]
187
+ return attrValue === attrValueMatch ;
188
+ }
189
+
190
+ return false ;
191
+ }
192
+
121
193
getTurndownService ( options = { } ) {
122
194
let { filePath, type } = options ;
123
195
let isFromWordPress = type === WordPressApi . TYPE || type === HostedWordPressApi . TYPE ;
@@ -127,37 +199,35 @@ class MarkdownToHtml {
127
199
bulletListMarker : "-" ,
128
200
codeBlockStyle : "fenced" ,
129
201
202
+ // Workaround to keep icon elements
203
+ blankReplacement ( content , node ) {
204
+ if ( node . localName === "i" ) {
205
+ if ( MarkdownToHtml . hasClass ( node , "|=fa" ) ) {
206
+ return node . outerHTML ;
207
+ }
208
+ }
209
+
210
+ // content will be empty unless it has a preserved child, e.g. <p><i class="fa-"></i></p>
211
+ return node . isBlock ? `\n\n${ content } \n\n` : content ;
212
+ } ,
213
+
130
214
// Intentionally opt-out
131
215
// preformattedCode: true,
132
216
} ) ;
133
217
134
- ts . keep ( [
135
- "abbr" ,
136
- "address" ,
137
- "audio" ,
138
- "cite" ,
139
- "dd" ,
140
- "del" ,
141
- "details" ,
142
- // "dialog",
143
- "dfn" ,
144
- // "figure",
145
- "form" ,
146
- "iframe" ,
147
- "ins" ,
148
- "kbd" ,
149
- "object" ,
150
- "q" ,
151
- "sub" ,
152
- "s" ,
153
- "samp" ,
154
- "svg" ,
155
- "table" ,
156
- "time" ,
157
- "var" ,
158
- "video" ,
159
- "wbr" ,
160
- ] ) ;
218
+ ts . keep ( TAGS_TO_KEEP ) ; // tags run through `keepReplacement` function if match
219
+
220
+ if ( this . preservedSelectors ) {
221
+ let preserved = Array . from ( this . preservedSelectors ) ;
222
+ ts . addRule ( "keep-via-classes" , {
223
+ filter : function ( node ) {
224
+ return preserved . find ( cls => MarkdownToHtml . hasClass ( node , cls ) ) ;
225
+ } ,
226
+ replacement : ( content , node ) => {
227
+ return node . outerHTML ;
228
+ }
229
+ } ) ;
230
+ }
161
231
162
232
ts . addRule ( "pre-without-code-to-fenced-codeblock" , {
163
233
filter : [ "pre" ] ,
0 commit comments