@@ -97,6 +97,8 @@ String _renderSafeHtml(
9797}) {
9898 // Filter unsafe urls on some of the elements.
9999 nodes.forEach ((node) => node.accept (_UnsafeUrlFilter ()));
100+ // Transform GitHub task lists.
101+ nodes.forEach ((node) => node.accept (_TaskListRewriteNodeVisitor ()));
100102
101103 if (! disableHashIds) {
102104 // add hash link HTML to header blocks
@@ -277,6 +279,38 @@ class _RelativeUrlRewriter implements m.NodeVisitor {
277279 }
278280}
279281
282+ /// HTML sanitization will remove the rendered `<input type="checkbox">` elements,
283+ /// we are replacing them with icons.
284+ class _TaskListRewriteNodeVisitor implements m.NodeVisitor {
285+ @override
286+ void visitElementAfter (m.Element element) {
287+ if (element.tag != 'li' ) {
288+ return ;
289+ }
290+ if (! (element.attributes['class' ]? .contains ('task-list-item' ) ?? false )) {
291+ return ;
292+ }
293+ final children = element.children;
294+ if (children == null || children.isEmpty) {
295+ return ;
296+ }
297+ final first = children.first;
298+ if (first is m.Element &&
299+ first.tag == 'input' &&
300+ first.attributes['type' ] == 'checkbox' ) {
301+ final checked = first.attributes['checked' ] == 'true' ;
302+ children.removeAt (0 );
303+ children.insert (0 , m.Text (checked ? '✅ ' : '❌ ' ));
304+ }
305+ }
306+
307+ @override
308+ bool visitElementBefore (m.Element element) => true ;
309+
310+ @override
311+ void visitText (m.Text text) {}
312+ }
313+
280314bool _isAbsolute (String url) => url.contains (':' );
281315
282316String _rewriteAbsoluteUrl (String url) {
0 commit comments